@cognipeer/agent-sdk 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -9
- package/dist/index.cjs +1934 -290
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +341 -35
- package/dist/index.d.ts +341 -35
- package/dist/index.mjs +1922 -292
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -2
package/dist/index.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,527 @@ 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
|
+
const serialized = JSON.stringify(value);
|
|
2143
|
+
if (typeof serialized === "string") {
|
|
2144
|
+
return serialized;
|
|
2145
|
+
}
|
|
2146
|
+
} catch {
|
|
2147
|
+
}
|
|
2148
|
+
return String(value);
|
|
2149
|
+
}
|
|
2150
|
+
function summarizeObject(value) {
|
|
2151
|
+
if (value == null) return "null";
|
|
2152
|
+
if (typeof value === "string") {
|
|
2153
|
+
return value.length > 240 ? `${value.slice(0, 240)}...` : value;
|
|
2154
|
+
}
|
|
2155
|
+
if (Array.isArray(value)) {
|
|
2156
|
+
const preview = value.slice(0, 3).map((entry) => safeStringify(entry).slice(0, 120)).join(" | ");
|
|
2157
|
+
return `array(length=${value.length}) ${preview}`.trim();
|
|
2158
|
+
}
|
|
2159
|
+
if (typeof value === "object") {
|
|
2160
|
+
const keys = Object.keys(value);
|
|
2161
|
+
const preview = keys.slice(0, 6).map((key) => `${key}=${safeStringify(value[key]).slice(0, 80)}`).join("; ");
|
|
2162
|
+
return `object(keys=${keys.length}) ${preview}`.trim();
|
|
2163
|
+
}
|
|
2164
|
+
return safeStringify(value);
|
|
2165
|
+
}
|
|
2166
|
+
function classifyToolResponse(toolName, output, config) {
|
|
2167
|
+
const serialized = safeStringify(output);
|
|
2168
|
+
const tokenCount = countApproxTokens(serialized);
|
|
2169
|
+
const criticalTools = new Set(config.toolResponses.criticalTools);
|
|
2170
|
+
if (criticalTools.has(toolName)) return "critical";
|
|
2171
|
+
if (tokenCount >= config.toolResponses.maxToolResponseTokens || serialized.length >= config.toolResponses.maxToolResponseChars) {
|
|
2172
|
+
return "verbose";
|
|
2173
|
+
}
|
|
2174
|
+
if (serialized === "ok" || serialized === "true" || serialized === "null") {
|
|
2175
|
+
return "redundant";
|
|
2176
|
+
}
|
|
2177
|
+
return "informative";
|
|
2178
|
+
}
|
|
2179
|
+
function resolveToolResponsePolicy(toolName, output, config) {
|
|
2180
|
+
const serialized = safeStringify(output);
|
|
2181
|
+
const tokenCount = countApproxTokens(serialized);
|
|
2182
|
+
const classification = classifyToolResponse(toolName, output, config);
|
|
2183
|
+
const byTool = config.toolResponses.toolResponseRetentionByTool[toolName];
|
|
2184
|
+
let retentionPolicy = byTool || config.toolResponses.defaultPolicy;
|
|
2185
|
+
if (classification === "critical") {
|
|
2186
|
+
retentionPolicy = byTool || "keep_structured";
|
|
2187
|
+
} else if (classification === "verbose") {
|
|
2188
|
+
retentionPolicy = byTool || config.toolResponses.largeResponsePolicy;
|
|
2189
|
+
} else if (classification === "redundant") {
|
|
2190
|
+
retentionPolicy = byTool || "drop";
|
|
2191
|
+
}
|
|
2192
|
+
if (retentionPolicy === "keep_full") {
|
|
2193
|
+
const summary2 = "";
|
|
2194
|
+
return { classification, retentionPolicy, content: serialized, rawOutput: output, summary: summary2, tokenCount };
|
|
2195
|
+
}
|
|
2196
|
+
const summary = summarizeObject(output);
|
|
2197
|
+
if (retentionPolicy === "keep_structured") {
|
|
2198
|
+
return { classification, retentionPolicy, content: summary, rawOutput: output, summary, tokenCount };
|
|
2199
|
+
}
|
|
2200
|
+
if (retentionPolicy === "summarize_archive") {
|
|
2201
|
+
return {
|
|
2202
|
+
classification,
|
|
2203
|
+
retentionPolicy,
|
|
2204
|
+
content: `ARCHIVED_TOOL_RESPONSE: ${summary}`,
|
|
2205
|
+
rawOutput: output,
|
|
2206
|
+
summary,
|
|
2207
|
+
tokenCount
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
return {
|
|
2211
|
+
classification,
|
|
2212
|
+
retentionPolicy,
|
|
2213
|
+
content: `DROPPED_TOOL_RESPONSE: ${summary}`,
|
|
2214
|
+
rawOutput: output,
|
|
2215
|
+
summary,
|
|
2216
|
+
tokenCount
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
function validateToolArgs(tool, args) {
|
|
2220
|
+
const schema = tool.schema;
|
|
2221
|
+
if (!schema || typeof schema.parse !== "function") {
|
|
2222
|
+
return { ok: true, value: args };
|
|
2223
|
+
}
|
|
2224
|
+
try {
|
|
2225
|
+
return { ok: true, value: schema.parse(args) };
|
|
2226
|
+
} catch (error) {
|
|
2227
|
+
if (error instanceof ZodError) {
|
|
2228
|
+
const message = error.issues.map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`).join("; ");
|
|
2229
|
+
return { ok: false, message };
|
|
2230
|
+
}
|
|
2231
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
// src/nodes/tools.ts
|
|
2236
|
+
function normalizeMaxExecutionsPerRun(value) {
|
|
2237
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
2238
|
+
const parsed = Number(value);
|
|
2239
|
+
if (!Number.isFinite(parsed)) return null;
|
|
2240
|
+
const normalized = Math.trunc(parsed);
|
|
2241
|
+
return normalized >= 0 ? normalized : null;
|
|
2242
|
+
}
|
|
2243
|
+
function countSuccessfulToolExecutions(toolHistory, toolName) {
|
|
2244
|
+
if (!Array.isArray(toolHistory)) return 0;
|
|
2245
|
+
return toolHistory.filter((entry) => entry?.toolName === toolName && (entry?.status === "success" || entry?.status === "handoff")).length;
|
|
2246
|
+
}
|
|
1463
2247
|
function createToolsNode(initialTools, opts) {
|
|
1464
|
-
const baseToolByName = /* @__PURE__ */ new Map();
|
|
1465
|
-
for (const t of initialTools) baseToolByName.set(t.name, t);
|
|
1466
2248
|
return async (state) => {
|
|
1467
2249
|
const runtime = state.agent || {
|
|
1468
2250
|
name: opts?.name,
|
|
@@ -1470,15 +2252,17 @@ function createToolsNode(initialTools, opts) {
|
|
|
1470
2252
|
tools: initialTools,
|
|
1471
2253
|
limits: opts?.limits,
|
|
1472
2254
|
systemPrompt: opts?.systemPrompt,
|
|
2255
|
+
todoListPrompt: opts?.todoListPrompt,
|
|
1473
2256
|
useTodoList: opts?.useTodoList,
|
|
1474
2257
|
outputSchema: opts?.outputSchema
|
|
1475
2258
|
};
|
|
2259
|
+
const resolved = getResolvedSmartConfig(opts || {}, runtime);
|
|
1476
2260
|
const activeTools = runtime.tools;
|
|
1477
2261
|
const toolByName = /* @__PURE__ */ new Map();
|
|
1478
|
-
for (const
|
|
2262
|
+
for (const tool of activeTools) toolByName.set(tool.name, tool);
|
|
1479
2263
|
const limits = {
|
|
1480
|
-
maxToolCalls: runtime.limits?.maxToolCalls ?? 10,
|
|
1481
|
-
maxParallelTools: Math.max(1, runtime.limits?.maxParallelTools ?? 1)
|
|
2264
|
+
maxToolCalls: runtime.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10,
|
|
2265
|
+
maxParallelTools: Math.max(1, runtime.limits?.maxParallelTools ?? resolved.limits.maxParallelTools ?? 1)
|
|
1482
2266
|
};
|
|
1483
2267
|
const appended = [];
|
|
1484
2268
|
const onEvent = state.ctx?.__onEvent;
|
|
@@ -1489,20 +2273,19 @@ function createToolsNode(initialTools, opts) {
|
|
|
1489
2273
|
const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
|
|
1490
2274
|
const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
|
|
1491
2275
|
let awaitingApproval = false;
|
|
1492
|
-
for (const
|
|
1493
|
-
const
|
|
1494
|
-
if (
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
2276
|
+
for (const tool of toolByName.values()) {
|
|
2277
|
+
const anyTool = tool;
|
|
2278
|
+
if (anyTool._stateRef && typeof anyTool._stateRef === "object") {
|
|
2279
|
+
anyTool._stateRef.toolHistory = state.toolHistory;
|
|
2280
|
+
anyTool._stateRef.toolHistoryArchived = state.toolHistoryArchived;
|
|
2281
|
+
anyTool._stateRef.__onEvent = onEvent;
|
|
1498
2282
|
}
|
|
1499
2283
|
}
|
|
1500
2284
|
const last = state.messages[state.messages.length - 1];
|
|
1501
2285
|
let toolCount = state.toolCallCount || 0;
|
|
1502
|
-
const toolCalls = Array.isArray(
|
|
1503
|
-
last?.tool_calls
|
|
1504
|
-
) ? last.tool_calls : [];
|
|
2286
|
+
const toolCalls = Array.isArray(last?.tool_calls) ? last.tool_calls : [];
|
|
1505
2287
|
const toolHistory = state.toolHistory || [];
|
|
2288
|
+
const toolHistoryArchived = state.toolHistoryArchived || [];
|
|
1506
2289
|
const remaining = Math.max(0, limits.maxToolCalls - toolCount);
|
|
1507
2290
|
const planned = toolCalls.slice(0, remaining);
|
|
1508
2291
|
const skipped = toolCalls.slice(remaining);
|
|
@@ -1511,6 +2294,17 @@ function createToolsNode(initialTools, opts) {
|
|
|
1511
2294
|
if (cancellationToken && cancellationToken.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
|
|
1512
2295
|
return { cancelled: false, reason: void 0 };
|
|
1513
2296
|
};
|
|
2297
|
+
const markToolFailure = (message, toolName, toolCallId) => {
|
|
2298
|
+
const ctx = { ...state.ctx || {} };
|
|
2299
|
+
ctx.__lastToolError = message;
|
|
2300
|
+
if (toolCallId) {
|
|
2301
|
+
ctx.__toolSchemaError = { toolName, toolCallId, message };
|
|
2302
|
+
}
|
|
2303
|
+
if (resolved.watchdog.autoReplanOnFailure) {
|
|
2304
|
+
ctx.__planNeedsReplan = true;
|
|
2305
|
+
}
|
|
2306
|
+
state.ctx = ctx;
|
|
2307
|
+
};
|
|
1514
2308
|
const runOne = async (tc) => {
|
|
1515
2309
|
const cancelState = isCancelled();
|
|
1516
2310
|
if (cancelState.cancelled) {
|
|
@@ -1520,15 +2314,10 @@ function createToolsNode(initialTools, opts) {
|
|
|
1520
2314
|
onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
|
|
1521
2315
|
return { status: "error" };
|
|
1522
2316
|
}
|
|
1523
|
-
const
|
|
1524
|
-
if (!
|
|
2317
|
+
const tool = toolByName.get(tc.name);
|
|
2318
|
+
if (!tool) {
|
|
2319
|
+
appended.push({ role: "tool", content: `Tool not found: ${tc.name}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
1525
2320
|
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
2321
|
toolCount += 1;
|
|
1533
2322
|
return { status: "error" };
|
|
1534
2323
|
}
|
|
@@ -1536,22 +2325,67 @@ function createToolsNode(initialTools, opts) {
|
|
|
1536
2325
|
if (typeof args === "string") {
|
|
1537
2326
|
try {
|
|
1538
2327
|
args = JSON.parse(args);
|
|
1539
|
-
} catch
|
|
2328
|
+
} catch {
|
|
1540
2329
|
}
|
|
1541
2330
|
}
|
|
1542
2331
|
const toolCallId = tc.id || `${tc.name}_${toolCount + 1}`;
|
|
2332
|
+
const validatedArgs = validateToolArgs(tool, args);
|
|
2333
|
+
if (!validatedArgs.ok && resolved.toolResponses.schemaValidation === "strict") {
|
|
2334
|
+
const errorMessage = `Tool argument validation failed for ${tc.name}: ${validatedArgs.message}`;
|
|
2335
|
+
appended.push({ role: "tool", content: errorMessage, tool_call_id: toolCallId, name: tc.name });
|
|
2336
|
+
markToolFailure(errorMessage, tc.name, toolCallId);
|
|
2337
|
+
onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args, error: { message: errorMessage } });
|
|
2338
|
+
toolCount += 1;
|
|
2339
|
+
return { status: "error" };
|
|
2340
|
+
}
|
|
2341
|
+
if (validatedArgs.ok) {
|
|
2342
|
+
args = validatedArgs.value;
|
|
2343
|
+
}
|
|
2344
|
+
const toolName = tool.name || tc.name;
|
|
2345
|
+
const maxExecutionsPerRun = normalizeMaxExecutionsPerRun(tool.maxExecutionsPerRun);
|
|
2346
|
+
if (maxExecutionsPerRun !== null) {
|
|
2347
|
+
const currentExecutions = countSuccessfulToolExecutions(toolHistory, toolName);
|
|
2348
|
+
if (currentExecutions >= maxExecutionsPerRun) {
|
|
2349
|
+
const limitMessage = `Skipped tool due to per-tool execution limit: ${toolName} (${maxExecutionsPerRun}/run)`;
|
|
2350
|
+
appended.push({ role: "tool", content: limitMessage, tool_call_id: toolCallId, name: tc.name });
|
|
2351
|
+
onEvent?.({ type: "tool_call", phase: "skipped", name: toolName, id: tc.id, args });
|
|
2352
|
+
recordTraceEvent(traceSession, {
|
|
2353
|
+
type: "tool_call",
|
|
2354
|
+
label: `Tool Skipped - ${toolName}`,
|
|
2355
|
+
actor: { scope: "tool", name: toolName, role: "tool" },
|
|
2356
|
+
status: "skipped",
|
|
2357
|
+
toolExecutionId: tc.id,
|
|
2358
|
+
messageList: [
|
|
2359
|
+
{
|
|
2360
|
+
role: "assistant",
|
|
2361
|
+
name: toolName,
|
|
2362
|
+
content: limitMessage,
|
|
2363
|
+
tool_calls: [
|
|
2364
|
+
{
|
|
2365
|
+
id: tc.id,
|
|
2366
|
+
type: "function",
|
|
2367
|
+
function: { name: toolName, arguments: sanitizeTracePayload(args) }
|
|
2368
|
+
}
|
|
2369
|
+
]
|
|
2370
|
+
}
|
|
2371
|
+
]
|
|
2372
|
+
});
|
|
2373
|
+
toolCount += 1;
|
|
2374
|
+
return { status: "error" };
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
1543
2377
|
let approvalEntry = pendingByCallId.get(toolCallId);
|
|
1544
|
-
const needsApproval = Boolean(
|
|
2378
|
+
const needsApproval = Boolean(tool.needsApproval);
|
|
1545
2379
|
if (needsApproval) {
|
|
1546
2380
|
if (!approvalEntry) {
|
|
1547
2381
|
approvalEntry = {
|
|
1548
2382
|
id: nanoid(),
|
|
1549
2383
|
toolCallId,
|
|
1550
|
-
toolName:
|
|
2384
|
+
toolName: tool.name || tc.name,
|
|
1551
2385
|
args,
|
|
1552
2386
|
status: "pending",
|
|
1553
2387
|
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1554
|
-
metadata:
|
|
2388
|
+
metadata: tool.approvalPrompt || tool.approvalDefaults ? { prompt: tool.approvalPrompt, defaults: tool.approvalDefaults } : void 0
|
|
1555
2389
|
};
|
|
1556
2390
|
pendingApprovals.push(approvalEntry);
|
|
1557
2391
|
pendingByCallId.set(toolCallId, approvalEntry);
|
|
@@ -1573,71 +2407,97 @@ function createToolsNode(initialTools, opts) {
|
|
|
1573
2407
|
}
|
|
1574
2408
|
if (approvalEntry.status === "rejected") {
|
|
1575
2409
|
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 });
|
|
2410
|
+
appended.push({ role: "tool", content: `Tool call rejected: ${rejectionMessage}`, tool_call_id: toolCallId, name: tc.name });
|
|
1583
2411
|
approvalEntry.metadata = { ...approvalEntry.metadata || {}, resolution: "rejected" };
|
|
1584
2412
|
approvalEntry.status = "executed";
|
|
1585
2413
|
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
2414
|
pendingByCallId.set(toolCallId, approvalEntry);
|
|
2415
|
+
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" });
|
|
2416
|
+
onEvent?.({ type: "tool_approval", status: "rejected", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, comment: approvalEntry.comment, decidedBy: approvalEntry.decidedBy });
|
|
1588
2417
|
toolCount += 1;
|
|
1589
2418
|
return { status: "rejected", approval: approvalEntry };
|
|
1590
2419
|
}
|
|
1591
|
-
if (approvalEntry.status === "approved") {
|
|
1592
|
-
|
|
1593
|
-
args = approvalEntry.approvedArgs;
|
|
1594
|
-
}
|
|
2420
|
+
if (approvalEntry.status === "approved" && approvalEntry.approvedArgs !== void 0) {
|
|
2421
|
+
args = approvalEntry.approvedArgs;
|
|
1595
2422
|
onEvent?.({ type: "tool_approval", status: "approved", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, decidedBy: approvalEntry.decidedBy, comment: approvalEntry.comment });
|
|
1596
2423
|
}
|
|
1597
2424
|
}
|
|
1598
2425
|
const start = Date.now();
|
|
2426
|
+
const sanitizedArgs = sanitizeTracePayload(args);
|
|
2427
|
+
const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
|
|
1599
2428
|
try {
|
|
1600
|
-
onEvent?.({ type: "tool_call", phase: "start", name:
|
|
2429
|
+
onEvent?.({ type: "tool_call", phase: "start", name: toolName, id: tc.id, args });
|
|
1601
2430
|
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;
|
|
2431
|
+
const anyTool = tool;
|
|
1606
2432
|
const callOptions = { cancellationToken, signal: abortSignal };
|
|
2433
|
+
let output;
|
|
1607
2434
|
if (typeof anyTool.func === "function") output = await anyTool.func(args, callOptions);
|
|
1608
2435
|
else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args, callOptions);
|
|
1609
2436
|
else if (typeof anyTool.call === "function") output = await anyTool.call(args, callOptions);
|
|
1610
2437
|
else if (typeof anyTool._call === "function") output = await anyTool._call(args, callOptions);
|
|
1611
2438
|
else if (typeof anyTool.run === "function") output = await anyTool.run(args, callOptions);
|
|
1612
2439
|
else throw new Error("Tool is not invokable");
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
toolHistory.push({ executionId
|
|
2440
|
+
const durationMs = Date.now() - start;
|
|
2441
|
+
const executionId = nanoid();
|
|
2442
|
+
if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
|
|
2443
|
+
toolHistory.push({ executionId, toolName, args, output: "handoff:ok", rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "handoff" });
|
|
1617
2444
|
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;
|
|
2445
|
+
state.agent = output.__handoff.runtime;
|
|
2446
|
+
onEvent?.({ type: "handoff", from: runtime.name, to: state.agent?.name, toolName });
|
|
2447
|
+
onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: "handoff", durationMs });
|
|
1622
2448
|
if (needsApproval && approvalEntry) {
|
|
1623
2449
|
approvalEntry.status = "executed";
|
|
1624
2450
|
approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1625
|
-
approvalEntry.executionId =
|
|
2451
|
+
approvalEntry.executionId = executionId;
|
|
1626
2452
|
pendingByCallId.set(toolCallId, approvalEntry);
|
|
1627
2453
|
}
|
|
2454
|
+
toolCount += 1;
|
|
1628
2455
|
return { status: "success", approval: approvalEntry };
|
|
1629
2456
|
}
|
|
1630
|
-
const content = typeof output === "string" ? output : JSON.stringify(output);
|
|
1631
2457
|
if (output && typeof output === "object" && output.__finalStructuredOutput) {
|
|
1632
2458
|
if (!state.ctx) state.ctx = {};
|
|
1633
2459
|
state.ctx.__structuredOutputParsed = output.data;
|
|
1634
2460
|
state.ctx.__finalizedDueToStructuredOutput = true;
|
|
1635
2461
|
}
|
|
1636
|
-
const
|
|
1637
|
-
const
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
2462
|
+
const responsePolicy = resolveToolResponsePolicy(toolName, output, resolved);
|
|
2463
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2464
|
+
const storedOutput = responsePolicy.retentionPolicy === "keep_full" ? output : responsePolicy.summary;
|
|
2465
|
+
toolHistory.push({
|
|
2466
|
+
executionId,
|
|
2467
|
+
toolName,
|
|
2468
|
+
args,
|
|
2469
|
+
output: storedOutput,
|
|
2470
|
+
rawOutput: output,
|
|
2471
|
+
timestamp,
|
|
2472
|
+
tool_call_id: tc.id,
|
|
2473
|
+
summarized: responsePolicy.retentionPolicy !== "keep_full",
|
|
2474
|
+
originalTokenCount: responsePolicy.tokenCount,
|
|
2475
|
+
classification: responsePolicy.classification,
|
|
2476
|
+
retentionPolicy: responsePolicy.retentionPolicy,
|
|
2477
|
+
summary: responsePolicy.summary,
|
|
2478
|
+
archiveId: responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop" ? executionId : void 0,
|
|
2479
|
+
status: "success"
|
|
2480
|
+
});
|
|
2481
|
+
if (resolved.context.archiveLargeToolResponses && (responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop")) {
|
|
2482
|
+
toolHistoryArchived.push({
|
|
2483
|
+
executionId,
|
|
2484
|
+
toolName,
|
|
2485
|
+
args,
|
|
2486
|
+
output,
|
|
2487
|
+
rawOutput: output,
|
|
2488
|
+
timestamp,
|
|
2489
|
+
tool_call_id: tc.id,
|
|
2490
|
+
summarized: true,
|
|
2491
|
+
originalTokenCount: responsePolicy.tokenCount,
|
|
2492
|
+
classification: responsePolicy.classification,
|
|
2493
|
+
retentionPolicy: responsePolicy.retentionPolicy,
|
|
2494
|
+
summary: responsePolicy.summary,
|
|
2495
|
+
archiveId: executionId,
|
|
2496
|
+
status: "success"
|
|
2497
|
+
});
|
|
2498
|
+
}
|
|
2499
|
+
appended.push({ role: "tool", content: responsePolicy.content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
2500
|
+
onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: output, durationMs });
|
|
1641
2501
|
onProgress?.({ stage: "tools", message: `Tool ${tc.name} completed`, detail: { durationMs } });
|
|
1642
2502
|
if (needsApproval && approvalEntry) {
|
|
1643
2503
|
approvalEntry.status = "executed";
|
|
@@ -1647,7 +2507,6 @@ function createToolsNode(initialTools, opts) {
|
|
|
1647
2507
|
}
|
|
1648
2508
|
const sanitizedOutput = sanitizeTracePayload(output);
|
|
1649
2509
|
const outputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedOutput) : void 0;
|
|
1650
|
-
const toolName = t.name || tc.name;
|
|
1651
2510
|
const messageList = [
|
|
1652
2511
|
{
|
|
1653
2512
|
role: "assistant",
|
|
@@ -1667,7 +2526,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1667
2526
|
{
|
|
1668
2527
|
role: "tool",
|
|
1669
2528
|
name: toolName,
|
|
1670
|
-
content:
|
|
2529
|
+
content: responsePolicy.content
|
|
1671
2530
|
}
|
|
1672
2531
|
];
|
|
1673
2532
|
recordTraceEvent(traceSession, {
|
|
@@ -1678,43 +2537,24 @@ function createToolsNode(initialTools, opts) {
|
|
|
1678
2537
|
requestBytes: inputBytes,
|
|
1679
2538
|
responseBytes: outputBytes,
|
|
1680
2539
|
toolExecutionId: executionId,
|
|
1681
|
-
messageList
|
|
2540
|
+
messageList,
|
|
2541
|
+
debug: {
|
|
2542
|
+
classification: responsePolicy.classification,
|
|
2543
|
+
retentionPolicy: responsePolicy.retentionPolicy,
|
|
2544
|
+
originalTokenCount: responsePolicy.tokenCount
|
|
2545
|
+
}
|
|
1682
2546
|
});
|
|
1683
2547
|
toolCount += 1;
|
|
1684
2548
|
return { status: "success", approval: approvalEntry };
|
|
1685
|
-
} catch (
|
|
2549
|
+
} catch (error) {
|
|
1686
2550
|
const durationMs = Date.now() - start;
|
|
1687
2551
|
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
|
-
];
|
|
2552
|
+
const message = error?.message || String(error);
|
|
2553
|
+
toolHistory.push({ executionId, toolName, args, output: `Error executing tool: ${message}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "error" });
|
|
2554
|
+
appended.push({ role: "tool", content: `Error executing tool: ${message}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
|
|
2555
|
+
markToolFailure(message, toolName, toolCallId);
|
|
2556
|
+
onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args, error: { message } });
|
|
2557
|
+
onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: message } });
|
|
1718
2558
|
recordTraceEvent(traceSession, {
|
|
1719
2559
|
type: "tool_call",
|
|
1720
2560
|
label: `Tool Error - ${toolName}`,
|
|
@@ -1723,8 +2563,26 @@ function createToolsNode(initialTools, opts) {
|
|
|
1723
2563
|
durationMs,
|
|
1724
2564
|
requestBytes: inputBytes,
|
|
1725
2565
|
toolExecutionId: executionId,
|
|
1726
|
-
error: { message
|
|
1727
|
-
messageList
|
|
2566
|
+
error: { message, stack: error?.stack },
|
|
2567
|
+
messageList: [
|
|
2568
|
+
{
|
|
2569
|
+
role: "assistant",
|
|
2570
|
+
name: toolName,
|
|
2571
|
+
content: "",
|
|
2572
|
+
tool_calls: [
|
|
2573
|
+
{
|
|
2574
|
+
id: tc.id || executionId,
|
|
2575
|
+
type: "function",
|
|
2576
|
+
function: { name: toolName, arguments: sanitizedArgs }
|
|
2577
|
+
}
|
|
2578
|
+
]
|
|
2579
|
+
},
|
|
2580
|
+
{
|
|
2581
|
+
role: "tool",
|
|
2582
|
+
name: toolName,
|
|
2583
|
+
content: `Error executing tool: ${message}`
|
|
2584
|
+
}
|
|
2585
|
+
]
|
|
1728
2586
|
});
|
|
1729
2587
|
toolCount += 1;
|
|
1730
2588
|
return { status: "error", approval: approvalEntry };
|
|
@@ -1741,7 +2599,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1741
2599
|
break;
|
|
1742
2600
|
}
|
|
1743
2601
|
const result = await runOne(tc);
|
|
1744
|
-
if (result
|
|
2602
|
+
if (result.status === "awaiting_approval") {
|
|
1745
2603
|
awaitingApproval = true;
|
|
1746
2604
|
break;
|
|
1747
2605
|
}
|
|
@@ -1749,37 +2607,28 @@ function createToolsNode(initialTools, opts) {
|
|
|
1749
2607
|
for (const tc of skipped) {
|
|
1750
2608
|
onEvent?.({ type: "tool_call", phase: "skipped", name: tc.name, id: tc.id, args: tc.args });
|
|
1751
2609
|
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
2610
|
recordTraceEvent(traceSession, {
|
|
1770
2611
|
type: "tool_call",
|
|
1771
2612
|
label: `Tool Skipped - ${tc.name}`,
|
|
1772
2613
|
actor: { scope: "tool", name: tc.name, role: "tool" },
|
|
1773
2614
|
status: "skipped",
|
|
1774
2615
|
toolExecutionId: tc.id,
|
|
1775
|
-
messageList
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
2616
|
+
messageList: [
|
|
2617
|
+
{
|
|
2618
|
+
role: "assistant",
|
|
2619
|
+
name: tc.name,
|
|
2620
|
+
content: `Skipped tool due to tool-call limit: ${tc.name}`,
|
|
2621
|
+
tool_calls: [
|
|
2622
|
+
{
|
|
2623
|
+
id: tc.id,
|
|
2624
|
+
type: "function",
|
|
2625
|
+
function: { name: tc.name, arguments: sanitizedArgs }
|
|
2626
|
+
}
|
|
2627
|
+
]
|
|
2628
|
+
}
|
|
2629
|
+
]
|
|
1782
2630
|
});
|
|
2631
|
+
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
2632
|
toolCount += 1;
|
|
1784
2633
|
}
|
|
1785
2634
|
if (!awaitingApproval && state.ctx?.__awaitingApproval) {
|
|
@@ -1794,6 +2643,7 @@ function createToolsNode(initialTools, opts) {
|
|
|
1794
2643
|
messages: [...state.messages, ...appended],
|
|
1795
2644
|
toolCallCount: toolCount,
|
|
1796
2645
|
toolHistory,
|
|
2646
|
+
toolHistoryArchived,
|
|
1797
2647
|
agent: state.agent,
|
|
1798
2648
|
pendingApprovals
|
|
1799
2649
|
};
|
|
@@ -2018,68 +2868,20 @@ function resolveToolApprovalState(state, resolution) {
|
|
|
2018
2868
|
};
|
|
2019
2869
|
}
|
|
2020
2870
|
|
|
2021
|
-
// src/
|
|
2022
|
-
function
|
|
2023
|
-
if (!
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
return Math.ceil(asciiCount / 4 + nonAsciiCount / 1.5);
|
|
2027
|
-
}
|
|
2028
|
-
function extractMessageText(m) {
|
|
2029
|
-
if (!m) return "";
|
|
2030
|
-
const parts = [];
|
|
2031
|
-
parts.push(m.role || "");
|
|
2032
|
-
if (m.name) parts.push(m.name);
|
|
2033
|
-
if (typeof m.content === "string") {
|
|
2034
|
-
parts.push(m.content);
|
|
2035
|
-
} else if (Array.isArray(m.content)) {
|
|
2036
|
-
for (const c of m.content) {
|
|
2037
|
-
if (typeof c === "string") parts.push(c);
|
|
2038
|
-
else if (c?.text) parts.push(c.text);
|
|
2039
|
-
else if (c?.content) parts.push(c.content);
|
|
2040
|
-
else if (c?.type === "tool_use" && c?.input) {
|
|
2041
|
-
parts.push(c.name || "");
|
|
2042
|
-
parts.push(typeof c.input === "string" ? c.input : JSON.stringify(c.input));
|
|
2043
|
-
} else if (c != null && typeof c === "object") {
|
|
2044
|
-
parts.push(JSON.stringify(c));
|
|
2045
|
-
}
|
|
2046
|
-
}
|
|
2047
|
-
} else if (m.content != null && typeof m.content === "object") {
|
|
2048
|
-
parts.push(JSON.stringify(m.content));
|
|
2049
|
-
}
|
|
2050
|
-
if (Array.isArray(m.tool_calls)) {
|
|
2051
|
-
for (const tc of m.tool_calls) {
|
|
2052
|
-
if (tc?.function?.name) parts.push(tc.function.name);
|
|
2053
|
-
if (tc?.function?.arguments) {
|
|
2054
|
-
parts.push(
|
|
2055
|
-
typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
|
|
2056
|
-
);
|
|
2057
|
-
}
|
|
2058
|
-
if (tc?.name && !tc?.function?.name) parts.push(tc.name);
|
|
2059
|
-
if (tc?.args && !tc?.function?.arguments) {
|
|
2060
|
-
parts.push(typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args));
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2871
|
+
// src/agent.ts
|
|
2872
|
+
function isSyntheticSummaryMessage(message) {
|
|
2873
|
+
if (!message) return false;
|
|
2874
|
+
if (message.role === "tool" && message.name === "summarize_context") {
|
|
2875
|
+
return true;
|
|
2063
2876
|
}
|
|
2064
|
-
if (Array.isArray(
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
|
|
2070
|
-
);
|
|
2071
|
-
}
|
|
2072
|
-
}
|
|
2877
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
|
|
2878
|
+
return message.tool_calls.some((toolCall) => {
|
|
2879
|
+
const toolName = toolCall?.function?.name || toolCall?.name;
|
|
2880
|
+
return toolName === "summarize_context";
|
|
2881
|
+
});
|
|
2073
2882
|
}
|
|
2074
|
-
return
|
|
2075
|
-
}
|
|
2076
|
-
function countMessagesTokens(messages) {
|
|
2077
|
-
if (!Array.isArray(messages) || messages.length === 0) return 0;
|
|
2078
|
-
const allText = messages.map(extractMessageText).join("\n");
|
|
2079
|
-
return countApproxTokens(allText) + messages.length * 4;
|
|
2883
|
+
return false;
|
|
2080
2884
|
}
|
|
2081
|
-
|
|
2082
|
-
// src/agent.ts
|
|
2083
2885
|
function createAgent(opts) {
|
|
2084
2886
|
const resolver = createResolverNode();
|
|
2085
2887
|
const agentCore = createAgentCoreNode(opts);
|
|
@@ -2122,6 +2924,7 @@ function createAgent(opts) {
|
|
|
2122
2924
|
tools: toolsBase,
|
|
2123
2925
|
guardrails: opts.guardrails,
|
|
2124
2926
|
systemPrompt: void 0,
|
|
2927
|
+
todoListPrompt: opts.todoListPrompt,
|
|
2125
2928
|
limits: opts.limits,
|
|
2126
2929
|
useTodoList: void 0,
|
|
2127
2930
|
outputSchema: opts.outputSchema,
|
|
@@ -2197,6 +3000,18 @@ function createAgent(opts) {
|
|
|
2197
3000
|
};
|
|
2198
3001
|
while (iterations < iterationLimit) {
|
|
2199
3002
|
iterations++;
|
|
3003
|
+
const traceSession = state.ctx?.__traceSession;
|
|
3004
|
+
if (traceSession) {
|
|
3005
|
+
const iterEvent = recordTraceEvent(traceSession, {
|
|
3006
|
+
type: "agent_iteration",
|
|
3007
|
+
label: `Iteration ${iterations}`,
|
|
3008
|
+
actor: { scope: "agent", name: opts.name || "agent", role: "orchestrator" },
|
|
3009
|
+
parentSpanId: traceSession.rootSpanId
|
|
3010
|
+
});
|
|
3011
|
+
if (iterEvent) {
|
|
3012
|
+
traceSession.currentIterationSpanId = iterEvent.spanId;
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
2200
3015
|
if (cancelIfRequested("loop")) break;
|
|
2201
3016
|
const skippingAgent = resumeStage === "tools";
|
|
2202
3017
|
if (!skippingAgent) {
|
|
@@ -2247,11 +3062,21 @@ function createAgent(opts) {
|
|
|
2247
3062
|
maxTok = 5e4;
|
|
2248
3063
|
}
|
|
2249
3064
|
if (maxTok) {
|
|
2250
|
-
const
|
|
3065
|
+
const tokCountMessages = (state.messages || []).filter((message) => {
|
|
3066
|
+
if (isSyntheticSummaryMessage(message)) return false;
|
|
3067
|
+
if (message.role === "system" && (message.name === "context_summary" || message.name === "memory_context")) return false;
|
|
3068
|
+
return true;
|
|
3069
|
+
});
|
|
3070
|
+
const tokenCount = countMessagesTokens(tokCountMessages);
|
|
2251
3071
|
if (tokenCount > maxTok) {
|
|
2252
|
-
const
|
|
2253
|
-
|
|
2254
|
-
|
|
3072
|
+
const hasFreshSummary = (state.messages || []).some(
|
|
3073
|
+
(m) => m.role === "tool" && m.content === "SUMMARIZED"
|
|
3074
|
+
);
|
|
3075
|
+
if (hasFreshSummary && tokenCount <= maxTok * 1.15) ; else {
|
|
3076
|
+
const ctx = { ...state.ctx || {}, __needsSummarization: true };
|
|
3077
|
+
state = { ...state, ctx };
|
|
3078
|
+
break;
|
|
3079
|
+
}
|
|
2255
3080
|
}
|
|
2256
3081
|
}
|
|
2257
3082
|
onProgress?.({ stage: "agent", message: "Invoking model" });
|
|
@@ -2489,7 +3314,11 @@ function createAgent(opts) {
|
|
|
2489
3314
|
schema,
|
|
2490
3315
|
func: async ({ input }) => {
|
|
2491
3316
|
const res = await instance.invoke({ messages: [{ role: "user", content: input }] });
|
|
2492
|
-
return {
|
|
3317
|
+
return {
|
|
3318
|
+
content: res.content,
|
|
3319
|
+
output: res.output,
|
|
3320
|
+
summary: res.state?.summaries?.[res.state.summaries.length - 1]
|
|
3321
|
+
};
|
|
2493
3322
|
}
|
|
2494
3323
|
});
|
|
2495
3324
|
},
|
|
@@ -2530,6 +3359,7 @@ function createAgentNode(opts) {
|
|
|
2530
3359
|
model: opts.model,
|
|
2531
3360
|
tools: opts.tools || [],
|
|
2532
3361
|
systemPrompt: opts.systemPrompt,
|
|
3362
|
+
todoListPrompt: opts.todoListPrompt,
|
|
2533
3363
|
limits: opts.limits,
|
|
2534
3364
|
useTodoList: opts.useTodoList,
|
|
2535
3365
|
outputSchema: opts.outputSchema
|
|
@@ -2547,7 +3377,8 @@ function createAgentNode(opts) {
|
|
|
2547
3377
|
content: buildSystemPrompt(
|
|
2548
3378
|
[runtime.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
|
|
2549
3379
|
runtime.useTodoList === true,
|
|
2550
|
-
runtime.name || "Agent"
|
|
3380
|
+
runtime.name || "Agent",
|
|
3381
|
+
runtime.todoListPrompt
|
|
2551
3382
|
)
|
|
2552
3383
|
};
|
|
2553
3384
|
const messages = [systemMsg, ...state.messages];
|
|
@@ -2629,9 +3460,119 @@ function createAgentNode(opts) {
|
|
|
2629
3460
|
};
|
|
2630
3461
|
}
|
|
2631
3462
|
|
|
3463
|
+
// src/smart/contextPolicy.ts
|
|
3464
|
+
function messageText(message) {
|
|
3465
|
+
if (typeof message.content === "string") return message.content;
|
|
3466
|
+
if (Array.isArray(message.content)) {
|
|
3467
|
+
return message.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? JSON.stringify(part)).join(" ");
|
|
3468
|
+
}
|
|
3469
|
+
return "";
|
|
3470
|
+
}
|
|
3471
|
+
function collectRecentTurns(messages, lastTurnsToKeep) {
|
|
3472
|
+
if (lastTurnsToKeep <= 0) return [];
|
|
3473
|
+
const systemPrefix = messages.filter((message, index) => index === 0 && message.role === "system");
|
|
3474
|
+
const body = systemPrefix.length > 0 ? messages.slice(1) : [...messages];
|
|
3475
|
+
let userTurnCount = 0;
|
|
3476
|
+
const collected = [];
|
|
3477
|
+
for (let index = body.length - 1; index >= 0; index -= 1) {
|
|
3478
|
+
const message = body[index];
|
|
3479
|
+
collected.unshift(message);
|
|
3480
|
+
if (message.role === "user") {
|
|
3481
|
+
userTurnCount += 1;
|
|
3482
|
+
if (userTurnCount >= lastTurnsToKeep) break;
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
return [...systemPrefix, ...collected];
|
|
3486
|
+
}
|
|
3487
|
+
function renderStructuredSummary(summary) {
|
|
3488
|
+
if (!summary) return "";
|
|
3489
|
+
const lines = ["Context summary:"];
|
|
3490
|
+
if (summary.stable_facts.length > 0) {
|
|
3491
|
+
lines.push("Stable facts:");
|
|
3492
|
+
lines.push(...summary.stable_facts.map((fact) => `- ${fact.key}: ${fact.value}`));
|
|
3493
|
+
}
|
|
3494
|
+
if (summary.active_goals.length > 0) {
|
|
3495
|
+
lines.push("Active goals:");
|
|
3496
|
+
lines.push(...summary.active_goals.map((goal) => `- ${goal}`));
|
|
3497
|
+
}
|
|
3498
|
+
if (summary.open_questions.length > 0) {
|
|
3499
|
+
lines.push("Open questions:");
|
|
3500
|
+
lines.push(...summary.open_questions.map((question) => `- ${question}`));
|
|
3501
|
+
}
|
|
3502
|
+
if (summary.discarded_obsolete.length > 0) {
|
|
3503
|
+
lines.push("Discarded obsolete:");
|
|
3504
|
+
lines.push(...summary.discarded_obsolete.map((item) => `- ${item}`));
|
|
3505
|
+
}
|
|
3506
|
+
return lines.join("\n");
|
|
3507
|
+
}
|
|
3508
|
+
function renderMemoryBlock(facts) {
|
|
3509
|
+
if (!facts || facts.length === 0) return "";
|
|
3510
|
+
return [
|
|
3511
|
+
"Retrieved memory:",
|
|
3512
|
+
...facts.map((fact) => `- ${fact.key}: ${fact.value}`)
|
|
3513
|
+
].join("\n");
|
|
3514
|
+
}
|
|
3515
|
+
function clampToBudget(messages, maxContextTokens) {
|
|
3516
|
+
let working = [...messages];
|
|
3517
|
+
while (working.length > 2) {
|
|
3518
|
+
const tokenCount = countApproxTokens(working.map(messageText).join("\n"));
|
|
3519
|
+
if (tokenCount <= maxContextTokens) return working;
|
|
3520
|
+
const firstNonSystem = working.findIndex((message, index) => !(index === 0 && message.role === "system"));
|
|
3521
|
+
if (firstNonSystem < 0) break;
|
|
3522
|
+
working.splice(firstNonSystem, 1);
|
|
3523
|
+
}
|
|
3524
|
+
return working;
|
|
3525
|
+
}
|
|
3526
|
+
function buildModelMessages(state, config) {
|
|
3527
|
+
const rawMessages = state.messages || [];
|
|
3528
|
+
if (config.context.policy === "raw") {
|
|
3529
|
+
return clampToBudget(rawMessages, config.limits.maxContextTokens);
|
|
3530
|
+
}
|
|
3531
|
+
const recentMessages = collectRecentTurns(rawMessages, config.context.lastTurnsToKeep);
|
|
3532
|
+
const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
|
|
3533
|
+
const summaryText = renderStructuredSummary(latestSummary);
|
|
3534
|
+
const memoryText = renderMemoryBlock(state.memoryFacts);
|
|
3535
|
+
const systemMessage2 = recentMessages[0]?.role === "system" ? recentMessages[0] : void 0;
|
|
3536
|
+
const body = systemMessage2 ? recentMessages.slice(1) : recentMessages;
|
|
3537
|
+
const syntheticContextMessages = [];
|
|
3538
|
+
if (summaryText) {
|
|
3539
|
+
syntheticContextMessages.push({ role: "system", name: "context_summary", content: summaryText });
|
|
3540
|
+
}
|
|
3541
|
+
if (memoryText) {
|
|
3542
|
+
syntheticContextMessages.push({ role: "system", name: "memory_context", content: memoryText });
|
|
3543
|
+
}
|
|
3544
|
+
const assembled = [
|
|
3545
|
+
...systemMessage2 ? [systemMessage2] : [],
|
|
3546
|
+
...syntheticContextMessages,
|
|
3547
|
+
...config.context.policy === "summary_only" ? body.filter((message) => message.role === "user" || message.role === "assistant").slice(-2) : body
|
|
3548
|
+
];
|
|
3549
|
+
return clampToBudget(assembled, config.limits.maxContextTokens);
|
|
3550
|
+
}
|
|
3551
|
+
function estimateContextRotScore(state) {
|
|
3552
|
+
const summaryCount = state.summaryRecords?.length || 0;
|
|
3553
|
+
const archivedCount = state.toolHistoryArchived?.length || 0;
|
|
3554
|
+
const messageCount = state.messages?.length || 0;
|
|
3555
|
+
const summarizedMessages = state.messages?.filter((message) => message.role === "tool" && message.content === "SUMMARIZED").length || 0;
|
|
3556
|
+
const numerator = summaryCount * 2 + archivedCount + summarizedMessages;
|
|
3557
|
+
const denominator = Math.max(messageCount, 1);
|
|
3558
|
+
return Number((numerator / denominator).toFixed(3));
|
|
3559
|
+
}
|
|
3560
|
+
|
|
2632
3561
|
// src/nodes/contextSummarize.ts
|
|
2633
3562
|
var systemMessage = (content) => ({ role: "system", content });
|
|
2634
3563
|
var humanMessage = (content) => ({ role: "user", content });
|
|
3564
|
+
function isSyntheticSummaryToolMessage(message) {
|
|
3565
|
+
return message.role === "tool" && message.name === "summarize_context";
|
|
3566
|
+
}
|
|
3567
|
+
function isSyntheticSummaryAssistantMessage(message) {
|
|
3568
|
+
if (message.role !== "assistant" || !Array.isArray(message.tool_calls)) {
|
|
3569
|
+
return false;
|
|
3570
|
+
}
|
|
3571
|
+
return message.tool_calls.some((toolCall) => {
|
|
3572
|
+
const toolName = toolCall?.function?.name || toolCall?.name;
|
|
3573
|
+
return toolName === "summarize_context";
|
|
3574
|
+
});
|
|
3575
|
+
}
|
|
2635
3576
|
function validateMessageSequence(messages) {
|
|
2636
3577
|
const validated = [];
|
|
2637
3578
|
const pendingToolCallIds = /* @__PURE__ */ new Set();
|
|
@@ -2657,33 +3598,137 @@ function validateMessageSequence(messages) {
|
|
|
2657
3598
|
}
|
|
2658
3599
|
return validated;
|
|
2659
3600
|
}
|
|
2660
|
-
function
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
3601
|
+
function extractJsonObject(text) {
|
|
3602
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
3603
|
+
if (fenced?.[1]) return fenced[1].trim();
|
|
3604
|
+
const start = text.indexOf("{");
|
|
3605
|
+
const end = text.lastIndexOf("}");
|
|
3606
|
+
if (start >= 0 && end > start) {
|
|
3607
|
+
return text.slice(start, end + 1);
|
|
3608
|
+
}
|
|
3609
|
+
return null;
|
|
3610
|
+
}
|
|
3611
|
+
function normalizeStructuredSummary(input, fallbackText) {
|
|
3612
|
+
const summary = {
|
|
3613
|
+
stable_facts: Array.isArray(input?.stable_facts) ? input.stable_facts.filter((fact) => fact && typeof fact === "object").map((fact) => ({
|
|
3614
|
+
key: String(fact.key || "unknown"),
|
|
3615
|
+
value: String(fact.value || ""),
|
|
3616
|
+
confidence: typeof fact.confidence === "number" ? fact.confidence : 0.7,
|
|
3617
|
+
source: fact.source ? String(fact.source) : void 0
|
|
3618
|
+
})) : [],
|
|
3619
|
+
active_goals: Array.isArray(input?.active_goals) ? input.active_goals.map((goal) => String(goal)) : [],
|
|
3620
|
+
open_questions: Array.isArray(input?.open_questions) ? input.open_questions.map((question) => String(question)) : [],
|
|
3621
|
+
discarded_obsolete: Array.isArray(input?.discarded_obsolete || input?.obsolete_discarded) ? (input.discarded_obsolete || input.obsolete_discarded).map((item) => String(item)) : [],
|
|
3622
|
+
rawSummary: typeof input?.rawSummary === "string" ? input.rawSummary : fallbackText
|
|
3623
|
+
};
|
|
3624
|
+
if (summary.stable_facts.length === 0 && fallbackText) {
|
|
3625
|
+
const importantLines = fallbackText.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, 8);
|
|
3626
|
+
summary.stable_facts = importantLines.map((line, index) => ({
|
|
3627
|
+
key: `fact_${index + 1}`,
|
|
3628
|
+
value: line,
|
|
3629
|
+
confidence: 0.5
|
|
3630
|
+
}));
|
|
3631
|
+
}
|
|
3632
|
+
return summary;
|
|
3633
|
+
}
|
|
3634
|
+
function normalizeFactKeySegment(value) {
|
|
3635
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "item";
|
|
3636
|
+
}
|
|
3637
|
+
function extractCanonicalFacts(messages) {
|
|
3638
|
+
const facts = /* @__PURE__ */ new Map();
|
|
3639
|
+
for (const message of messages) {
|
|
3640
|
+
const content = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content.map((part) => part?.text ?? part?.content ?? String(part ?? "")).join("\n") : "";
|
|
3641
|
+
if (!content) continue;
|
|
3642
|
+
const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
3643
|
+
for (const line of lines) {
|
|
3644
|
+
if (!/^[A-Z0-9_]+\|/.test(line) || !line.includes("=")) continue;
|
|
3645
|
+
const segments = line.split("|").map((segment) => segment.trim()).filter(Boolean);
|
|
3646
|
+
const label = segments[0];
|
|
3647
|
+
const entries = segments.slice(1).map((segment) => {
|
|
3648
|
+
const eqIndex = segment.indexOf("=");
|
|
3649
|
+
if (eqIndex <= 0) return null;
|
|
3650
|
+
return {
|
|
3651
|
+
key: segment.slice(0, eqIndex).trim(),
|
|
3652
|
+
value: segment.slice(eqIndex + 1).trim()
|
|
3653
|
+
};
|
|
3654
|
+
}).filter((entry) => Boolean(entry?.key));
|
|
3655
|
+
if (entries.length === 0) continue;
|
|
3656
|
+
const identifier = entries.find((entry) => ["code", "id", "project", "name", "key"].includes(entry.key.toLowerCase()))?.value;
|
|
3657
|
+
const keyPrefix = [normalizeFactKeySegment(label), identifier ? normalizeFactKeySegment(identifier) : void 0].filter(Boolean).join(".");
|
|
3658
|
+
for (const entry of entries) {
|
|
3659
|
+
const normalizedEntryKey = normalizeFactKeySegment(entry.key);
|
|
3660
|
+
const factKey = keyPrefix ? `${keyPrefix}.${normalizedEntryKey}` : `${normalizeFactKeySegment(label)}.${normalizedEntryKey}`;
|
|
3661
|
+
facts.set(factKey, {
|
|
3662
|
+
key: factKey,
|
|
3663
|
+
value: entry.value,
|
|
3664
|
+
confidence: 0.98,
|
|
3665
|
+
source: "canonical_tool_output"
|
|
3666
|
+
});
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
return [...facts.values()];
|
|
3671
|
+
}
|
|
3672
|
+
function mergeStableFacts(summary, additionalFacts) {
|
|
3673
|
+
if (additionalFacts.length === 0) return summary;
|
|
3674
|
+
const merged = new Map(summary.stable_facts.map((fact) => [fact.key, fact]));
|
|
3675
|
+
for (const fact of additionalFacts) {
|
|
3676
|
+
const existing = merged.get(fact.key);
|
|
3677
|
+
if (!existing || (fact.confidence || 0) >= (existing.confidence || 0)) {
|
|
3678
|
+
merged.set(fact.key, fact);
|
|
3679
|
+
}
|
|
2670
3680
|
}
|
|
2671
3681
|
return {
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
summaryPromptMaxTokens: 8e3,
|
|
2675
|
-
promptTemplate: void 0
|
|
3682
|
+
...summary,
|
|
3683
|
+
stable_facts: [...merged.values()]
|
|
2676
3684
|
};
|
|
2677
3685
|
}
|
|
3686
|
+
function runIntegrityCheck(previous, current) {
|
|
3687
|
+
const notes = [];
|
|
3688
|
+
const previousKeys = new Set((previous?.stable_facts || []).map((fact) => fact.key));
|
|
3689
|
+
const currentKeys = new Set(current.stable_facts.map((fact) => fact.key));
|
|
3690
|
+
const obsoleteKeys = new Set(current.discarded_obsolete);
|
|
3691
|
+
const criticalFactLoss = [...previousKeys].some((key) => !obsoleteKeys.has(key) && !currentKeys.has(key));
|
|
3692
|
+
const obsoleteFactRevived = current.stable_facts.some((fact) => obsoleteKeys.has(fact.key));
|
|
3693
|
+
if (criticalFactLoss) {
|
|
3694
|
+
notes.push("Missing previously retained facts; merged forward during integrity repair.");
|
|
3695
|
+
}
|
|
3696
|
+
if (obsoleteFactRevived) {
|
|
3697
|
+
notes.push("Obsolete facts reappeared; removed during integrity repair.");
|
|
3698
|
+
}
|
|
3699
|
+
return {
|
|
3700
|
+
passed: !criticalFactLoss && !obsoleteFactRevived,
|
|
3701
|
+
criticalFactLoss,
|
|
3702
|
+
obsoleteFactRevived,
|
|
3703
|
+
notes
|
|
3704
|
+
};
|
|
3705
|
+
}
|
|
3706
|
+
function repairStructuredSummary(previous, current, integrity) {
|
|
3707
|
+
if (!previous) return current;
|
|
3708
|
+
const nextFacts = [...current.stable_facts];
|
|
3709
|
+
if (integrity.criticalFactLoss) {
|
|
3710
|
+
const existingKeys = new Set(nextFacts.map((fact) => fact.key));
|
|
3711
|
+
for (const fact of previous.stable_facts) {
|
|
3712
|
+
if (!existingKeys.has(fact.key) && !current.discarded_obsolete.includes(fact.key)) {
|
|
3713
|
+
nextFacts.push(fact);
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
const filteredFacts = integrity.obsoleteFactRevived ? nextFacts.filter((fact) => !current.discarded_obsolete.includes(fact.key)) : nextFacts;
|
|
3718
|
+
return { ...current, stable_facts: filteredFacts };
|
|
3719
|
+
}
|
|
2678
3720
|
function createContextSummarizeNode(opts) {
|
|
2679
3721
|
return async (state) => {
|
|
2680
|
-
const
|
|
2681
|
-
|
|
3722
|
+
const resolved = getResolvedSmartConfig(opts, state.agent);
|
|
3723
|
+
const config = resolved.summarization;
|
|
3724
|
+
if (!config.enable) return {};
|
|
2682
3725
|
const model = opts.model;
|
|
2683
3726
|
const messages = state.messages || [];
|
|
2684
3727
|
const onEvent = state.ctx?.__onEvent;
|
|
2685
3728
|
const traceSession = state.ctx?.__traceSession;
|
|
2686
|
-
const compressableMessages = messages.filter(
|
|
3729
|
+
const compressableMessages = messages.filter(
|
|
3730
|
+
(m) => m.role === "tool" && m.content !== "SUMMARIZED" && !isSyntheticSummaryToolMessage(m)
|
|
3731
|
+
);
|
|
2687
3732
|
if (compressableMessages.length === 0) {
|
|
2688
3733
|
return {};
|
|
2689
3734
|
}
|
|
@@ -2695,6 +3740,9 @@ function createContextSummarizeNode(opts) {
|
|
|
2695
3740
|
let accumulatedTokens = 0;
|
|
2696
3741
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2697
3742
|
const m = messages[i];
|
|
3743
|
+
if (isSyntheticSummaryToolMessage(m) || isSyntheticSummaryAssistantMessage(m)) {
|
|
3744
|
+
continue;
|
|
3745
|
+
}
|
|
2698
3746
|
let content = "";
|
|
2699
3747
|
if (typeof m.content === "string") content = m.content;
|
|
2700
3748
|
else if (Array.isArray(m.content)) {
|
|
@@ -2706,39 +3754,60 @@ function createContextSummarizeNode(opts) {
|
|
|
2706
3754
|
}
|
|
2707
3755
|
let prefix = `${m.role.toUpperCase()}`;
|
|
2708
3756
|
if (m.name) prefix += ` (${m.name})`;
|
|
2709
|
-
const
|
|
2710
|
-
const messageTokens = countApproxTokens(
|
|
3757
|
+
const messageText2 = `${prefix}: ${content}`;
|
|
3758
|
+
const messageTokens = countApproxTokens(messageText2);
|
|
2711
3759
|
if (accumulatedTokens + messageTokens > availableTokensForConversation) {
|
|
2712
3760
|
messageTexts.unshift("[Earlier conversation history omitted for brevity]");
|
|
2713
3761
|
break;
|
|
2714
3762
|
}
|
|
2715
|
-
messageTexts.unshift(
|
|
3763
|
+
messageTexts.unshift(messageText2);
|
|
2716
3764
|
accumulatedTokens += messageTokens;
|
|
2717
3765
|
}
|
|
2718
3766
|
const conversationText = messageTexts.join("\n\n");
|
|
3767
|
+
const canonicalFacts = extractCanonicalFacts(compressableMessages);
|
|
2719
3768
|
const previousSummary = Array.isArray(state.summaries) && state.summaries.length > 0 ? state.summaries[state.summaries.length - 1] : "";
|
|
2720
|
-
const
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
3769
|
+
const previousStructuredSummary = Array.isArray(state.summaryRecords) && state.summaryRecords.length > 0 ? state.summaryRecords[state.summaryRecords.length - 1] : void 0;
|
|
3770
|
+
const defaultPrompt = `Summarize the conversation into strict JSON.
|
|
3771
|
+
|
|
3772
|
+
Return exactly one JSON object with this schema:
|
|
3773
|
+
{
|
|
3774
|
+
"stable_facts": [{ "key": string, "value": string, "confidence": number }],
|
|
3775
|
+
"active_goals": [string],
|
|
3776
|
+
"open_questions": [string],
|
|
3777
|
+
"discarded_obsolete": [string],
|
|
3778
|
+
"rawSummary": string
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
Rules:
|
|
3782
|
+
- Keep stable_facts only for facts that future turns must remember.
|
|
3783
|
+
- Put invalidated or superseded fact keys in discarded_obsolete.
|
|
3784
|
+
- Preserve active_goals still relevant to the user.
|
|
3785
|
+
- Preserve unresolved questions.
|
|
3786
|
+
- Do not include prose outside JSON.
|
|
3787
|
+
- Target compression ratio: ${config.summaryCompressionRatioTarget}.
|
|
3788
|
+
- Summary mode: ${config.summaryMode}.
|
|
2726
3789
|
|
|
2727
|
-
Previous summary
|
|
3790
|
+
Previous summary text:
|
|
2728
3791
|
${previousSummary || "(none)"}
|
|
2729
3792
|
|
|
3793
|
+
Previous structured summary:
|
|
3794
|
+
${previousStructuredSummary ? JSON.stringify(previousStructuredSummary) : "(none)"}
|
|
3795
|
+
|
|
2730
3796
|
Conversation:
|
|
2731
3797
|
${conversationText}
|
|
2732
3798
|
|
|
2733
|
-
|
|
3799
|
+
Canonical facts extracted from tool outputs:
|
|
3800
|
+
${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fact.value}`).join("\n") : "(none)"}`;
|
|
2734
3801
|
const template = config.promptTemplate || defaultPrompt;
|
|
2735
3802
|
const promptBody = template.replace(/\{\{\s*conversation\s*\}\}/g, conversationText).replace(/\{\{\s*previousSummary\s*\}\}/g, previousSummary || "");
|
|
2736
3803
|
const summaryPrompt = [
|
|
2737
|
-
systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
|
|
3804
|
+
systemMessage("You are a helpful assistant that summarizes conversation history efficiently. Return strict JSON only."),
|
|
2738
3805
|
humanMessage(promptBody)
|
|
2739
3806
|
];
|
|
2740
3807
|
const inputTokensEstimate = countApproxTokens(summaryPrompt.map((m) => m.content).join("\n"));
|
|
2741
3808
|
let summaryText = "Summary unavailable.";
|
|
3809
|
+
let structuredSummary;
|
|
3810
|
+
let integrity;
|
|
2742
3811
|
let outputTokensEstimate = 0;
|
|
2743
3812
|
let durationMs = 0;
|
|
2744
3813
|
let inputTokensActual;
|
|
@@ -2747,10 +3816,23 @@ Summary:`;
|
|
|
2747
3816
|
let totalTokensActual;
|
|
2748
3817
|
const startTime = Date.now();
|
|
2749
3818
|
try {
|
|
3819
|
+
if (!model || typeof model.invoke !== "function") {
|
|
3820
|
+
throw new Error("Summarization model is unavailable.");
|
|
3821
|
+
}
|
|
2750
3822
|
const response = await model.invoke(summaryPrompt);
|
|
2751
3823
|
durationMs = Date.now() - startTime;
|
|
2752
3824
|
summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
|
|
2753
3825
|
outputTokensEstimate = countApproxTokens(summaryText);
|
|
3826
|
+
const jsonText = extractJsonObject(summaryText);
|
|
3827
|
+
const parsed = jsonText ? JSON.parse(jsonText) : void 0;
|
|
3828
|
+
structuredSummary = normalizeStructuredSummary(parsed, summaryText);
|
|
3829
|
+
structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
|
|
3830
|
+
integrity = config.integrityCheck ? runIntegrityCheck(previousStructuredSummary, structuredSummary) : { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: [] };
|
|
3831
|
+
if (integrity && !integrity.passed) {
|
|
3832
|
+
structuredSummary = repairStructuredSummary(previousStructuredSummary, structuredSummary, integrity);
|
|
3833
|
+
integrity = { ...integrity, passed: true };
|
|
3834
|
+
}
|
|
3835
|
+
summaryText = renderStructuredSummary(structuredSummary);
|
|
2754
3836
|
const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
|
|
2755
3837
|
const normalized = normalizeUsage(rawUsage);
|
|
2756
3838
|
if (normalized) {
|
|
@@ -2761,17 +3843,22 @@ Summary:`;
|
|
|
2761
3843
|
}
|
|
2762
3844
|
} catch (err) {
|
|
2763
3845
|
durationMs = Date.now() - startTime;
|
|
2764
|
-
|
|
3846
|
+
structuredSummary = normalizeStructuredSummary(void 0, previousSummary || conversationText.slice(0, 800));
|
|
3847
|
+
structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
|
|
3848
|
+
integrity = { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: ["Fallback summary generated locally."] };
|
|
3849
|
+
summaryText = renderStructuredSummary(structuredSummary);
|
|
2765
3850
|
const errorEvent = {
|
|
2766
3851
|
type: "summarization",
|
|
2767
|
-
summary:
|
|
2768
|
-
messagesCompressed:
|
|
3852
|
+
summary: summaryText,
|
|
3853
|
+
messagesCompressed: compressableMessages.length,
|
|
2769
3854
|
inputTokens: inputTokensEstimate,
|
|
2770
|
-
outputTokens:
|
|
3855
|
+
outputTokens: countApproxTokens(summaryText),
|
|
2771
3856
|
durationMs,
|
|
2772
3857
|
previousSummary: previousSummary || void 0,
|
|
2773
3858
|
tokenCountBefore,
|
|
2774
|
-
tokenCountAfter: tokenCountBefore
|
|
3859
|
+
tokenCountAfter: tokenCountBefore,
|
|
3860
|
+
structuredSummary,
|
|
3861
|
+
integrity
|
|
2775
3862
|
};
|
|
2776
3863
|
onEvent?.(errorEvent);
|
|
2777
3864
|
recordTraceEvent(traceSession, {
|
|
@@ -2781,11 +3868,10 @@ Summary:`;
|
|
|
2781
3868
|
status: "error",
|
|
2782
3869
|
durationMs,
|
|
2783
3870
|
inputTokens: inputTokensEstimate,
|
|
2784
|
-
outputTokens:
|
|
3871
|
+
outputTokens: countApproxTokens(summaryText),
|
|
2785
3872
|
error: { message: err?.message || String(err), stack: err?.stack },
|
|
2786
3873
|
messageList: sanitizeTracePayload(summaryPrompt)
|
|
2787
3874
|
});
|
|
2788
|
-
return {};
|
|
2789
3875
|
}
|
|
2790
3876
|
const toolCallIdToAssistantIdx = /* @__PURE__ */ new Map();
|
|
2791
3877
|
messages.forEach((m, idx) => {
|
|
@@ -2799,6 +3885,9 @@ Summary:`;
|
|
|
2799
3885
|
});
|
|
2800
3886
|
const newMessages = messages.map((m) => {
|
|
2801
3887
|
if (m.role === "tool") {
|
|
3888
|
+
if (isSyntheticSummaryToolMessage(m)) {
|
|
3889
|
+
return m;
|
|
3890
|
+
}
|
|
2802
3891
|
const toolCallId = m.tool_call_id;
|
|
2803
3892
|
if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
|
|
2804
3893
|
return null;
|
|
@@ -2836,6 +3925,7 @@ Summary:`;
|
|
|
2836
3925
|
content: summaryText
|
|
2837
3926
|
};
|
|
2838
3927
|
const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
|
|
3928
|
+
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() }];
|
|
2839
3929
|
const finalMessages = [...validatedMessages, assistantSummaryCall, toolSummaryResponse];
|
|
2840
3930
|
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");
|
|
2841
3931
|
const tokenCountAfter = countApproxTokens(allTextAfter);
|
|
@@ -2851,8 +3941,10 @@ Summary:`;
|
|
|
2851
3941
|
previousSummary: previousSummary || void 0,
|
|
2852
3942
|
tokenCountBefore,
|
|
2853
3943
|
tokenCountAfter,
|
|
2854
|
-
archivedCount: compressableMessages.length
|
|
3944
|
+
archivedCount: compressableMessages.length,
|
|
2855
3945
|
// deprecated field for backward compatibility
|
|
3946
|
+
structuredSummary,
|
|
3947
|
+
integrity
|
|
2856
3948
|
};
|
|
2857
3949
|
onEvent?.(summarizationEvent);
|
|
2858
3950
|
recordTraceEvent(traceSession, {
|
|
@@ -2876,12 +3968,19 @@ Summary:`;
|
|
|
2876
3968
|
tokensSaved: tokenCountBefore - tokenCountAfter,
|
|
2877
3969
|
previousSummaryLength: previousSummary?.length || 0,
|
|
2878
3970
|
usageFromModel: inputTokensActual !== void 0,
|
|
2879
|
-
cachedInputTokens
|
|
3971
|
+
cachedInputTokens,
|
|
3972
|
+
integrity
|
|
2880
3973
|
}
|
|
2881
3974
|
});
|
|
2882
3975
|
return {
|
|
2883
3976
|
messages: finalMessages,
|
|
2884
|
-
summaries
|
|
3977
|
+
summaries,
|
|
3978
|
+
summaryRecords,
|
|
3979
|
+
watchdog: {
|
|
3980
|
+
...state.watchdog || {},
|
|
3981
|
+
compactions: (state.watchdog?.compactions || 0) + 1,
|
|
3982
|
+
lastAction: "summarized"
|
|
3983
|
+
}
|
|
2885
3984
|
};
|
|
2886
3985
|
};
|
|
2887
3986
|
}
|
|
@@ -2904,38 +4003,200 @@ async function applyTokenLimits({
|
|
|
2904
4003
|
}
|
|
2905
4004
|
return { ...state, messages: newMessages };
|
|
2906
4005
|
}
|
|
4006
|
+
var todoStatusSchema = z.enum(["not-started", "in-progress", "completed", "blocked"]);
|
|
4007
|
+
var todoWriteItemSchema = z.object({
|
|
4008
|
+
id: z.number().int().min(1).describe("Sequential id starting from 1"),
|
|
4009
|
+
step: z.string().min(1).optional(),
|
|
4010
|
+
title: z.string().min(1).optional(),
|
|
4011
|
+
description: z.string().min(1).optional(),
|
|
4012
|
+
owner: z.string().min(1).optional(),
|
|
4013
|
+
exitCriteria: z.string().min(1).optional(),
|
|
4014
|
+
status: todoStatusSchema,
|
|
4015
|
+
evidence: z.string().max(300).optional()
|
|
4016
|
+
});
|
|
4017
|
+
var todoUpdateItemSchema = z.object({
|
|
4018
|
+
id: z.number().int().min(1).describe("Existing todo id to update"),
|
|
4019
|
+
step: z.string().min(1).optional(),
|
|
4020
|
+
title: z.string().min(1).optional(),
|
|
4021
|
+
description: z.string().min(1).optional(),
|
|
4022
|
+
owner: z.string().min(1).optional(),
|
|
4023
|
+
exitCriteria: z.string().min(1).optional(),
|
|
4024
|
+
status: todoStatusSchema.optional(),
|
|
4025
|
+
evidence: z.string().max(300).optional()
|
|
4026
|
+
});
|
|
4027
|
+
function normalizeTodoItem(item, existing) {
|
|
4028
|
+
const id = item.id ?? existing?.id;
|
|
4029
|
+
const step = item.step || item.title || item.description || existing?.step || existing?.title || existing?.description || `Step ${id}`;
|
|
4030
|
+
const title = item.title || item.step || existing?.title || existing?.step || `Step ${id}`;
|
|
4031
|
+
const description = item.description || item.step || item.title || existing?.description || existing?.step || existing?.title || `Step ${id}`;
|
|
4032
|
+
return {
|
|
4033
|
+
...existing,
|
|
4034
|
+
...item,
|
|
4035
|
+
id,
|
|
4036
|
+
step,
|
|
4037
|
+
title,
|
|
4038
|
+
description,
|
|
4039
|
+
owner: item.owner || existing?.owner || "agent",
|
|
4040
|
+
exitCriteria: item.exitCriteria || existing?.exitCriteria || description || `Complete step ${id}`,
|
|
4041
|
+
status: item.status || existing?.status,
|
|
4042
|
+
evidence: item.evidence ?? existing?.evidence
|
|
4043
|
+
};
|
|
4044
|
+
}
|
|
4045
|
+
function validatePlanInvariants(todoList, requireSequentialIds) {
|
|
4046
|
+
const ids = todoList.map((item) => item.id);
|
|
4047
|
+
const uniqueIds = new Set(ids);
|
|
4048
|
+
if (uniqueIds.size !== ids.length) {
|
|
4049
|
+
return { ok: false, error: "Todo ids must be unique." };
|
|
4050
|
+
}
|
|
4051
|
+
if (requireSequentialIds) {
|
|
4052
|
+
const expectedIds = Array.from({ length: todoList.length }, (_, index) => index + 1);
|
|
4053
|
+
const isSequential = ids.every((id, index) => id === expectedIds[index]);
|
|
4054
|
+
if (!isSequential) {
|
|
4055
|
+
return { ok: false, error: "Todo ids must be sequential starting from 1 for write operations." };
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
const inProgressCount = todoList.filter((item) => item.status === "in-progress").length;
|
|
4059
|
+
if (inProgressCount > 1) {
|
|
4060
|
+
return { ok: false, error: "Only one todo item may be in-progress at a time." };
|
|
4061
|
+
}
|
|
4062
|
+
return { ok: true };
|
|
4063
|
+
}
|
|
4064
|
+
function calculateAdherenceScore(todoList) {
|
|
4065
|
+
const completedCount = todoList.filter((item) => item.status === "completed").length;
|
|
4066
|
+
return todoList.length === 0 ? 1 : Number((completedCount / todoList.length).toFixed(2));
|
|
4067
|
+
}
|
|
2907
4068
|
function createContextTools(stateRef, opts) {
|
|
2908
4069
|
const tools = [];
|
|
2909
4070
|
if (opts?.planningEnabled) {
|
|
2910
4071
|
const manageTodo = createTool({
|
|
2911
4072
|
name: "manage_todo_list",
|
|
2912
|
-
description: "Manage a structured todo list to track progress and plan tasks throughout your coding session
|
|
4073
|
+
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",
|
|
2913
4074
|
schema: z.object({
|
|
2914
|
-
operation: z.enum(["write", "read"]),
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
title: z.string().min(1),
|
|
2918
|
-
description: z.string().min(1),
|
|
2919
|
-
status: z.enum(["not-started", "in-progress", "completed"]),
|
|
2920
|
-
evidence: z.string().max(200).optional()
|
|
2921
|
-
})).optional()
|
|
4075
|
+
operation: z.enum(["write", "read", "update"]),
|
|
4076
|
+
expectedVersion: z.number().int().min(0).optional(),
|
|
4077
|
+
todoList: z.array(z.union([todoWriteItemSchema, todoUpdateItemSchema])).optional()
|
|
2922
4078
|
}),
|
|
2923
|
-
func: async ({ operation, todoList }) => {
|
|
4079
|
+
func: async ({ operation, expectedVersion, todoList }) => {
|
|
2924
4080
|
const onEvent = manageTodo._stateRef?.__onEvent;
|
|
4081
|
+
const currentVersion = stateRef.planVersion || 0;
|
|
2925
4082
|
if (operation === "read") {
|
|
2926
4083
|
const list = stateRef.todoList || [];
|
|
2927
|
-
onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list });
|
|
4084
|
+
onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
|
|
2928
4085
|
return list;
|
|
2929
4086
|
}
|
|
2930
|
-
if (
|
|
2931
|
-
|
|
4087
|
+
if (typeof expectedVersion === "number" && expectedVersion !== currentVersion) {
|
|
4088
|
+
return {
|
|
4089
|
+
status: "error",
|
|
4090
|
+
operation,
|
|
4091
|
+
error: "Plan version mismatch. Read the latest plan and retry.",
|
|
4092
|
+
version: currentVersion,
|
|
4093
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4094
|
+
};
|
|
4095
|
+
}
|
|
4096
|
+
if (!Array.isArray(todoList)) {
|
|
4097
|
+
return {
|
|
4098
|
+
status: "error",
|
|
4099
|
+
operation,
|
|
4100
|
+
error: "todoList is required for write and update operations.",
|
|
4101
|
+
version: currentVersion,
|
|
4102
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4103
|
+
};
|
|
2932
4104
|
}
|
|
4105
|
+
if (operation === "write") {
|
|
4106
|
+
const parsed = z.array(todoWriteItemSchema).safeParse(todoList);
|
|
4107
|
+
if (!parsed.success) {
|
|
4108
|
+
return {
|
|
4109
|
+
status: "error",
|
|
4110
|
+
operation,
|
|
4111
|
+
error: parsed.error.issues[0]?.message || "Invalid todoList for write.",
|
|
4112
|
+
version: currentVersion,
|
|
4113
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4114
|
+
};
|
|
4115
|
+
}
|
|
4116
|
+
const normalizedList = parsed.data.map((item) => normalizeTodoItem(item));
|
|
4117
|
+
const validation = validatePlanInvariants(normalizedList, true);
|
|
4118
|
+
if (!validation.ok) {
|
|
4119
|
+
return {
|
|
4120
|
+
status: "error",
|
|
4121
|
+
operation,
|
|
4122
|
+
error: validation.error,
|
|
4123
|
+
version: currentVersion,
|
|
4124
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4125
|
+
};
|
|
4126
|
+
}
|
|
4127
|
+
stateRef.todoList = normalizedList;
|
|
4128
|
+
} else if (operation === "update") {
|
|
4129
|
+
const parsed = z.array(todoUpdateItemSchema).safeParse(todoList);
|
|
4130
|
+
if (!parsed.success) {
|
|
4131
|
+
return {
|
|
4132
|
+
status: "error",
|
|
4133
|
+
operation,
|
|
4134
|
+
error: parsed.error.issues[0]?.message || "Invalid todoList for update.",
|
|
4135
|
+
version: currentVersion,
|
|
4136
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4137
|
+
};
|
|
4138
|
+
}
|
|
4139
|
+
const currentList = Array.isArray(stateRef.todoList) ? stateRef.todoList : [];
|
|
4140
|
+
if (currentList.length === 0) {
|
|
4141
|
+
return {
|
|
4142
|
+
status: "error",
|
|
4143
|
+
operation,
|
|
4144
|
+
error: "No existing plan to update. Use write first.",
|
|
4145
|
+
version: currentVersion,
|
|
4146
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4147
|
+
};
|
|
4148
|
+
}
|
|
4149
|
+
const patchIds = /* @__PURE__ */ new Set();
|
|
4150
|
+
for (const item of parsed.data) {
|
|
4151
|
+
if (patchIds.has(item.id)) {
|
|
4152
|
+
return {
|
|
4153
|
+
status: "error",
|
|
4154
|
+
operation,
|
|
4155
|
+
error: "Update payload contains duplicate todo ids.",
|
|
4156
|
+
version: currentVersion,
|
|
4157
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4158
|
+
};
|
|
4159
|
+
}
|
|
4160
|
+
patchIds.add(item.id);
|
|
4161
|
+
}
|
|
4162
|
+
const currentMap = new Map(currentList.map((item) => [item.id, item]));
|
|
4163
|
+
for (const item of parsed.data) {
|
|
4164
|
+
if (!currentMap.has(item.id)) {
|
|
4165
|
+
return {
|
|
4166
|
+
status: "error",
|
|
4167
|
+
operation,
|
|
4168
|
+
error: `Cannot update missing todo id ${item.id}. Use write to replace the plan.`,
|
|
4169
|
+
version: currentVersion,
|
|
4170
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4171
|
+
};
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
const mergedList = currentList.map((item) => {
|
|
4175
|
+
const patch = parsed.data.find((candidate) => candidate.id === item.id);
|
|
4176
|
+
return patch ? normalizeTodoItem(patch, item) : item;
|
|
4177
|
+
});
|
|
4178
|
+
const validation = validatePlanInvariants(mergedList, false);
|
|
4179
|
+
if (!validation.ok) {
|
|
4180
|
+
return {
|
|
4181
|
+
status: "error",
|
|
4182
|
+
operation,
|
|
4183
|
+
error: validation.error,
|
|
4184
|
+
version: currentVersion,
|
|
4185
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4186
|
+
};
|
|
4187
|
+
}
|
|
4188
|
+
stateRef.todoList = mergedList;
|
|
4189
|
+
}
|
|
4190
|
+
stateRef.planVersion = currentVersion + 1;
|
|
4191
|
+
stateRef.adherenceScore = calculateAdherenceScore(stateRef.todoList || []);
|
|
2933
4192
|
const payload = {
|
|
2934
4193
|
status: "ok",
|
|
2935
4194
|
operation,
|
|
2936
|
-
count: Array.isArray(todoList) ? todoList.length : void 0
|
|
4195
|
+
count: Array.isArray(todoList) ? todoList.length : void 0,
|
|
4196
|
+
version: stateRef.planVersion || 1,
|
|
4197
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
2937
4198
|
};
|
|
2938
|
-
onEvent?.({ type: "plan", source: "manage_todo_list", operation:
|
|
4199
|
+
onEvent?.({ type: "plan", source: "manage_todo_list", operation, todoList: stateRef.todoList, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
|
|
2939
4200
|
return payload;
|
|
2940
4201
|
}
|
|
2941
4202
|
});
|
|
@@ -2944,12 +4205,13 @@ function createContextTools(stateRef, opts) {
|
|
|
2944
4205
|
}
|
|
2945
4206
|
const getTool = createTool({
|
|
2946
4207
|
name: "get_tool_response",
|
|
2947
|
-
description: "RETRIEVE tool execution response: Use this to access the full output of a tool execution that shows as 'SUMMARIZED' in the tool history.",
|
|
4208
|
+
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.",
|
|
2948
4209
|
schema: z.object({ executionId: z.string().describe("Tool execution id") }),
|
|
2949
4210
|
func: async ({ executionId }) => {
|
|
2950
|
-
|
|
4211
|
+
const matchesExecution = (t) => t?.executionId === executionId || t?.tool_call_id === executionId;
|
|
4212
|
+
let execution = stateRef.toolHistory?.find((t) => matchesExecution(t));
|
|
2951
4213
|
if (!execution) {
|
|
2952
|
-
execution = stateRef.toolHistoryArchived?.find((t) => t
|
|
4214
|
+
execution = stateRef.toolHistoryArchived?.find((t) => matchesExecution(t));
|
|
2953
4215
|
}
|
|
2954
4216
|
if (execution) {
|
|
2955
4217
|
return execution.rawOutput || execution.output;
|
|
@@ -2980,19 +4242,33 @@ function createContextTools(stateRef, opts) {
|
|
|
2980
4242
|
}
|
|
2981
4243
|
|
|
2982
4244
|
// src/graph/decisions.ts
|
|
4245
|
+
function isSyntheticSummaryMessage2(message) {
|
|
4246
|
+
if (!message) return false;
|
|
4247
|
+
if (message.role === "tool" && message.name === "summarize_context") {
|
|
4248
|
+
return true;
|
|
4249
|
+
}
|
|
4250
|
+
if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
|
|
4251
|
+
return message.tool_calls.some((toolCall) => {
|
|
4252
|
+
const toolName = toolCall?.function?.name || toolCall?.name;
|
|
4253
|
+
return toolName === "summarize_context";
|
|
4254
|
+
});
|
|
4255
|
+
}
|
|
4256
|
+
return false;
|
|
4257
|
+
}
|
|
2983
4258
|
function needsSummarization(state, opts, summarizationEnabled) {
|
|
2984
4259
|
if (!summarizationEnabled) {
|
|
2985
4260
|
return false;
|
|
2986
4261
|
}
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
maxTok = opts.summarization.maxTokens;
|
|
4262
|
+
if (state.ctx?.__needsSummarization) {
|
|
4263
|
+
return true;
|
|
2990
4264
|
}
|
|
2991
|
-
|
|
2992
|
-
|
|
4265
|
+
const resolved = getResolvedSmartConfig(opts, state.agent);
|
|
4266
|
+
if (!resolved.summarization.enable) {
|
|
4267
|
+
return false;
|
|
2993
4268
|
}
|
|
4269
|
+
const maxTok = resolved.summarization.summaryTriggerTokens;
|
|
2994
4270
|
try {
|
|
2995
|
-
const allText = (state.messages || []).map((m) => {
|
|
4271
|
+
const allText = (state.messages || []).filter((message) => !isSyntheticSummaryMessage2(message)).map((m) => {
|
|
2996
4272
|
if (typeof m.content === "string") return m.content;
|
|
2997
4273
|
if (Array.isArray(m.content)) {
|
|
2998
4274
|
return m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? JSON.stringify(c)).join("");
|
|
@@ -3003,7 +4279,8 @@ function needsSummarization(state, opts, summarizationEnabled) {
|
|
|
3003
4279
|
return "";
|
|
3004
4280
|
}).join("\n");
|
|
3005
4281
|
const tokenCount = countApproxTokens(allText);
|
|
3006
|
-
const
|
|
4282
|
+
const contextRotScore = state.watchdog?.contextRotScore || 0;
|
|
4283
|
+
const needsSum = tokenCount > maxTok || resolved.watchdog.enabled && resolved.watchdog.autoCompaction && contextRotScore >= resolved.watchdog.contextRotThreshold;
|
|
3007
4284
|
if (process.env.DEBUG_SUMMARIZATION) {
|
|
3008
4285
|
const msgCount = (state.messages || []).length;
|
|
3009
4286
|
const toolMsgs = (state.messages || []).filter((m) => m.role === "tool");
|
|
@@ -3013,10 +4290,10 @@ function needsSummarization(state, opts, summarizationEnabled) {
|
|
|
3013
4290
|
});
|
|
3014
4291
|
console.log(`[Summarization] Messages: ${msgCount}, Tool messages: ${toolMsgs.length}`);
|
|
3015
4292
|
toolDetails.forEach((d) => console.log(` ${d}`));
|
|
3016
|
-
console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Needs summarization: ${needsSum}`);
|
|
4293
|
+
console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Context rot: ${contextRotScore}, Needs summarization: ${needsSum}`);
|
|
3017
4294
|
}
|
|
3018
4295
|
return needsSum;
|
|
3019
|
-
} catch
|
|
4296
|
+
} catch {
|
|
3020
4297
|
return false;
|
|
3021
4298
|
}
|
|
3022
4299
|
}
|
|
@@ -3034,33 +4311,205 @@ function toolsDecisionFactory(opts, summarizationEnabled) {
|
|
|
3034
4311
|
};
|
|
3035
4312
|
}
|
|
3036
4313
|
|
|
4314
|
+
// src/smart/memory.ts
|
|
4315
|
+
function normalizeFact(scope, fact) {
|
|
4316
|
+
return {
|
|
4317
|
+
...fact,
|
|
4318
|
+
scope,
|
|
4319
|
+
obsolete: fact.obsolete ?? false,
|
|
4320
|
+
lastUpdatedAt: fact.lastUpdatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4321
|
+
confidence: Number.isFinite(fact.confidence) ? fact.confidence : 0.5
|
|
4322
|
+
};
|
|
4323
|
+
}
|
|
4324
|
+
var InMemoryMemoryStore = class {
|
|
4325
|
+
constructor() {
|
|
4326
|
+
this.buckets = /* @__PURE__ */ new Map();
|
|
4327
|
+
}
|
|
4328
|
+
async get(scope, options) {
|
|
4329
|
+
const includeObsolete = options?.includeObsolete === true;
|
|
4330
|
+
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());
|
|
4331
|
+
return typeof options?.limit === "number" ? all.slice(0, options.limit) : all;
|
|
4332
|
+
}
|
|
4333
|
+
async upsert(scope, facts) {
|
|
4334
|
+
const existing = [...this.buckets.get(scope) || []];
|
|
4335
|
+
for (const fact of facts) {
|
|
4336
|
+
const normalized = normalizeFact(scope, fact);
|
|
4337
|
+
const sameKey = existing.filter((entry) => entry.key === normalized.key && !entry.obsolete);
|
|
4338
|
+
for (const entry of sameKey) {
|
|
4339
|
+
if (entry.value !== normalized.value) {
|
|
4340
|
+
entry.obsolete = true;
|
|
4341
|
+
entry.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
const match = existing.find((entry) => entry.key === normalized.key && entry.value === normalized.value && !entry.obsolete);
|
|
4345
|
+
if (match) {
|
|
4346
|
+
match.confidence = Math.max(match.confidence, normalized.confidence);
|
|
4347
|
+
match.sourceTurn = normalized.sourceTurn;
|
|
4348
|
+
match.ttl = normalized.ttl;
|
|
4349
|
+
match.tags = normalized.tags;
|
|
4350
|
+
match.lastUpdatedAt = normalized.lastUpdatedAt;
|
|
4351
|
+
} else {
|
|
4352
|
+
existing.push(normalized);
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
this.buckets.set(scope, existing);
|
|
4356
|
+
}
|
|
4357
|
+
async markObsolete(scope, keys) {
|
|
4358
|
+
const bucket = this.buckets.get(scope) || [];
|
|
4359
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4360
|
+
for (const fact of bucket) {
|
|
4361
|
+
if (keys.includes(fact.key)) {
|
|
4362
|
+
fact.obsolete = true;
|
|
4363
|
+
fact.lastUpdatedAt = now;
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4366
|
+
this.buckets.set(scope, bucket);
|
|
4367
|
+
}
|
|
4368
|
+
async semanticSearch(scope, query, options) {
|
|
4369
|
+
const normalizedQuery = query.toLowerCase();
|
|
4370
|
+
const bucket = (this.buckets.get(scope) || []).filter((fact) => !fact.obsolete);
|
|
4371
|
+
const scored = bucket.map((fact) => {
|
|
4372
|
+
const haystack = `${fact.key} ${fact.value} ${(fact.tags || []).join(" ")}`.toLowerCase();
|
|
4373
|
+
const score = haystack.includes(normalizedQuery) ? 2 : normalizedQuery.split(/\s+/).filter(Boolean).reduce((acc, token) => acc + (haystack.includes(token) ? 1 : 0), 0);
|
|
4374
|
+
return { fact, score };
|
|
4375
|
+
}).filter((entry) => entry.score > 0).sort((left, right) => right.score - left.score || right.fact.confidence - left.fact.confidence).map((entry) => entry.fact);
|
|
4376
|
+
return typeof options?.limit === "number" ? scored.slice(0, options.limit) : scored;
|
|
4377
|
+
}
|
|
4378
|
+
};
|
|
4379
|
+
function resolveMemoryStore(config) {
|
|
4380
|
+
if (config.memory.store) return config.memory.store;
|
|
4381
|
+
if (config.memory.provider === "inMemory") return new InMemoryMemoryStore();
|
|
4382
|
+
return void 0;
|
|
4383
|
+
}
|
|
4384
|
+
async function readMemoryFacts(store, config, query) {
|
|
4385
|
+
if (!store) return [];
|
|
4386
|
+
const scope = config.memory.scope;
|
|
4387
|
+
const limit = config.context.lastTurnsToKeep;
|
|
4388
|
+
const policy = config.memory.readPolicy;
|
|
4389
|
+
if (policy === "recent_only") {
|
|
4390
|
+
return store.get(scope, { limit });
|
|
4391
|
+
}
|
|
4392
|
+
if (policy === "semantic" && typeof store.semanticSearch === "function") {
|
|
4393
|
+
return store.semanticSearch(scope, query, { limit });
|
|
4394
|
+
}
|
|
4395
|
+
if (policy === "hybrid") {
|
|
4396
|
+
const recent = await store.get(scope, { limit: Math.max(3, Math.ceil(limit / 2)) });
|
|
4397
|
+
const semantic = typeof store.semanticSearch === "function" ? await store.semanticSearch(scope, query, { limit: Math.max(3, Math.ceil(limit / 2)) }) : [];
|
|
4398
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
4399
|
+
for (const fact of [...semantic, ...recent]) {
|
|
4400
|
+
deduped.set(`${fact.key}:${fact.value}`, fact);
|
|
4401
|
+
}
|
|
4402
|
+
return [...deduped.values()].slice(0, limit);
|
|
4403
|
+
}
|
|
4404
|
+
return store.get(scope, { limit });
|
|
4405
|
+
}
|
|
4406
|
+
async function writeSummaryFactsToMemory(store, config, summary, sourceTurn) {
|
|
4407
|
+
if (!store || !summary) return [];
|
|
4408
|
+
const facts = summary.stable_facts.map((fact) => ({
|
|
4409
|
+
key: fact.key,
|
|
4410
|
+
value: fact.value,
|
|
4411
|
+
sourceTurn,
|
|
4412
|
+
confidence: fact.confidence ?? 0.8,
|
|
4413
|
+
obsolete: false,
|
|
4414
|
+
tags: ["summary_fact"]
|
|
4415
|
+
}));
|
|
4416
|
+
if (summary.discarded_obsolete.length > 0) {
|
|
4417
|
+
await store.markObsolete(config.memory.scope, summary.discarded_obsolete);
|
|
4418
|
+
}
|
|
4419
|
+
if (facts.length > 0) {
|
|
4420
|
+
await store.upsert(config.memory.scope, facts);
|
|
4421
|
+
}
|
|
4422
|
+
return store.get(config.memory.scope, { limit: config.context.lastTurnsToKeep + summary.stable_facts.length });
|
|
4423
|
+
}
|
|
4424
|
+
function renderMemoryFacts(facts) {
|
|
4425
|
+
if (facts.length === 0) return "";
|
|
4426
|
+
return [
|
|
4427
|
+
"Memory facts:",
|
|
4428
|
+
...facts.map((fact) => `- ${fact.key}: ${fact.value} (confidence ${fact.confidence.toFixed(2)})`)
|
|
4429
|
+
].join("\n");
|
|
4430
|
+
}
|
|
4431
|
+
|
|
3037
4432
|
// src/smart/index.ts
|
|
3038
4433
|
function createSmartAgent(opts) {
|
|
3039
|
-
const
|
|
3040
|
-
const planningEnabled =
|
|
4434
|
+
const resolved = normalizeSmartAgentOptions(opts);
|
|
4435
|
+
const planningEnabled = resolved.planning.mode !== "off";
|
|
4436
|
+
const memoryStore = resolveMemoryStore(resolved);
|
|
4437
|
+
const runtimeOpts = {
|
|
4438
|
+
...opts,
|
|
4439
|
+
runtimeProfile: resolved.runtimeProfile,
|
|
4440
|
+
limits: resolved.limits,
|
|
4441
|
+
summarization: resolved.summarization,
|
|
4442
|
+
context: resolved.context,
|
|
4443
|
+
planning: resolved.planning,
|
|
4444
|
+
delegation: resolved.delegation,
|
|
4445
|
+
memory: { ...resolved.memory, store: memoryStore },
|
|
4446
|
+
toolResponses: resolved.toolResponses,
|
|
4447
|
+
watchdog: resolved.watchdog,
|
|
4448
|
+
useTodoList: planningEnabled
|
|
4449
|
+
};
|
|
4450
|
+
const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
|
|
3041
4451
|
const contextTools = createContextTools(stateRef, { planningEnabled, outputSchema: void 0 });
|
|
3042
4452
|
const mergedTools = [...opts.tools ?? [], ...contextTools];
|
|
3043
|
-
const base = createAgent({ ...
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
const summarizer = summarizationEnabled ? createContextSummarizeNode(opts) : void 0;
|
|
3051
|
-
const decideBefore = resolverDecisionFactory(opts, summarizationEnabled);
|
|
3052
|
-
const decideAfter = toolsDecisionFactory(opts, summarizationEnabled);
|
|
4453
|
+
const base = createAgent({ ...runtimeOpts, tools: mergedTools });
|
|
4454
|
+
base.__runtime.runtimeProfile = resolved.runtimeProfile;
|
|
4455
|
+
base.__runtime.smart = resolved;
|
|
4456
|
+
const summarizationEnabled = resolved.summarization.enable !== false;
|
|
4457
|
+
const summarizer = summarizationEnabled ? createContextSummarizeNode(runtimeOpts) : void 0;
|
|
4458
|
+
const decideBefore = resolverDecisionFactory(runtimeOpts, summarizationEnabled);
|
|
4459
|
+
const decideAfter = toolsDecisionFactory(runtimeOpts, summarizationEnabled);
|
|
3053
4460
|
const structuredOutputHint = opts.outputSchema ? [
|
|
3054
4461
|
"A structured output schema is active.",
|
|
3055
4462
|
"Do NOT output the final JSON directly as an assistant message.",
|
|
3056
4463
|
"When completely finished, call tool `response` passing the final JSON matching the schema as its arguments (direct object).",
|
|
3057
4464
|
"Call it exactly once then STOP producing further assistant messages."
|
|
3058
4465
|
].join("\n") : "";
|
|
4466
|
+
const runtimeHint = [
|
|
4467
|
+
resolved.runtimeProfile === resolved.baseProfile ? `Runtime profile: ${resolved.runtimeProfile}.` : `Runtime profile: ${resolved.runtimeProfile} (base: ${resolved.baseProfile}).`,
|
|
4468
|
+
`Context policy: ${resolved.context.policy}.`,
|
|
4469
|
+
`Planning mode: ${resolved.planning.mode}. Replan policy: ${resolved.planning.replanPolicy}.`,
|
|
4470
|
+
`Delegation mode: ${resolved.delegation.mode}. Child context policy: ${resolved.delegation.childContextPolicy}.`,
|
|
4471
|
+
`Tool response policy: ${resolved.context.toolResponsePolicy}.`
|
|
4472
|
+
].join("\n");
|
|
4473
|
+
function latestUserPrompt(messages) {
|
|
4474
|
+
const latest = [...messages].reverse().find((message) => message.role === "user");
|
|
4475
|
+
if (!latest) return "";
|
|
4476
|
+
return typeof latest.content === "string" ? latest.content : Array.isArray(latest.content) ? latest.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? "").join(" ") : "";
|
|
4477
|
+
}
|
|
4478
|
+
async function syncMemory(state) {
|
|
4479
|
+
const query = latestUserPrompt(state.messages || []);
|
|
4480
|
+
const memoryFacts = await readMemoryFacts(memoryStore, resolved, query);
|
|
4481
|
+
return { ...state, memoryFacts };
|
|
4482
|
+
}
|
|
4483
|
+
async function persistLatestSummary(state) {
|
|
4484
|
+
if (!memoryStore) return state;
|
|
4485
|
+
if (resolved.memory.writePolicy === "manual") return state;
|
|
4486
|
+
const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
|
|
4487
|
+
if (!latestSummary) return state;
|
|
4488
|
+
if (resolved.memory.writePolicy === "auto_important" && latestSummary.stable_facts.length === 0) {
|
|
4489
|
+
return state;
|
|
4490
|
+
}
|
|
4491
|
+
const memoryFacts = await writeSummaryFactsToMemory(memoryStore, resolved, latestSummary, Math.max(1, (state.messages || []).filter((message) => message.role === "user").length));
|
|
4492
|
+
return { ...state, memoryFacts };
|
|
4493
|
+
}
|
|
4494
|
+
function syncPlanState(state) {
|
|
4495
|
+
if (!Array.isArray(stateRef.todoList)) return state;
|
|
4496
|
+
return {
|
|
4497
|
+
...state,
|
|
4498
|
+
plan: {
|
|
4499
|
+
version: stateRef.planVersion || state.planVersion || 1,
|
|
4500
|
+
steps: stateRef.todoList,
|
|
4501
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4502
|
+
adherenceScore: stateRef.adherenceScore || 0
|
|
4503
|
+
},
|
|
4504
|
+
planVersion: stateRef.planVersion || state.planVersion || 1
|
|
4505
|
+
};
|
|
4506
|
+
}
|
|
3059
4507
|
function systemMessage2() {
|
|
3060
4508
|
const sys = buildSystemPrompt(
|
|
3061
|
-
[opts.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
|
|
3062
|
-
|
|
3063
|
-
opts.name || "Agent"
|
|
4509
|
+
[opts.systemPrompt, runtimeHint, structuredOutputHint].filter(Boolean).join("\n"),
|
|
4510
|
+
planningEnabled,
|
|
4511
|
+
opts.name || "Agent",
|
|
4512
|
+
opts.todoListPrompt
|
|
3064
4513
|
);
|
|
3065
4514
|
return { role: "system", content: sys };
|
|
3066
4515
|
}
|
|
@@ -3068,38 +4517,81 @@ function createSmartAgent(opts) {
|
|
|
3068
4517
|
invoke: async (input, config) => {
|
|
3069
4518
|
stateRef.toolHistory = input.toolHistory;
|
|
3070
4519
|
stateRef.toolHistoryArchived = input.toolHistoryArchived;
|
|
4520
|
+
stateRef.todoList = input.plan?.steps;
|
|
4521
|
+
stateRef.planVersion = input.planVersion || input.plan?.version || 0;
|
|
4522
|
+
stateRef.adherenceScore = input.plan?.adherenceScore || 0;
|
|
3071
4523
|
const alreadyHasSystem = Array.isArray(input.messages) && input.messages[0]?.role === "system";
|
|
3072
4524
|
const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
|
|
3073
|
-
let state = { ...input, messages: seedMessages };
|
|
4525
|
+
let state = await syncMemory({ ...input, messages: seedMessages });
|
|
3074
4526
|
let lastResult = null;
|
|
3075
|
-
|
|
4527
|
+
let rawMessages = [...seedMessages];
|
|
4528
|
+
const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10;
|
|
3076
4529
|
const iterationLimit = Math.max(effectiveMaxToolCalls * 3 + 5, 30);
|
|
4530
|
+
let previousTokenCount = countApproxTokens(rawMessages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n"));
|
|
3077
4531
|
for (let i = 0; i < iterationLimit; i++) {
|
|
4532
|
+
state = { ...state, messages: rawMessages };
|
|
3078
4533
|
const next = summarizationEnabled ? decideBefore(state) : "agent";
|
|
3079
4534
|
if (next === "contextSummarize" && summarizer) {
|
|
3080
4535
|
const delta = await summarizer(state);
|
|
3081
4536
|
if (!delta || Object.keys(delta).length === 0) ; else {
|
|
3082
|
-
state = { ...state, ...delta };
|
|
4537
|
+
state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
|
|
4538
|
+
rawMessages = [...state.messages || rawMessages];
|
|
3083
4539
|
continue;
|
|
3084
4540
|
}
|
|
3085
4541
|
}
|
|
3086
|
-
const
|
|
4542
|
+
const modelMessages = buildModelMessages(state, resolved);
|
|
4543
|
+
const res = await base.invoke({ ...state, messages: modelMessages }, config);
|
|
3087
4544
|
lastResult = res;
|
|
3088
4545
|
const currentSummaries = state.summaries;
|
|
4546
|
+
const currentSummaryRecords = state.summaryRecords;
|
|
4547
|
+
const currentPlan = state.plan;
|
|
4548
|
+
const appendedMessages = res.messages.slice(modelMessages.length);
|
|
4549
|
+
rawMessages = [...rawMessages, ...appendedMessages];
|
|
3089
4550
|
state = res.state || { ...state, messages: res.messages };
|
|
4551
|
+
state = { ...state, messages: rawMessages };
|
|
3090
4552
|
if (currentSummaries && currentSummaries.length > 0 && (!state.summaries || state.summaries.length === 0)) {
|
|
3091
4553
|
state = { ...state, summaries: currentSummaries };
|
|
3092
4554
|
}
|
|
3093
|
-
if (state.
|
|
4555
|
+
if (currentSummaryRecords && currentSummaryRecords.length > 0 && (!state.summaryRecords || state.summaryRecords.length === 0)) {
|
|
4556
|
+
state = { ...state, summaryRecords: currentSummaryRecords };
|
|
4557
|
+
}
|
|
4558
|
+
if (currentPlan && !state.plan) {
|
|
4559
|
+
state = { ...state, plan: currentPlan };
|
|
4560
|
+
}
|
|
4561
|
+
state = syncPlanState(await syncMemory(state));
|
|
4562
|
+
stateRef.toolHistory = state.toolHistory;
|
|
4563
|
+
stateRef.toolHistoryArchived = state.toolHistoryArchived;
|
|
4564
|
+
const currentTokenCount = countApproxTokens(rawMessages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n"));
|
|
4565
|
+
const tokenDrift = Math.max(0, currentTokenCount - previousTokenCount);
|
|
4566
|
+
const toolMessagesThisTurn = appendedMessages.filter((message) => message.role === "tool").length;
|
|
4567
|
+
const overToolingRate = appendedMessages.length === 0 ? 0 : Number((toolMessagesThisTurn / appendedMessages.length).toFixed(2));
|
|
4568
|
+
state = {
|
|
4569
|
+
...state,
|
|
4570
|
+
watchdog: {
|
|
4571
|
+
tokenDrift,
|
|
4572
|
+
contextRotScore: estimateContextRotScore(state),
|
|
4573
|
+
overToolingRate,
|
|
4574
|
+
compactions: state.watchdog?.compactions || 0,
|
|
4575
|
+
lastAction: appendedMessages.length > 0 ? "agent_turn" : state.watchdog?.lastAction
|
|
4576
|
+
}
|
|
4577
|
+
};
|
|
4578
|
+
previousTokenCount = currentTokenCount;
|
|
4579
|
+
if (resolved.watchdog.enabled && resolved.watchdog.autoCompaction) {
|
|
4580
|
+
const shouldCompact = tokenDrift >= resolved.watchdog.tokenDriftThreshold || (state.watchdog?.overToolingRate || 0) >= resolved.watchdog.overToolingSpikeThreshold || (state.watchdog?.contextRotScore || 0) >= resolved.watchdog.contextRotThreshold;
|
|
4581
|
+
if (shouldCompact) {
|
|
4582
|
+
state = { ...state, ctx: { ...state.ctx || {}, __needsSummarization: true } };
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
3094
4585
|
if (state.ctx?.__needsSummarization && summarizer) {
|
|
3095
4586
|
const ctx = { ...state.ctx || {} };
|
|
3096
4587
|
delete ctx.__needsSummarization;
|
|
3097
4588
|
state = { ...state, ctx };
|
|
3098
4589
|
const delta = await summarizer(state);
|
|
3099
4590
|
if (delta && Object.keys(delta).length > 0) {
|
|
3100
|
-
state = { ...state, ...delta };
|
|
4591
|
+
state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
|
|
4592
|
+
rawMessages = [...state.messages || rawMessages];
|
|
4593
|
+
continue;
|
|
3101
4594
|
}
|
|
3102
|
-
continue;
|
|
3103
4595
|
}
|
|
3104
4596
|
if (state.ctx?.__finalizedDueToStructuredOutput) break;
|
|
3105
4597
|
if (summarizationEnabled) {
|
|
@@ -3107,7 +4599,8 @@ function createSmartAgent(opts) {
|
|
|
3107
4599
|
if (after === "contextSummarize" && summarizer) {
|
|
3108
4600
|
const delta = await summarizer(state);
|
|
3109
4601
|
if (delta && Object.keys(delta).length > 0) {
|
|
3110
|
-
state = { ...state, ...delta };
|
|
4602
|
+
state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
|
|
4603
|
+
rawMessages = [...state.messages || rawMessages];
|
|
3111
4604
|
continue;
|
|
3112
4605
|
}
|
|
3113
4606
|
}
|
|
@@ -3120,10 +4613,12 @@ function createSmartAgent(opts) {
|
|
|
3120
4613
|
}
|
|
3121
4614
|
if (state.summaries && state.summaries.length > 0) {
|
|
3122
4615
|
if (lastResult.state) {
|
|
3123
|
-
lastResult = { ...lastResult, state: { ...lastResult.state, summaries: state.summaries } };
|
|
4616
|
+
lastResult = { ...lastResult, state: { ...lastResult.state, summaries: state.summaries, summaryRecords: state.summaryRecords, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, watchdog: state.watchdog, messages: rawMessages } };
|
|
3124
4617
|
} else {
|
|
3125
|
-
lastResult = { ...lastResult, state: { ...state, summaries: state.summaries } };
|
|
4618
|
+
lastResult = { ...lastResult, state: { ...state, summaries: state.summaries, summaryRecords: state.summaryRecords, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, watchdog: state.watchdog, messages: rawMessages } };
|
|
3126
4619
|
}
|
|
4620
|
+
} else if (lastResult.state) {
|
|
4621
|
+
lastResult = { ...lastResult, state: { ...lastResult.state, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, watchdog: state.watchdog, messages: rawMessages } };
|
|
3127
4622
|
}
|
|
3128
4623
|
return lastResult;
|
|
3129
4624
|
},
|
|
@@ -3137,6 +4632,141 @@ function createSmartAgent(opts) {
|
|
|
3137
4632
|
return instance;
|
|
3138
4633
|
}
|
|
3139
4634
|
|
|
4635
|
+
// src/smart/eval.ts
|
|
4636
|
+
function normalizeProfileTarget(target) {
|
|
4637
|
+
if (typeof target === "string") {
|
|
4638
|
+
return {
|
|
4639
|
+
label: target,
|
|
4640
|
+
runtimeProfile: target,
|
|
4641
|
+
baseProfile: target === "custom" ? "balanced" : target
|
|
4642
|
+
};
|
|
4643
|
+
}
|
|
4644
|
+
return {
|
|
4645
|
+
label: target.label,
|
|
4646
|
+
runtimeProfile: target.runtimeProfile,
|
|
4647
|
+
baseProfile: target.baseProfile ?? (target.runtimeProfile === "custom" ? target.customProfile?.extends ?? "balanced" : target.runtimeProfile),
|
|
4648
|
+
customProfile: target.customProfile
|
|
4649
|
+
};
|
|
4650
|
+
}
|
|
4651
|
+
function normalizeText(value) {
|
|
4652
|
+
return (value || "").toLowerCase();
|
|
4653
|
+
}
|
|
4654
|
+
function ratio(numerator, denominator) {
|
|
4655
|
+
if (denominator <= 0) return 1;
|
|
4656
|
+
return Number((numerator / denominator).toFixed(3));
|
|
4657
|
+
}
|
|
4658
|
+
function sum(values) {
|
|
4659
|
+
return values.reduce((acc, value) => acc + value, 0);
|
|
4660
|
+
}
|
|
4661
|
+
function mean(values) {
|
|
4662
|
+
if (values.length === 0) return 0;
|
|
4663
|
+
return Number((sum(values) / values.length).toFixed(3));
|
|
4664
|
+
}
|
|
4665
|
+
function extractTotalTokens(usage) {
|
|
4666
|
+
if (!usage?.totals || typeof usage.totals !== "object") return 0;
|
|
4667
|
+
return Object.values(usage.totals).reduce((acc, entry) => acc + (Number(entry?.total) || 0), 0);
|
|
4668
|
+
}
|
|
4669
|
+
function caseResultFromRun(descriptor, testCase, content, events, state, latencyMs, totalTokens) {
|
|
4670
|
+
const normalizedContent = normalizeText(content);
|
|
4671
|
+
const matchedPhrases = (testCase.expectedPhrases || []).filter((phrase) => normalizedContent.includes(normalizeText(phrase)));
|
|
4672
|
+
const matchedFacts = (testCase.expectedFacts || []).filter((fact) => {
|
|
4673
|
+
const memoryMatch = state?.memoryFacts?.some((entry) => entry.key === fact.key && normalizeText(entry.value).includes(normalizeText(fact.value)));
|
|
4674
|
+
const summaryMatch = state?.summaryRecords?.some((record) => record.stable_facts.some((entry) => entry.key === fact.key && normalizeText(entry.value).includes(normalizeText(fact.value))));
|
|
4675
|
+
return Boolean(memoryMatch || summaryMatch || normalizedContent.includes(normalizeText(fact.value)));
|
|
4676
|
+
});
|
|
4677
|
+
const forbiddenMisses = (testCase.forbiddenPhrases || []).filter((phrase) => !normalizedContent.includes(normalizeText(phrase)));
|
|
4678
|
+
const toolCalls = events.filter((event) => event.type === "tool_call" && event.phase === "success");
|
|
4679
|
+
const expectedToolHits = (testCase.expectedToolNames || []).filter((toolName) => toolCalls.some((event) => event.name === toolName));
|
|
4680
|
+
const toolPrecision = ratio(expectedToolHits.length, Math.max(toolCalls.length, 1));
|
|
4681
|
+
const recallAccuracy = ratio(matchedPhrases.length + matchedFacts.length, (testCase.expectedPhrases?.length || 0) + (testCase.expectedFacts?.length || 0));
|
|
4682
|
+
const obsoleteDropAccuracy = ratio(forbiddenMisses.length, testCase.forbiddenPhrases?.length || 0);
|
|
4683
|
+
const trajectoryScore = ratio(expectedToolHits.length, testCase.expectedToolNames?.length || 0);
|
|
4684
|
+
const hadRecoverableError = events.some((event) => event.type === "tool_call" && event.phase === "error");
|
|
4685
|
+
const recoveryRate = hadRecoverableError ? Number((normalizedContent.length > 0 ? 1 : 0).toFixed(3)) : 1;
|
|
4686
|
+
const overToolingRate = toolCalls.length === 0 ? 0 : Number((Math.max(0, toolCalls.length - Math.max(testCase.expectedToolNames?.length || 0, 1)) / toolCalls.length).toFixed(3));
|
|
4687
|
+
const success = recallAccuracy >= 0.8 && obsoleteDropAccuracy >= 0.8;
|
|
4688
|
+
return {
|
|
4689
|
+
id: testCase.id,
|
|
4690
|
+
family: testCase.family,
|
|
4691
|
+
success,
|
|
4692
|
+
recallAccuracy,
|
|
4693
|
+
obsoleteDropAccuracy,
|
|
4694
|
+
trajectoryScore: Number(((trajectoryScore + toolPrecision) / 2).toFixed(3)),
|
|
4695
|
+
recoveryRate,
|
|
4696
|
+
overToolingRate,
|
|
4697
|
+
latencyMs,
|
|
4698
|
+
totalTokens,
|
|
4699
|
+
profile: descriptor.runtimeProfile,
|
|
4700
|
+
profileLabel: descriptor.label,
|
|
4701
|
+
baseProfile: descriptor.baseProfile,
|
|
4702
|
+
notes: success ? [] : [
|
|
4703
|
+
recallAccuracy < 0.8 ? "Recall below threshold." : "",
|
|
4704
|
+
obsoleteDropAccuracy < 0.8 ? "Obsolete filtering below threshold." : "",
|
|
4705
|
+
trajectoryScore < 0.5 ? "Tool trajectory diverged from expectation." : ""
|
|
4706
|
+
].filter(Boolean)
|
|
4707
|
+
};
|
|
4708
|
+
}
|
|
4709
|
+
function aggregateMetrics(caseResults) {
|
|
4710
|
+
const taskSuccess = ratio(caseResults.filter((result) => result.success).length, caseResults.length);
|
|
4711
|
+
const recallAccuracy = mean(caseResults.map((result) => result.recallAccuracy));
|
|
4712
|
+
const obsoleteDropAccuracy = mean(caseResults.map((result) => result.obsoleteDropAccuracy));
|
|
4713
|
+
const trajectoryScore = mean(caseResults.map((result) => result.trajectoryScore));
|
|
4714
|
+
const recoveryRate = mean(caseResults.map((result) => result.recoveryRate));
|
|
4715
|
+
const overToolingRate = mean(caseResults.map((result) => result.overToolingRate));
|
|
4716
|
+
const latencyMs = mean(caseResults.map((result) => result.latencyMs));
|
|
4717
|
+
const totalTokens = sum(caseResults.map((result) => result.totalTokens || 0));
|
|
4718
|
+
const toolPrecision = trajectoryScore;
|
|
4719
|
+
const costScore = totalTokens <= 0 ? 1 : Number((1 / (1 + totalTokens / 1e4)).toFixed(3));
|
|
4720
|
+
const score = Number((0.45 * taskSuccess + 0.2 * trajectoryScore + 0.15 * toolPrecision + 0.1 * recoveryRate + 0.1 * costScore).toFixed(3));
|
|
4721
|
+
return {
|
|
4722
|
+
taskSuccess,
|
|
4723
|
+
recallAccuracy,
|
|
4724
|
+
obsoleteDropAccuracy,
|
|
4725
|
+
trajectoryScore,
|
|
4726
|
+
recoveryRate,
|
|
4727
|
+
overToolingRate,
|
|
4728
|
+
latencyMs,
|
|
4729
|
+
totalTokens,
|
|
4730
|
+
score
|
|
4731
|
+
};
|
|
4732
|
+
}
|
|
4733
|
+
async function runSmartAgentEvalHarness(params) {
|
|
4734
|
+
const results = [];
|
|
4735
|
+
for (const profileTarget of params.profiles) {
|
|
4736
|
+
const descriptor = normalizeProfileTarget(profileTarget);
|
|
4737
|
+
const caseResults = [];
|
|
4738
|
+
for (const testCase of params.cases) {
|
|
4739
|
+
const agent = params.createAgent(descriptor.runtimeProfile, descriptor);
|
|
4740
|
+
const events = [];
|
|
4741
|
+
const startedAt = Date.now();
|
|
4742
|
+
const result = await agent.invoke({
|
|
4743
|
+
messages: [{ role: "user", content: testCase.prompt }]
|
|
4744
|
+
}, {
|
|
4745
|
+
onEvent: (event) => {
|
|
4746
|
+
events.push(event);
|
|
4747
|
+
}
|
|
4748
|
+
});
|
|
4749
|
+
caseResults.push(caseResultFromRun(
|
|
4750
|
+
descriptor,
|
|
4751
|
+
testCase,
|
|
4752
|
+
result.content,
|
|
4753
|
+
events,
|
|
4754
|
+
result.state,
|
|
4755
|
+
Date.now() - startedAt,
|
|
4756
|
+
extractTotalTokens(result.metadata?.usage)
|
|
4757
|
+
));
|
|
4758
|
+
}
|
|
4759
|
+
results.push({
|
|
4760
|
+
profile: descriptor.runtimeProfile,
|
|
4761
|
+
profileLabel: descriptor.label,
|
|
4762
|
+
baseProfile: descriptor.baseProfile,
|
|
4763
|
+
metrics: aggregateMetrics(caseResults),
|
|
4764
|
+
cases: caseResults
|
|
4765
|
+
});
|
|
4766
|
+
}
|
|
4767
|
+
return results;
|
|
4768
|
+
}
|
|
4769
|
+
|
|
3140
4770
|
// src/utils/content.ts
|
|
3141
4771
|
function contentToString(content) {
|
|
3142
4772
|
try {
|
|
@@ -3414,6 +5044,6 @@ function createCodeGuardrail(options) {
|
|
|
3414
5044
|
});
|
|
3415
5045
|
}
|
|
3416
5046
|
|
|
3417
|
-
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 };
|
|
5047
|
+
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 };
|
|
3418
5048
|
//# sourceMappingURL=index.mjs.map
|
|
3419
5049
|
//# sourceMappingURL=index.mjs.map
|