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