@myagentroam/agent 0.9.65 → 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/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 = { responsesChain: undefined, adapters: new Map() };
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 <= messages.length
662
+ sessionRuntime.responsesChain.messageCount <= requestContextMessages.length
627
663
  ? {
628
664
  previousResponseId: sessionRuntime.responsesChain.responseId,
629
- deltaMessages: messages.slice(sessionRuntime.responsesChain.messageCount)
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: normalizeMessagesForModel(insertContextualInstructions(messages, agentInstructionInsertIndex, agentInstructionMessage), {
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 (active.size > 0 || reconfiguring)
1145
- throw new MarAgentError('MAR_AGENT_EXECUTION_ACTIVE', 'Models cannot be reconfigured while an execution is active.');
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 adapters = [...sessionRuntimes.values()].flatMap((runtime) => [
1148
- ...runtime.adapters.values()
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 sessionRuntimes.values()) {
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
  };
@@ -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 === contextGcExecOutputReplacement(result));
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.target.role === 'tool' &&
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.target.role === 'tool_call' &&
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.target.role === 'tool' &&
531
- (value.toolName === 'agent_wait' || value.toolName === 'agent_message') &&
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 contextGcExecInputReplacement(arguments_: unknown): unknown;
50
- export declare function contextGcExecOutputReplacement(result: ContextGcExecResult): string;
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;