@myagentroam/agent 0.9.64 → 0.9.66
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/dist/cli/main.js +13 -9
- package/dist/model/openai-responses.js +26 -5
- package/dist/prompts/output.d.ts +1 -0
- package/dist/prompts/output.js +1 -0
- package/dist/runtime/context-gc-runtime.js +47 -10
- package/dist/runtime/context-gc.d.ts +8 -1
- package/dist/runtime/context-gc.js +170 -16
- package/dist/runtime/context-projection.js +3 -1
- package/dist/sdk/agent.js +111 -39
- package/dist/sdk/types.d.ts +1 -0
- package/dist/session/context-gc-checkpoint.d.ts +9 -1
- package/dist/session/context-gc-checkpoint.js +130 -12
- package/dist/session/context-gc-replacements.d.ts +6 -3
- package/dist/session/context-gc-replacements.js +75 -15
- package/dist/session/context-gc-subagent.d.ts +12 -0
- package/dist/session/context-gc-subagent.js +79 -7
- package/dist/session/jsonl-store.d.ts +15 -0
- package/dist/session/jsonl-store.js +15 -0
- package/dist/subagent/scheduler.d.ts +14 -1
- package/dist/subagent/scheduler.js +46 -3
- package/dist/subagent/session-controller.d.ts +4 -1
- package/dist/subagent/session-controller.js +118 -22
- package/dist/tools/agent-wait.js +12 -8
- package/dist/tools/execution-limits.d.ts +1 -0
- package/dist/tools/execution-limits.js +1 -0
- package/dist/tools/view-image.js +1 -1
- package/package.json +1 -1
package/dist/sdk/agent.js
CHANGED
|
@@ -17,6 +17,7 @@ import { TOOL_EXECUTION_LIMITS } from '../tools/execution-limits.js';
|
|
|
17
17
|
import { DEFAULT_COMPACTION_FOCUS, CompactionOperation, buildCompactionInput, buildCompactionMessages, buildCompactedHistory, compactedHistoryMessages, continuationMessage } from '../runtime/compact.js';
|
|
18
18
|
import { buildSystemPrompt, MAR_AGENT_PROMPT_VERSION } from '../prompts/index.js';
|
|
19
19
|
import { buildEnvironmentContext } from '../prompts/core.js';
|
|
20
|
+
import { modelOutputContinuationMessage } from '../prompts/output.js';
|
|
20
21
|
import { environmentContextUpdate, restoreEnvironmentState } from '../runtime/environment-context.js';
|
|
21
22
|
import { childSubagentInstruction } from '../prompts/subagent.js';
|
|
22
23
|
import { sessionTitlePrompt } from '../prompts/title.js';
|
|
@@ -46,6 +47,7 @@ export async function createMarAgent(options) {
|
|
|
46
47
|
const generatedImageDisplayDirectory = normalizeGeneratedImageDisplayDirectory(options.generatedImageDisplayDirectory);
|
|
47
48
|
const resolvedModels = resolveAgentModels(options.models);
|
|
48
49
|
let { models, defaultModelId } = resolvedModels;
|
|
50
|
+
let modelGeneration = 0;
|
|
49
51
|
const { credentialProviders } = resolvedModels;
|
|
50
52
|
const description = negotiateHost(await options.host.describe());
|
|
51
53
|
if (description.mode !== options.mode)
|
|
@@ -65,6 +67,15 @@ export async function createMarAgent(options) {
|
|
|
65
67
|
? new OpenAiResponsesAdapter(configuration)
|
|
66
68
|
: new AnthropicMessagesAdapter(configuration)));
|
|
67
69
|
};
|
|
70
|
+
const activeSessionIds = () => new Set([...active.keys()].map((key) => key.slice(0, key.indexOf(':'))));
|
|
71
|
+
const activeAdaptersExcept = (sessionId) => {
|
|
72
|
+
const activeAdapters = new Set();
|
|
73
|
+
for (const [candidateSessionId, runtime] of sessionRuntimes)
|
|
74
|
+
if (candidateSessionId !== sessionId && activeSessionIds().has(candidateSessionId))
|
|
75
|
+
for (const adapter of runtime.adapters.values())
|
|
76
|
+
activeAdapters.add(adapter);
|
|
77
|
+
return activeAdapters;
|
|
78
|
+
};
|
|
68
79
|
const openSession = async (sessionId, workspace) => {
|
|
69
80
|
const storedSession = await store.getSession(sessionId);
|
|
70
81
|
const storedWorkspace = storedSession.workspace;
|
|
@@ -85,7 +96,12 @@ export async function createMarAgent(options) {
|
|
|
85
96
|
const environmentContext = buildEnvironmentContext(currentEnvironment);
|
|
86
97
|
let sessionRuntime = sessionRuntimes.get(sessionId);
|
|
87
98
|
if (sessionRuntime === undefined) {
|
|
88
|
-
sessionRuntime = {
|
|
99
|
+
sessionRuntime = {
|
|
100
|
+
sourceType: sessionSource.type,
|
|
101
|
+
adapterGeneration: modelGeneration,
|
|
102
|
+
responsesChain: undefined,
|
|
103
|
+
adapters: new Map()
|
|
104
|
+
};
|
|
89
105
|
sessionRuntimes.set(sessionId, sessionRuntime);
|
|
90
106
|
}
|
|
91
107
|
if (sessionSource.type === 'user' && sessionRuntime.subagents === undefined) {
|
|
@@ -110,6 +126,15 @@ export async function createMarAgent(options) {
|
|
|
110
126
|
}
|
|
111
127
|
return selected;
|
|
112
128
|
};
|
|
129
|
+
const refreshSessionAdapters = async (generation) => {
|
|
130
|
+
if (sessionRuntime.adapterGeneration === generation)
|
|
131
|
+
return;
|
|
132
|
+
const retainedByActiveSessions = activeAdaptersExcept(sessionId);
|
|
133
|
+
await closeModelAdapters([...sessionRuntime.adapters.values()].filter((adapter) => !retainedByActiveSessions.has(adapter)));
|
|
134
|
+
sessionRuntime.responsesChain = undefined;
|
|
135
|
+
sessionRuntime.adapters.clear();
|
|
136
|
+
sessionRuntime.adapterGeneration = generation;
|
|
137
|
+
};
|
|
113
138
|
const execution = (mode, input, internal = {}) => {
|
|
114
139
|
if (disposed)
|
|
115
140
|
throw new MarAgentError('MAR_AGENT_DISPOSED', 'Agent is disposed.');
|
|
@@ -120,6 +145,7 @@ export async function createMarAgent(options) {
|
|
|
120
145
|
const executionId = randomUUID(), turnId = internal.turnId ?? randomUUID(), queue = new AsyncQueue(), controller = new AbortController();
|
|
121
146
|
const executionModels = models;
|
|
122
147
|
const executionDefaultModelId = defaultModelId;
|
|
148
|
+
const executionModelGeneration = modelGeneration;
|
|
123
149
|
const manualCompactionId = mode === 'compact' ? randomUUID() : undefined;
|
|
124
150
|
active.set(`${sessionId}:${executionId}`, controller);
|
|
125
151
|
const mailbox = [...(internal.initialMailbox ?? [])];
|
|
@@ -219,6 +245,7 @@ export async function createMarAgent(options) {
|
|
|
219
245
|
const subagents = sessionSource.type === 'user' ? sessionRuntime.subagents : undefined;
|
|
220
246
|
try {
|
|
221
247
|
releaseSession = await store.acquireSessionLease(sessionId);
|
|
248
|
+
await refreshSessionAdapters(executionModelGeneration);
|
|
222
249
|
const sessionState = await store.readExecutionState(sessionId);
|
|
223
250
|
const todo = TodoStore.restore(sessionState.todo);
|
|
224
251
|
const contextRecords = sessionState.contextRecords;
|
|
@@ -350,6 +377,9 @@ export async function createMarAgent(options) {
|
|
|
350
377
|
...(task.latestMessage === undefined
|
|
351
378
|
? {}
|
|
352
379
|
: { latestMessage: task.latestMessage }),
|
|
380
|
+
...(task.latestActivity === undefined
|
|
381
|
+
? {}
|
|
382
|
+
: { latestActivity: task.latestActivity }),
|
|
353
383
|
...(task.errorCode === undefined ? {} : { errorCode: task.errorCode }),
|
|
354
384
|
...(task.errorMessage === undefined ? {} : { errorMessage: task.errorMessage })
|
|
355
385
|
}),
|
|
@@ -599,6 +629,8 @@ export async function createMarAgent(options) {
|
|
|
599
629
|
throw new MarAgentError('MAR_AGENT_CONTEXT_LIMIT', 'Provider usage reached the compaction threshold but no history can be compacted.');
|
|
600
630
|
};
|
|
601
631
|
let recoveringFromContextOverflow = false;
|
|
632
|
+
let maxOutputContinuationUsed = false;
|
|
633
|
+
let pendingOutputContinuation;
|
|
602
634
|
const modelRetryState = { attempts: 0 };
|
|
603
635
|
while (true) {
|
|
604
636
|
await appendMailboxMessages();
|
|
@@ -607,6 +639,7 @@ export async function createMarAgent(options) {
|
|
|
607
639
|
let responseId;
|
|
608
640
|
let latestUsage;
|
|
609
641
|
const pendingTools = [];
|
|
642
|
+
let incompleteToolCall = false;
|
|
610
643
|
await ensureContextBudget();
|
|
611
644
|
const toolsAllowed = mode !== 'compact' && (!rolloutBudget?.active || rolloutBudget.stage !== 'exhausted');
|
|
612
645
|
const roundTools = toolsAllowed ? executionTools : [];
|
|
@@ -620,17 +653,42 @@ export async function createMarAgent(options) {
|
|
|
620
653
|
reasoningEffort
|
|
621
654
|
}))
|
|
622
655
|
.digest('hex');
|
|
656
|
+
const requestContextMessages = pendingOutputContinuation === undefined
|
|
657
|
+
? messages
|
|
658
|
+
: [...messages, pendingOutputContinuation];
|
|
623
659
|
const continuation = selectedModel.responsesPreviousResponseId &&
|
|
624
660
|
sessionRuntime.responsesChain?.modelId === selectedModel.id &&
|
|
625
661
|
sessionRuntime.responsesChain.requestSignature === requestSignature &&
|
|
626
|
-
sessionRuntime.responsesChain.messageCount <=
|
|
662
|
+
sessionRuntime.responsesChain.messageCount <= requestContextMessages.length
|
|
627
663
|
? {
|
|
628
664
|
previousResponseId: sessionRuntime.responsesChain.responseId,
|
|
629
|
-
deltaMessages:
|
|
665
|
+
deltaMessages: requestContextMessages.slice(sessionRuntime.responsesChain.messageCount)
|
|
630
666
|
}
|
|
631
667
|
: undefined;
|
|
668
|
+
const modelMessages = normalizeMessagesForModel(insertContextualInstructions(requestContextMessages, agentInstructionInsertIndex, agentInstructionMessage), {
|
|
669
|
+
supportsImages: selectedModel.inputCapabilities.includes('IMAGE')
|
|
670
|
+
});
|
|
632
671
|
let sawModelSemanticEvent = false;
|
|
633
672
|
const roundMessageCount = messages.length;
|
|
673
|
+
const persistToolCalls = async (calls) => {
|
|
674
|
+
for (const call of calls) {
|
|
675
|
+
const message = {
|
|
676
|
+
role: 'tool_call',
|
|
677
|
+
callId: call.callId,
|
|
678
|
+
name: call.name,
|
|
679
|
+
arguments: call.arguments,
|
|
680
|
+
...(call.inputMode ? { inputMode: call.inputMode } : {}),
|
|
681
|
+
content: ''
|
|
682
|
+
};
|
|
683
|
+
messages.push(message);
|
|
684
|
+
await store.append(sessionId, {
|
|
685
|
+
type: 'model.context',
|
|
686
|
+
payload: message,
|
|
687
|
+
executionId,
|
|
688
|
+
turnId
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
};
|
|
634
692
|
try {
|
|
635
693
|
if (manualCompactionId !== undefined && !manualCompactionStarted) {
|
|
636
694
|
manualCompactionStarted = true;
|
|
@@ -643,9 +701,7 @@ export async function createMarAgent(options) {
|
|
|
643
701
|
for await (const event of startModel({
|
|
644
702
|
retryState: modelRetryState,
|
|
645
703
|
system: systemPrompt,
|
|
646
|
-
messages:
|
|
647
|
-
supportsImages: selectedModel.inputCapabilities.includes('IMAGE')
|
|
648
|
-
}),
|
|
704
|
+
messages: modelMessages,
|
|
649
705
|
tools: roundTools,
|
|
650
706
|
...(toolsAllowed ? {} : { allowTools: false }),
|
|
651
707
|
promptCacheKey: sessionId,
|
|
@@ -764,26 +820,18 @@ export async function createMarAgent(options) {
|
|
|
764
820
|
}
|
|
765
821
|
else if (event.type === 'client_tool.call') {
|
|
766
822
|
sawTool = true;
|
|
767
|
-
const message = {
|
|
768
|
-
role: 'tool_call',
|
|
769
|
-
callId: event.callId,
|
|
770
|
-
name: event.name,
|
|
771
|
-
arguments: event.arguments,
|
|
772
|
-
...(event.inputMode ? { inputMode: event.inputMode } : {}),
|
|
773
|
-
content: ''
|
|
774
|
-
};
|
|
775
|
-
messages.push(message);
|
|
776
|
-
await store.append(sessionId, {
|
|
777
|
-
type: 'model.context',
|
|
778
|
-
payload: message,
|
|
779
|
-
executionId,
|
|
780
|
-
turnId
|
|
781
|
-
});
|
|
782
823
|
pendingTools.push(event);
|
|
783
824
|
}
|
|
784
825
|
else if (event.type === 'completed')
|
|
785
826
|
stop = event.reason;
|
|
786
827
|
}
|
|
828
|
+
pendingOutputContinuation = undefined;
|
|
829
|
+
incompleteToolCall = pendingTools.some((call) => call.argumentError !== undefined);
|
|
830
|
+
if (!(stop === 'max_output' && incompleteToolCall)) {
|
|
831
|
+
await persistToolCalls(pendingTools);
|
|
832
|
+
}
|
|
833
|
+
else
|
|
834
|
+
pendingTools.splice(0, pendingTools.length);
|
|
787
835
|
modelRetryState.attempts = 0;
|
|
788
836
|
}
|
|
789
837
|
catch (error) {
|
|
@@ -794,6 +842,10 @@ export async function createMarAgent(options) {
|
|
|
794
842
|
finalAnswer = '';
|
|
795
843
|
if (mode === 'compact')
|
|
796
844
|
messages.splice(roundMessageCount);
|
|
845
|
+
if (pendingTools.some((call) => call.argumentError !== undefined))
|
|
846
|
+
pendingTools.splice(0, pendingTools.length);
|
|
847
|
+
else if (pendingTools.length > 0)
|
|
848
|
+
await persistToolCalls(pendingTools);
|
|
797
849
|
if (pendingTools.length === 0)
|
|
798
850
|
continue;
|
|
799
851
|
}
|
|
@@ -952,6 +1004,24 @@ export async function createMarAgent(options) {
|
|
|
952
1004
|
finalAnswer = '';
|
|
953
1005
|
}
|
|
954
1006
|
rolloutBudget?.recordUsage(latestUsage);
|
|
1007
|
+
if (mode !== 'compact' && stop === 'max_output') {
|
|
1008
|
+
const needsContinuation = !sawTool || incompleteToolCall;
|
|
1009
|
+
if (needsContinuation) {
|
|
1010
|
+
if (maxOutputContinuationUsed ||
|
|
1011
|
+
(rolloutBudget?.active && rolloutBudget.stage === 'exhausted'))
|
|
1012
|
+
throw new MarAgentError('MAR_AGENT_MODEL_OUTPUT_INCOMPLETE', 'Model output reached its limit before completing.');
|
|
1013
|
+
maxOutputContinuationUsed = true;
|
|
1014
|
+
pendingOutputContinuation = {
|
|
1015
|
+
role: 'user',
|
|
1016
|
+
content: modelOutputContinuationMessage
|
|
1017
|
+
};
|
|
1018
|
+
finalAnswer = '';
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
maxOutputContinuationUsed = false;
|
|
1022
|
+
}
|
|
1023
|
+
else
|
|
1024
|
+
maxOutputContinuationUsed = false;
|
|
955
1025
|
if (mode !== 'compact' && latestUsage !== undefined) {
|
|
956
1026
|
const committed = await contextGc.run({
|
|
957
1027
|
store,
|
|
@@ -1141,12 +1211,21 @@ export async function createMarAgent(options) {
|
|
|
1141
1211
|
reconfigureModels: async (input) => {
|
|
1142
1212
|
if (disposed)
|
|
1143
1213
|
throw new MarAgentError('MAR_AGENT_DISPOSED', 'Agent is disposed.');
|
|
1144
|
-
if (
|
|
1145
|
-
throw new MarAgentError('MAR_AGENT_EXECUTION_ACTIVE', 'Models
|
|
1214
|
+
if (reconfiguring)
|
|
1215
|
+
throw new MarAgentError('MAR_AGENT_EXECUTION_ACTIVE', 'Models are already being reconfigured for the next execution.');
|
|
1146
1216
|
const next = resolveAgentModels(input.models, input.defaultModelId);
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1149
|
-
|
|
1217
|
+
const activeSessions = activeSessionIds();
|
|
1218
|
+
if ([...activeSessions].some((sessionId) => sessionRuntimes.get(sessionId)?.sourceType !== 'subagent'))
|
|
1219
|
+
throw new MarAgentError('MAR_AGENT_EXECUTION_ACTIVE', 'Models cannot be reconfigured while a main execution is active.');
|
|
1220
|
+
const activeAdapters = new Set();
|
|
1221
|
+
for (const [sessionId, runtime] of sessionRuntimes)
|
|
1222
|
+
if (activeSessions.has(sessionId))
|
|
1223
|
+
for (const adapter of runtime.adapters.values())
|
|
1224
|
+
activeAdapters.add(adapter);
|
|
1225
|
+
const inactiveRuntimes = [...sessionRuntimes].flatMap(([sessionId, runtime]) => activeSessions.has(sessionId) ? [] : [runtime]);
|
|
1226
|
+
const adapters = inactiveRuntimes
|
|
1227
|
+
.flatMap((runtime) => [...runtime.adapters.values()])
|
|
1228
|
+
.filter((adapter) => !activeAdapters.has(adapter));
|
|
1150
1229
|
reconfiguring = true;
|
|
1151
1230
|
try {
|
|
1152
1231
|
await closeModelAdapters(adapters);
|
|
@@ -1154,11 +1233,13 @@ export async function createMarAgent(options) {
|
|
|
1154
1233
|
throw new MarAgentError('MAR_AGENT_DISPOSED', 'Agent is disposed.');
|
|
1155
1234
|
models = next.models;
|
|
1156
1235
|
defaultModelId = next.defaultModelId;
|
|
1236
|
+
modelGeneration++;
|
|
1157
1237
|
for (const provider of next.credentialProviders)
|
|
1158
1238
|
credentialProviders.add(provider);
|
|
1159
|
-
for (const runtime of
|
|
1239
|
+
for (const runtime of inactiveRuntimes) {
|
|
1160
1240
|
runtime.responsesChain = undefined;
|
|
1161
1241
|
runtime.adapters.clear();
|
|
1242
|
+
runtime.adapterGeneration = modelGeneration;
|
|
1162
1243
|
}
|
|
1163
1244
|
}
|
|
1164
1245
|
finally {
|
|
@@ -1228,20 +1309,10 @@ function createSubagentBackend(store, workspace) {
|
|
|
1228
1309
|
await store.delete(sessionId);
|
|
1229
1310
|
},
|
|
1230
1311
|
listChildren: async (parentSessionId) => {
|
|
1231
|
-
const sessions =
|
|
1232
|
-
let cursor;
|
|
1233
|
-
do {
|
|
1234
|
-
const page = await store.list({
|
|
1235
|
-
source: 'subagent',
|
|
1236
|
-
parentSessionId,
|
|
1237
|
-
limit: 200,
|
|
1238
|
-
...(cursor === undefined ? {} : { cursor })
|
|
1239
|
-
});
|
|
1240
|
-
sessions.push(...page.sessions);
|
|
1241
|
-
cursor = page.nextCursor;
|
|
1242
|
-
} while (cursor !== undefined);
|
|
1312
|
+
const sessions = await store.listSubagentChildren(parentSessionId);
|
|
1243
1313
|
return Promise.all(sessions.map(async (session) => ({
|
|
1244
1314
|
id: session.id,
|
|
1315
|
+
createdAt: session.createdAt,
|
|
1245
1316
|
source: session.source,
|
|
1246
1317
|
records: await store.read(session.id)
|
|
1247
1318
|
})));
|
|
@@ -1260,6 +1331,7 @@ function createSubagentBackend(store, workspace) {
|
|
|
1260
1331
|
return undefined;
|
|
1261
1332
|
return {
|
|
1262
1333
|
id: session.id,
|
|
1334
|
+
createdAt: session.createdAt,
|
|
1263
1335
|
source: session.source,
|
|
1264
1336
|
records: await store.read(session.id)
|
|
1265
1337
|
};
|
package/dist/sdk/types.d.ts
CHANGED
|
@@ -162,6 +162,7 @@ export type MarAgentEvent = MarAgentEventBase & ({
|
|
|
162
162
|
modelName?: string;
|
|
163
163
|
reasoningEffort?: MarAgentReasoningEffort;
|
|
164
164
|
latestMessage?: import('../subagent/scheduler.js').SubagentLatestMessage;
|
|
165
|
+
latestActivity?: import('../subagent/scheduler.js').SubagentLatestActivity;
|
|
165
166
|
errorCode?: string;
|
|
166
167
|
errorMessage?: string;
|
|
167
168
|
} | {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ContextGcWebToolName } from './context-gc-replacements.js';
|
|
1
2
|
import { type ContextGcSubagentToolName } from './context-gc-subagent.js';
|
|
2
3
|
import type { SessionRecord } from './jsonl-store.js';
|
|
3
4
|
export declare const CONTEXT_GC_CHECKPOINT_VERSION: 1;
|
|
@@ -63,7 +64,7 @@ export type ContextGcReplacement = {
|
|
|
63
64
|
readonly evidenceSequence: number;
|
|
64
65
|
} | {
|
|
65
66
|
readonly kind: 'SUBAGENT_OUTPUT';
|
|
66
|
-
readonly toolName: 'agent_wait' | 'agent_message';
|
|
67
|
+
readonly toolName: 'agent_wait' | 'agent_message' | 'agent_cancel';
|
|
67
68
|
readonly callId: string;
|
|
68
69
|
readonly target: ContextGcTarget;
|
|
69
70
|
readonly replacementContent: string;
|
|
@@ -75,6 +76,13 @@ export type ContextGcReplacement = {
|
|
|
75
76
|
readonly callId: string;
|
|
76
77
|
readonly evidenceSequence: number;
|
|
77
78
|
};
|
|
79
|
+
} | {
|
|
80
|
+
readonly kind: 'WEB_OUTPUT';
|
|
81
|
+
readonly toolName: ContextGcWebToolName;
|
|
82
|
+
readonly callId: string;
|
|
83
|
+
readonly target: ContextGcTarget;
|
|
84
|
+
readonly replacementContent: string;
|
|
85
|
+
readonly evidenceSequence: number;
|
|
78
86
|
};
|
|
79
87
|
export interface ContextGcMetrics {
|
|
80
88
|
readonly stage: 'STAGE_A' | 'STAGE_A_AND_EXEC';
|
|
@@ -2,8 +2,8 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { posix, win32 } from 'node:path';
|
|
3
3
|
import { isDeepStrictEqual } from 'node:util';
|
|
4
4
|
import { parseReadContextOutput } from '../tools/read-context.js';
|
|
5
|
-
import { contextGcApplyPatchInputReplacement, contextGcExecInputReplacement, contextGcExecOutputReplacement, contextGcPatchResultFromCompletedPayload, contextGcStaleReadReplacement, normalizeContextGcExecResult } from './context-gc-replacements.js';
|
|
6
|
-
import { contextGcSubagentNeedsRetainedPriorFinal, contextGcSubagentOutputReplacement, contextGcSubagentSnapshotIsSuperseded, normalizeContextGcSubagentSnapshot } from './context-gc-subagent.js';
|
|
5
|
+
import { contextGcApplyPatchInputReplacement, contextGcWebOutputReplacement, contextGcExecInputReplacement, contextGcExecOutputReplacement, contextGcPatchResultFromCompletedPayload, contextGcStaleReadReplacement, normalizeContextGcExecResult } from './context-gc-replacements.js';
|
|
6
|
+
import { contextGcSubagentNeedsRetainedPriorFinal, contextGcSubagentOutputReplacement, contextGcSubagentSnapshotIsPreciselyCovered, contextGcSubagentSnapshotIsSuperseded, normalizeContextGcSubagentSnapshot } from './context-gc-subagent.js';
|
|
7
7
|
export const CONTEXT_GC_CHECKPOINT_VERSION = 1;
|
|
8
8
|
export const CONTEXT_GC_MAX_TARGETS = 4_096;
|
|
9
9
|
/** Disposable memoization for one evaluation's immutable record snapshot. */
|
|
@@ -251,7 +251,8 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
|
|
|
251
251
|
content: original.content,
|
|
252
252
|
evidenceSequence: item.evidenceSequence,
|
|
253
253
|
supersededByCallId: evidence.callId,
|
|
254
|
-
operation: evidence.file.operation
|
|
254
|
+
operation: evidence.file.operation,
|
|
255
|
+
formatVersion: staleReadReplacementFormatVersion(item.replacement)
|
|
255
256
|
}));
|
|
256
257
|
});
|
|
257
258
|
}
|
|
@@ -261,7 +262,7 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
|
|
|
261
262
|
return (evidence?.callId === replacement.callId &&
|
|
262
263
|
evidence.call.sequence === replacement.target.sequence &&
|
|
263
264
|
result !== undefined &&
|
|
264
|
-
isDeepStrictEqual(replacement.replacementArguments, contextGcApplyPatchInputReplacement(source.payload.arguments, result, replacement.evidenceSequence)));
|
|
265
|
+
isDeepStrictEqual(replacement.replacementArguments, contextGcApplyPatchInputReplacement(source.payload.arguments, result, replacement.evidenceSequence, applyPatchReplacementFormatVersion(replacement.replacementArguments))));
|
|
265
266
|
}
|
|
266
267
|
if (replacement.kind === 'SUBAGENT_OUTPUT') {
|
|
267
268
|
const retainedPriorFinal = replacement.retainedPriorFinal;
|
|
@@ -293,24 +294,79 @@ function replacementMatchesRecords(replacement, records, recordsBySequence, work
|
|
|
293
294
|
(retainedPriorFinalEvidence === undefined ||
|
|
294
295
|
retainedPriorFinalEvidence.result.sequence < sourceEvidence.result.sequence) &&
|
|
295
296
|
sourceEvidence.result.sequence < terminalEvidence.result.sequence &&
|
|
296
|
-
contextGcSubagentSnapshotIsSuperseded(sourceSnapshot, terminalSnapshot, retainedPriorFinalSnapshot)
|
|
297
|
+
(contextGcSubagentSnapshotIsSuperseded(sourceSnapshot, terminalSnapshot, retainedPriorFinalSnapshot) ||
|
|
298
|
+
contextGcSubagentSnapshotIsPreciselyCovered(sourceSnapshot, terminalSnapshot)) &&
|
|
297
299
|
replacement.replacementContent ===
|
|
298
300
|
contextGcSubagentOutputReplacement({
|
|
299
301
|
sourceContent: sourceEvidence.result.payload.content,
|
|
300
302
|
source: sourceSnapshot,
|
|
301
303
|
terminal: terminalSnapshot,
|
|
302
304
|
terminalCallId: replacement.terminalCallId,
|
|
303
|
-
evidenceSequence: replacement.evidenceSequence
|
|
305
|
+
evidenceSequence: replacement.evidenceSequence,
|
|
306
|
+
formatVersion: subagentReplacementFormatVersion(replacement.replacementContent)
|
|
304
307
|
}));
|
|
305
308
|
}
|
|
309
|
+
if (replacement.kind === 'WEB_OUTPUT') {
|
|
310
|
+
const sourceEvidence = successfulToolEvidence(records, recordsBySequence, source.sequence, replacement.toolName);
|
|
311
|
+
if (!sourceEvidence ||
|
|
312
|
+
sourceEvidence.callId !== replacement.callId ||
|
|
313
|
+
hasFailedToolEvidence(records, sourceEvidence.call, sourceEvidence.result, replacement.toolName) ||
|
|
314
|
+
!completeWebToolEvidence(sourceEvidence.completed, sourceEvidence.result) ||
|
|
315
|
+
!isRecord(sourceEvidence.result.payload) ||
|
|
316
|
+
typeof sourceEvidence.result.payload.content !== 'string' ||
|
|
317
|
+
sourceEvidence.result.sequence !== replacement.evidenceSequence ||
|
|
318
|
+
!isRecord(sourceEvidence.completed.payload) ||
|
|
319
|
+
typeof sourceEvidence.completed.payload.truncated !== 'boolean')
|
|
320
|
+
return false;
|
|
321
|
+
return (replacement.replacementContent ===
|
|
322
|
+
contextGcWebOutputReplacement(replacement.toolName, sourceEvidence.result.payload.content, sourceEvidence.completed.payload.truncated));
|
|
323
|
+
}
|
|
306
324
|
if (!isSuccessfulExecLifecycleEvidence(replacement, source, records, recordsBySequence))
|
|
307
325
|
return false;
|
|
308
326
|
if (replacement.kind === 'TOOL_CALL_INPUT')
|
|
309
|
-
return isDeepStrictEqual(replacement.replacementArguments, contextGcExecInputReplacement(source.payload.arguments));
|
|
327
|
+
return isDeepStrictEqual(replacement.replacementArguments, contextGcExecInputReplacement(source.payload.arguments, execInputReplacementFormatVersion(replacement.replacementArguments)));
|
|
310
328
|
const targetEvidence = successfulToolEvidence(records, recordsBySequence, source.sequence, 'exec');
|
|
311
329
|
const result = normalizeContextGcExecResult(source.payload.content, targetEvidence?.completed.payload);
|
|
312
330
|
return (result !== undefined &&
|
|
313
|
-
replacement.replacementContent ===
|
|
331
|
+
replacement.replacementContent ===
|
|
332
|
+
contextGcExecOutputReplacement(result, execOutputReplacementFormatVersion(replacement.replacementContent)));
|
|
333
|
+
}
|
|
334
|
+
function staleReadReplacementFormatVersion(value) {
|
|
335
|
+
return value.includes(' formatVersion="2"') ? 2 : 1;
|
|
336
|
+
}
|
|
337
|
+
function applyPatchReplacementFormatVersion(value) {
|
|
338
|
+
return typeof value === 'string' && value.includes(' formatVersion="2"') ? 2 : 1;
|
|
339
|
+
}
|
|
340
|
+
function subagentReplacementFormatVersion(value) {
|
|
341
|
+
try {
|
|
342
|
+
const parsed = JSON.parse(value);
|
|
343
|
+
return isRecord(parsed) && isRecord(parsed.contextGc) && parsed.contextGc.formatVersion === 2
|
|
344
|
+
? 2
|
|
345
|
+
: 1;
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
return 1;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function execInputReplacementFormatVersion(value) {
|
|
352
|
+
if (typeof value === 'string' && value.includes(' formatVersion="3"'))
|
|
353
|
+
return 3;
|
|
354
|
+
if (!isRecord(value) || !isRecord(value.contextGc))
|
|
355
|
+
return 1;
|
|
356
|
+
return value.contextGc.formatVersion === 3 ? 3 : value.contextGc.formatVersion === 2 ? 2 : 1;
|
|
357
|
+
}
|
|
358
|
+
function execOutputReplacementFormatVersion(value) {
|
|
359
|
+
try {
|
|
360
|
+
const parsed = JSON.parse(value);
|
|
361
|
+
return isRecord(parsed) && parsed.contextGcFormatVersion === 3
|
|
362
|
+
? 3
|
|
363
|
+
: isRecord(parsed) && parsed.contextGcFormatVersion === 2
|
|
364
|
+
? 2
|
|
365
|
+
: 1;
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
return 1;
|
|
369
|
+
}
|
|
314
370
|
}
|
|
315
371
|
function toolNameForCallId(records, callId, beforeSequence) {
|
|
316
372
|
const call = modelToolCallForCallId(records, callId, beforeSequence);
|
|
@@ -338,6 +394,25 @@ function successfulToolEvidence(records, recordsBySequence, evidenceSequence, to
|
|
|
338
394
|
record.payload.toolName === toolName);
|
|
339
395
|
return completed ? { callId, call, completed, result } : undefined;
|
|
340
396
|
}
|
|
397
|
+
function completeWebToolEvidence(completed, result) {
|
|
398
|
+
return (isRecord(completed.payload) &&
|
|
399
|
+
completed.payload.type === 'tool.completed' &&
|
|
400
|
+
typeof completed.payload.truncated === 'boolean' &&
|
|
401
|
+
isRecord(result.payload) &&
|
|
402
|
+
typeof result.payload.content === 'string');
|
|
403
|
+
}
|
|
404
|
+
function hasFailedToolEvidence(records, call, result, toolName) {
|
|
405
|
+
if (!isRecord(result.payload) || typeof result.payload.callId !== 'string')
|
|
406
|
+
return true;
|
|
407
|
+
const callId = result.payload.callId;
|
|
408
|
+
return records.some((record) => record.sequence > call.sequence &&
|
|
409
|
+
record.sequence < result.sequence &&
|
|
410
|
+
record.type === 'tool.failed' &&
|
|
411
|
+
isRecord(record.payload) &&
|
|
412
|
+
record.payload.type === 'tool.failed' &&
|
|
413
|
+
record.payload.callId === callId &&
|
|
414
|
+
record.payload.toolName === toolName);
|
|
415
|
+
}
|
|
341
416
|
function modelToolCallForCallId(records, callId, beforeSequence) {
|
|
342
417
|
return records.findLast((record) => record.sequence < beforeSequence &&
|
|
343
418
|
record.type === 'model.context' &&
|
|
@@ -499,10 +574,12 @@ function isContextGcReplacement(value) {
|
|
|
499
574
|
if (value.kind === 'READ_OUTPUT_ITEMS') {
|
|
500
575
|
const items = value.items;
|
|
501
576
|
const evidenceSequences = value.evidenceSequences;
|
|
502
|
-
return (value
|
|
577
|
+
return (hasOnlyKeys(value, ['kind', 'callId', 'target', 'items', 'evidenceSequences']) &&
|
|
578
|
+
value.target.role === 'tool' &&
|
|
503
579
|
Array.isArray(items) &&
|
|
504
580
|
items.length > 0 &&
|
|
505
581
|
items.every((item) => isRecord(item) &&
|
|
582
|
+
hasOnlyKeys(item, ['index', 'path', 'replacement', 'evidenceSequence']) &&
|
|
506
583
|
isNonnegativeInteger(item.index) &&
|
|
507
584
|
isNonemptyString(item.path) &&
|
|
508
585
|
isNonemptyString(item.replacement) &&
|
|
@@ -516,7 +593,16 @@ function isContextGcReplacement(value) {
|
|
|
516
593
|
evidenceSequences.every((sequence) => items.some((item) => item.evidenceSequence === sequence)));
|
|
517
594
|
}
|
|
518
595
|
if (value.kind === 'TOOL_CALL_INPUT')
|
|
519
|
-
return (value
|
|
596
|
+
return (hasOnlyKeys(value, [
|
|
597
|
+
'kind',
|
|
598
|
+
'toolName',
|
|
599
|
+
'callId',
|
|
600
|
+
'target',
|
|
601
|
+
'replacementArguments',
|
|
602
|
+
'providerAliasTargets',
|
|
603
|
+
'evidenceSequence'
|
|
604
|
+
]) &&
|
|
605
|
+
value.target.role === 'tool_call' &&
|
|
520
606
|
(value.toolName === 'apply_patch' || value.toolName === 'exec') &&
|
|
521
607
|
value.target.toolName === value.toolName &&
|
|
522
608
|
Array.isArray(value.providerAliasTargets) &&
|
|
@@ -527,8 +613,21 @@ function isContextGcReplacement(value) {
|
|
|
527
613
|
isPositiveInteger(value.evidenceSequence) &&
|
|
528
614
|
value.replacementArguments !== undefined);
|
|
529
615
|
if (value.kind === 'SUBAGENT_OUTPUT')
|
|
530
|
-
return (value
|
|
531
|
-
|
|
616
|
+
return (hasOnlyKeys(value, [
|
|
617
|
+
'kind',
|
|
618
|
+
'toolName',
|
|
619
|
+
'callId',
|
|
620
|
+
'target',
|
|
621
|
+
'replacementContent',
|
|
622
|
+
'terminalToolName',
|
|
623
|
+
'terminalCallId',
|
|
624
|
+
'evidenceSequence',
|
|
625
|
+
'retainedPriorFinal'
|
|
626
|
+
]) &&
|
|
627
|
+
value.target.role === 'tool' &&
|
|
628
|
+
(value.toolName === 'agent_wait' ||
|
|
629
|
+
value.toolName === 'agent_message' ||
|
|
630
|
+
value.toolName === 'agent_cancel') &&
|
|
532
631
|
(value.target.toolName === undefined || value.target.toolName === value.toolName) &&
|
|
533
632
|
isNonemptyString(value.replacementContent) &&
|
|
534
633
|
(value.terminalToolName === 'agent_start' ||
|
|
@@ -546,13 +645,29 @@ function isContextGcReplacement(value) {
|
|
|
546
645
|
value.retainedPriorFinal.toolName === 'agent_cancel') &&
|
|
547
646
|
isNonemptyString(value.retainedPriorFinal.callId) &&
|
|
548
647
|
isPositiveInteger(value.retainedPriorFinal.evidenceSequence))));
|
|
648
|
+
if (value.kind === 'WEB_OUTPUT')
|
|
649
|
+
return (hasOnlyKeys(value, [
|
|
650
|
+
'kind',
|
|
651
|
+
'toolName',
|
|
652
|
+
'callId',
|
|
653
|
+
'target',
|
|
654
|
+
'replacementContent',
|
|
655
|
+
'evidenceSequence'
|
|
656
|
+
]) &&
|
|
657
|
+
value.target.role === 'tool' &&
|
|
658
|
+
(value.toolName === 'web_search' || value.toolName === 'web_fetch') &&
|
|
659
|
+
(value.target.toolName === undefined || value.target.toolName === value.toolName) &&
|
|
660
|
+
isNonemptyString(value.replacementContent) &&
|
|
661
|
+
isPositiveInteger(value.evidenceSequence));
|
|
549
662
|
return (value.kind === 'EXEC_OUTPUT' &&
|
|
663
|
+
hasOnlyKeys(value, ['kind', 'callId', 'target', 'replacementContent', 'evidenceSequence']) &&
|
|
550
664
|
value.target.role === 'tool' &&
|
|
551
665
|
isNonemptyString(value.replacementContent) &&
|
|
552
666
|
isPositiveInteger(value.evidenceSequence));
|
|
553
667
|
}
|
|
554
668
|
function isContextGcTarget(value) {
|
|
555
669
|
return (isRecord(value) &&
|
|
670
|
+
hasOnlyKeys(value, ['sequence', 'recordId', 'role', 'callId', 'toolName', 'sourceHash']) &&
|
|
556
671
|
isPositiveInteger(value.sequence) &&
|
|
557
672
|
isNonemptyString(value.recordId) &&
|
|
558
673
|
(value.role === 'tool_call' || value.role === 'tool' || value.role === 'provider') &&
|
|
@@ -635,6 +750,9 @@ function isContextGcMetrics(value) {
|
|
|
635
750
|
function isRecord(value) {
|
|
636
751
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
637
752
|
}
|
|
753
|
+
function hasOnlyKeys(value, keys) {
|
|
754
|
+
return Object.keys(value).every((key) => keys.includes(key));
|
|
755
|
+
}
|
|
638
756
|
function isNonemptyString(value) {
|
|
639
757
|
return typeof value === 'string' && value.length > 0;
|
|
640
758
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export type ContextGcWebToolName = 'web_search' | 'web_fetch';
|
|
1
2
|
export interface ContextGcPatchFile {
|
|
2
3
|
readonly path: string;
|
|
3
4
|
readonly targetPath?: string;
|
|
@@ -38,15 +39,17 @@ export type ContextGcExecResult = {
|
|
|
38
39
|
readonly modelContextContent: string;
|
|
39
40
|
};
|
|
40
41
|
export declare function contextGcPatchResultFromCompletedPayload(payload: unknown): ContextGcPatchResult | undefined;
|
|
41
|
-
export declare function contextGcApplyPatchInputReplacement(arguments_: unknown, result: ContextGcPatchResult, evidenceSequence: number): string;
|
|
42
|
+
export declare function contextGcApplyPatchInputReplacement(arguments_: unknown, result: ContextGcPatchResult, evidenceSequence: number, formatVersion?: 1 | 2): string;
|
|
42
43
|
export declare function contextGcStaleReadReplacement(input: {
|
|
43
44
|
readonly path: string;
|
|
44
45
|
readonly content: string;
|
|
45
46
|
readonly evidenceSequence: number;
|
|
46
47
|
readonly supersededByCallId: string;
|
|
47
48
|
readonly operation: Exclude<ContextGcPatchFile['operation'], 'added'>;
|
|
49
|
+
readonly formatVersion?: 1 | 2;
|
|
48
50
|
}): string;
|
|
49
|
-
export declare function
|
|
50
|
-
export declare function
|
|
51
|
+
export declare function contextGcWebOutputReplacement(toolName: ContextGcWebToolName, originalContent: string, truncated: boolean): string;
|
|
52
|
+
export declare function contextGcExecInputReplacement(arguments_: unknown, formatVersion?: 1 | 2 | 3): unknown;
|
|
53
|
+
export declare function contextGcExecOutputReplacement(result: ContextGcExecResult, formatVersion?: 1 | 2 | 3): string;
|
|
51
54
|
export declare function normalizeContextGcExecResult(modelContent: unknown, completedPayload: unknown): ContextGcExecResult | undefined;
|
|
52
55
|
export declare function parseContextGcExecResult(value: unknown): ContextGcExecResult | undefined;
|