@myagentroam/agent 0.9.164 → 0.9.165

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.
@@ -1,5 +1,3 @@
1
- import { countModelTokens } from '../token-budget.js';
2
- import { renderObservations } from '../../tools/code-mode/observations.js';
3
1
  import { codeCallEvidence, codeObservationEvidence, retainedCodeInputs } from '../../session/context-gc-observations.js';
4
2
  import { ContextGcCheckpointCache, contextGcBaseFromRecords, contextGcLogicalPathKey, contextGcMinimumUsageGrowth, contextGcRecordSourceHash, contextGcTargetKey, normalizeContextGcReplacements } from '../../session/context-gc-checkpoint.js';
5
3
  import { codeCellCompletionSequence, contextGcApplyPatchInputReplacement, contextGcCodeModeInputReplacement, contextGcCodeModeOutputReplacement, contextGcExecInputReplacement, contextGcExecOutputReplacement, contextGcPatchResultFromCompletedPayload, contextGcStaleReadReplacement, contextGcWebOutputReplacement, normalizeContextGcCodeModeInput, normalizeContextGcExecResult } from '../../session/context-gc-replacements.js';
@@ -30,8 +28,6 @@ const MINIMUM_CONTEXT_WINDOW_REDUCTION_RATIO = 0.05;
30
28
  const COMPACT_CREDIT_ACTIVATION_RATIO = 0.8;
31
29
  const COMPACT_THRESHOLD_RATIO = 0.9;
32
30
  const IMMINENT_COMPACT_CREDIT_DISCOUNT = 0.8;
33
- const LARGE_TERMINAL_OUTPUT_TOKENS = 4_096;
34
- const HIGH_WATER_LARGE_TERMINAL_OUTPUT_TOKENS = 2_048;
35
31
  export function evaluateContextGcEconomics(input) {
36
32
  const reclaimedTokens = Math.max(0, input.reclaimedTokens);
37
33
  const cacheReadTokens = Math.max(0, input.cacheReadTokens);
@@ -150,14 +146,15 @@ export function inspectContextGc(input) {
150
146
  return {};
151
147
  const groups = buildToolGroups(input.records, input.messages, input.messageSources);
152
148
  const existing = previous?.payload.replacements ?? [];
149
+ const candidateSavings = createCandidateSavingsEstimator(input, existing);
153
150
  const existingTargets = new Set(existing.flatMap((replacement) => [
154
151
  contextGcTargetKey(replacement.target),
155
152
  ...(replacement.kind === 'TOOL_CALL_INPUT'
156
153
  ? replacement.providerAliasTargets.map(contextGcTargetKey)
157
154
  : [])
158
155
  ]));
159
- const stageA = collectStageACandidates(input, groups, existing, existingTargets);
160
- const subagent = collectSubagentCandidates(input, groups, existingTargets);
156
+ const stageA = collectStageACandidates(input, groups, existing, existingTargets, candidateSavings);
157
+ const subagent = collectSubagentCandidates(input, groups, existingTargets, candidateSavings);
161
158
  const deterministicCandidates = [...stageA.eligible, ...subagent.eligible];
162
159
  const deterministicProtectedTokens = stageA.protectedTokens + subagent.protectedTokens;
163
160
  const stageAPlan = evaluateCandidateSet(input, existing, deterministicCandidates, {
@@ -169,9 +166,9 @@ export function inspectContextGc(input) {
169
166
  protectedCandidateTokens: deterministicProtectedTokens
170
167
  });
171
168
  if (stageAPlan?.economics.passes) {
172
- const exec = collectExecCandidates(input, groups, existingTargets, usageRatio);
173
- const codeMode = collectCodeModeCandidates(input, groups, existingTargets, usageRatio);
174
- const web = collectWebCandidates(input, groups, existingTargets, usageRatio);
169
+ const exec = collectExecCandidates(input, groups, existingTargets, candidateSavings);
170
+ const codeMode = collectCodeModeCandidates(input, groups, existingTargets, candidateSavings);
171
+ const web = collectWebCandidates(input, groups, existingTargets, candidateSavings);
175
172
  const protectedCandidateTokens = deterministicProtectedTokens +
176
173
  exec.protectedTokens +
177
174
  codeMode.protectedTokens +
@@ -202,9 +199,9 @@ export function inspectContextGc(input) {
202
199
  }
203
200
  : {};
204
201
  }
205
- const exec = collectExecCandidates(input, groups, existingTargets, usageRatio);
206
- const codeMode = collectCodeModeCandidates(input, groups, existingTargets, usageRatio);
207
- const web = collectWebCandidates(input, groups, existingTargets, usageRatio);
202
+ const exec = collectExecCandidates(input, groups, existingTargets, candidateSavings);
203
+ const codeMode = collectCodeModeCandidates(input, groups, existingTargets, candidateSavings);
204
+ const web = collectWebCandidates(input, groups, existingTargets, candidateSavings);
208
205
  const combined = evaluateCandidateSet(input, existing, [...deterministicCandidates, ...exec.eligible, ...codeMode.eligible, ...web.eligible], {
209
206
  stage: 'STAGE_A_AND_EXEC',
210
207
  contextUsageTokens,
@@ -230,7 +227,7 @@ export function inspectContextGc(input) {
230
227
  ? { retryAfterRollouts }
231
228
  : {};
232
229
  }
233
- function collectCodeModeCandidates(input, groups, existingTargets, usageRatio) {
230
+ function collectCodeModeCandidates(input, groups, existingTargets, candidateSavings) {
234
231
  const eligible = [];
235
232
  let protectedTokens = 0;
236
233
  let retryAfterRollouts = Infinity;
@@ -251,19 +248,8 @@ function collectCodeModeCandidates(input, groups, existingTargets, usageRatio) {
251
248
  !isRecord(completedPayload) ||
252
249
  typeof completedPayload.truncated !== 'boolean')
253
250
  continue;
254
- const visibleTokens = contextGcMessageTokens(input, [
255
- group.call.message,
256
- group.result.message,
257
- ...group.aliases.map((alias) => alias.message)
258
- ]);
259
- const minimumVisibleTokens = usageRatio >= COMPACT_CREDIT_ACTIVATION_RATIO
260
- ? HIGH_WATER_LARGE_TERMINAL_OUTPUT_TOKENS
261
- : LARGE_TERMINAL_OUTPUT_TOKENS;
262
- if (visibleTokens < minimumVisibleTokens)
263
- continue;
264
251
  for (const observation of group.result.message.observations ?? []) {
265
- if (observation.type !== 'value' ||
266
- countModelTokens(observation.value) < minimumVisibleTokens)
252
+ if (observation.type !== 'value')
267
253
  continue;
268
254
  const target = targetFor(group.result.record, {
269
255
  ...group.result.message,
@@ -285,10 +271,13 @@ function collectCodeModeCandidates(input, groups, existingTargets, usageRatio) {
285
271
  observedRollouts: successfulRolloutsAfter(input.records, Math.max(evidenceSequence, completionSequence), input.modelId, input.contextWindowTokens),
286
272
  evidenceSequence
287
273
  };
274
+ const reclaimedTokens = candidateSavings(candidate);
275
+ if (reclaimedTokens <= 0)
276
+ continue;
288
277
  if (candidate.observedRollouts >= CONTEXT_GC_POLICY.largeExecProtectionRollouts)
289
278
  eligible.push(candidate);
290
279
  else {
291
- protectedTokens += candidateTokens(input, candidate);
280
+ protectedTokens += reclaimedTokens;
292
281
  retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - candidate.observedRollouts);
293
282
  }
294
283
  }
@@ -323,10 +312,13 @@ function collectCodeModeCandidates(input, groups, existingTargets, usageRatio) {
323
312
  continue;
324
313
  const observedRollouts = successfulRolloutsAfter(input.records, Math.max(evidenceSequence, completionSequence), input.modelId, input.contextWindowTokens);
325
314
  const candidate = { replacements, observedRollouts, evidenceSequence };
315
+ const reclaimedTokens = candidateSavings(candidate);
316
+ if (reclaimedTokens <= 0)
317
+ continue;
326
318
  if (observedRollouts >= CONTEXT_GC_POLICY.largeExecProtectionRollouts)
327
319
  eligible.push(candidate);
328
320
  else {
329
- protectedTokens += candidateTokens(input, candidate);
321
+ protectedTokens += reclaimedTokens;
330
322
  retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - observedRollouts);
331
323
  }
332
324
  }
@@ -528,7 +520,7 @@ function evaluateCandidateSet(input, existing, candidates, state) {
528
520
  eligibleCandidateCount: candidates.length
529
521
  };
530
522
  }
531
- function collectStageACandidates(input, groups, existingReplacements, existingTargets) {
523
+ function collectStageACandidates(input, groups, existingReplacements, existingTargets, candidateSavings) {
532
524
  const eligible = [];
533
525
  let protectedTokens = 0;
534
526
  let retryAfterRollouts = Infinity;
@@ -555,6 +547,7 @@ function collectStageACandidates(input, groups, existingReplacements, existingTa
555
547
  const patchCall = patch.call;
556
548
  const observedRollouts = successfulRolloutsAfter(input.records, evidenceSequence, input.modelId, input.contextWindowTokens);
557
549
  const replacements = [];
550
+ const candidateReadItems = new Set();
558
551
  const patchTarget = targetFor(patchCall.record, patchCall.message);
559
552
  if (patchTarget && !existingTargets.has(contextGcTargetKey(patchTarget)))
560
553
  replacements.push({
@@ -578,14 +571,14 @@ function collectStageACandidates(input, groups, existingReplacements, existingTa
578
571
  if (item.pathTruncated)
579
572
  return [];
580
573
  const itemKey = `${read.result.record.recordId}:${read.result.message.observationId ?? ''}:${item.index}`;
581
- if (claimedReadItems.has(itemKey))
574
+ if (claimedReadItems.has(itemKey) || candidateReadItems.has(itemKey))
582
575
  return [];
583
576
  const pathKey = contextGcLogicalPathKey(item.path, input.workspace, input.platform);
584
577
  const matched = result.files.find((file) => file.operation !== 'added' &&
585
578
  contextGcLogicalPathKey(file.path, input.workspace, input.platform) === pathKey);
586
579
  if (!pathKey || !matched || matched.operation === 'added')
587
580
  return [];
588
- claimedReadItems.add(itemKey);
581
+ candidateReadItems.add(itemKey);
589
582
  return [
590
583
  {
591
584
  index: item.index,
@@ -625,16 +618,21 @@ function collectStageACandidates(input, groups, existingReplacements, existingTa
625
618
  observedRollouts,
626
619
  evidenceSequence
627
620
  };
621
+ const reclaimedTokens = candidateSavings(candidate);
622
+ if (reclaimedTokens <= 0)
623
+ continue;
624
+ for (const itemKey of candidateReadItems)
625
+ claimedReadItems.add(itemKey);
628
626
  if (observedRollouts >= CONTEXT_GC_POLICY.stageAProtectionRollouts)
629
627
  eligible.push(candidate);
630
628
  else {
631
- protectedTokens += candidateTokens(input, candidate);
629
+ protectedTokens += reclaimedTokens;
632
630
  retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.stageAProtectionRollouts - observedRollouts);
633
631
  }
634
632
  }
635
633
  return { eligible, protectedTokens, retryAfterRollouts };
636
634
  }
637
- function collectSubagentCandidates(input, groups, existingTargets) {
635
+ function collectSubagentCandidates(input, groups, existingTargets, candidateSavings) {
638
636
  const eligible = [];
639
637
  let protectedTokens = 0;
640
638
  let retryAfterRollouts = Infinity;
@@ -714,16 +712,19 @@ function collectSubagentCandidates(input, groups, existingTargets) {
714
712
  observedRollouts,
715
713
  evidenceSequence
716
714
  };
715
+ const reclaimedTokens = candidateSavings(candidate);
716
+ if (reclaimedTokens <= 0)
717
+ continue;
717
718
  if (observedRollouts >= CONTEXT_GC_POLICY.stageAProtectionRollouts)
718
719
  eligible.push(candidate);
719
720
  else {
720
- protectedTokens += candidateTokens(input, candidate);
721
+ protectedTokens += reclaimedTokens;
721
722
  retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.stageAProtectionRollouts - observedRollouts);
722
723
  }
723
724
  }
724
725
  return { eligible, protectedTokens, retryAfterRollouts };
725
726
  }
726
- function collectExecCandidates(input, groups, existingTargets, usageRatio) {
727
+ function collectExecCandidates(input, groups, existingTargets, candidateSavings) {
727
728
  const eligible = [];
728
729
  let protectedTokens = 0;
729
730
  let retryAfterRollouts = Infinity;
@@ -797,18 +798,6 @@ function collectExecCandidates(input, groups, existingTargets, usageRatio) {
797
798
  if (!terminal || (processId && input.activeProcessIds.has(processId)))
798
799
  continue;
799
800
  const final = lifecycle.at(-1);
800
- const visibleTokens = contextGcMessageTokens(input, lifecycle
801
- .filter((invocation) => !invocation.group.evidenceOnly)
802
- .flatMap((invocation) => [
803
- invocation.group.call.message,
804
- invocation.group.result.message,
805
- ...invocation.group.aliases.map((alias) => alias.message)
806
- ]));
807
- const minimumVisibleTokens = usageRatio >= COMPACT_CREDIT_ACTIVATION_RATIO
808
- ? HIGH_WATER_LARGE_TERMINAL_OUTPUT_TOKENS
809
- : LARGE_TERMINAL_OUTPUT_TOKENS;
810
- if (visibleTokens < minimumVisibleTokens)
811
- continue;
812
801
  const evidenceSequence = final.group.result.record.sequence;
813
802
  const observedRollouts = successfulRolloutsAfter(input.records, evidenceSequence, input.modelId, input.contextWindowTokens);
814
803
  const replacements = [];
@@ -850,22 +839,22 @@ function collectExecCandidates(input, groups, existingTargets, usageRatio) {
850
839
  observedRollouts,
851
840
  evidenceSequence
852
841
  };
842
+ const reclaimedTokens = candidateSavings(candidate);
843
+ if (reclaimedTokens <= 0)
844
+ continue;
853
845
  if (observedRollouts >= CONTEXT_GC_POLICY.largeExecProtectionRollouts)
854
846
  eligible.push(candidate);
855
847
  else {
856
- protectedTokens += candidateTokens(input, candidate);
848
+ protectedTokens += reclaimedTokens;
857
849
  retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - observedRollouts);
858
850
  }
859
851
  }
860
852
  return { eligible, protectedTokens, retryAfterRollouts };
861
853
  }
862
- function collectWebCandidates(input, groups, existingTargets, usageRatio) {
854
+ function collectWebCandidates(input, groups, existingTargets, candidateSavings) {
863
855
  const eligible = [];
864
856
  let protectedTokens = 0;
865
857
  let retryAfterRollouts = Infinity;
866
- const minimumVisibleTokens = usageRatio >= COMPACT_CREDIT_ACTIVATION_RATIO
867
- ? HIGH_WATER_LARGE_TERMINAL_OUTPUT_TOKENS
868
- : LARGE_TERMINAL_OUTPUT_TOKENS;
869
858
  for (const group of [...groups.values()].sort((left, right) => (left.result?.record.sequence ?? Number.MAX_SAFE_INTEGER) -
870
859
  (right.result?.record.sequence ?? Number.MAX_SAFE_INTEGER))) {
871
860
  if (!isContextGcWebToolName(group.toolName) ||
@@ -885,9 +874,6 @@ function collectWebCandidates(input, groups, existingTargets, usageRatio) {
885
874
  const target = targetFor(group.result.record, group.result.message);
886
875
  if (!target || existingTargets.has(contextGcTargetKey(target)))
887
876
  continue;
888
- const visibleTokens = contextGcMessageTokens(input, [group.result.message]);
889
- if (visibleTokens < minimumVisibleTokens)
890
- continue;
891
877
  const evidenceSequence = group.result.record.sequence;
892
878
  const observedRollouts = successfulRolloutsAfter(input.records, evidenceSequence, input.modelId, input.contextWindowTokens);
893
879
  const candidate = {
@@ -904,10 +890,13 @@ function collectWebCandidates(input, groups, existingTargets, usageRatio) {
904
890
  observedRollouts,
905
891
  evidenceSequence
906
892
  };
893
+ const reclaimedTokens = candidateSavings(candidate);
894
+ if (reclaimedTokens <= 0)
895
+ continue;
907
896
  if (observedRollouts >= CONTEXT_GC_POLICY.largeExecProtectionRollouts)
908
897
  eligible.push(candidate);
909
898
  else {
910
- protectedTokens += candidateTokens(input, candidate);
899
+ protectedTokens += reclaimedTokens;
911
900
  retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - observedRollouts);
912
901
  }
913
902
  }
@@ -1230,23 +1219,46 @@ function firstMatchingUsageAfter(records, sequence, modelId, contextWindowTokens
1230
1219
  }
1231
1220
  return undefined;
1232
1221
  }
1233
- function candidateTokens(input, candidate) {
1234
- const targets = new Set(candidate.replacements.flatMap((replacement) => [
1235
- contextGcTargetKey(replacement.target),
1236
- ...(replacement.kind === 'TOOL_CALL_INPUT'
1237
- ? replacement.providerAliasTargets.map(contextGcTargetKey)
1238
- : [])
1239
- ]));
1240
- return contextGcMessageTokens(input, input.messages.flatMap((message, index) => {
1241
- const target = targetFor(input.messageSources[index], message);
1242
- if (target && targets.has(contextGcTargetKey(target)))
1243
- return [message];
1244
- const selected = target &&
1245
- message.observations?.filter((value) => targets.has(contextGcTargetKey({ ...target, observationId: value.id })));
1246
- return selected?.length
1247
- ? [{ ...message, observations: selected, content: renderObservations(selected) }]
1248
- : [];
1249
- }));
1222
+ /** Measure only affected call families, retaining native aliases for provider lowering. */
1223
+ function createCandidateSavingsEstimator(input, existing) {
1224
+ const byCall = new Map();
1225
+ const byRecord = new Map();
1226
+ for (const [index, message] of input.messages.entries()) {
1227
+ const callId = message.role === 'provider' ? providerToolCallId(message) : message.callId;
1228
+ if (callId) {
1229
+ const indices = byCall.get(callId) ?? [];
1230
+ indices.push(index);
1231
+ byCall.set(callId, indices);
1232
+ }
1233
+ const recordId = input.messageSources[index].recordId;
1234
+ const indices = byRecord.get(recordId) ?? [];
1235
+ indices.push(index);
1236
+ byRecord.set(recordId, indices);
1237
+ }
1238
+ return (candidate) => {
1239
+ const indices = new Set();
1240
+ for (const replacement of candidate.replacements) {
1241
+ const targets = [
1242
+ replacement.target,
1243
+ ...(replacement.kind === 'TOOL_CALL_INPUT' ? replacement.providerAliasTargets : [])
1244
+ ];
1245
+ for (const target of targets) {
1246
+ for (const index of byRecord.get(target.recordId) ?? [])
1247
+ indices.add(index);
1248
+ if (target.callId)
1249
+ for (const index of byCall.get(target.callId) ?? [])
1250
+ indices.add(index);
1251
+ }
1252
+ }
1253
+ const ordered = [...indices].sort((a, b) => a - b);
1254
+ const messages = ordered.map((index) => input.messages[index]);
1255
+ const sources = ordered.map((index) => input.messageSources[index]);
1256
+ const replacements = normalizeContextGcReplacements([...existing, ...candidate.replacements]);
1257
+ if (!replacements)
1258
+ return 0;
1259
+ const projected = applyContextGcReplacementsToMessages(messages, sources, replacements);
1260
+ return contextGcReclaimedTokens(input, messages, projected.messages);
1261
+ };
1250
1262
  }
1251
1263
  function targetFor(record, message) {
1252
1264
  if (record.type !== 'model.context' ||
@@ -272,7 +272,9 @@ function targetFromMessage(record, message) {
272
272
  };
273
273
  }
274
274
  function targetMatchesReplacement(left, right) {
275
- return (contextGcTargetKey(left) === contextGcTargetKey(right) && left.sourceHash === right.sourceHash);
275
+ return (contextGcTargetKey(left) === contextGcTargetKey(right) &&
276
+ left.sourceHash === right.sourceHash &&
277
+ (right.toolName === undefined || right.toolName === left.toolName));
276
278
  }
277
279
  function cloneMessage(message) {
278
280
  return structuredClone(message);
@@ -58,7 +58,8 @@ export function contextGcLogicalPathKey(path, workspace, platform) {
58
58
  return resolved;
59
59
  }
60
60
  export function contextGcTargetKey(target) {
61
- return `${target.sequence}:${target.recordId}:${target.role}:${target.callId ?? ''}:${target.toolName ?? ''}${target.observationId ? `:${target.observationId}` : ''}`;
61
+ // Tool outputs are identified by their record/call and observation, not optional tool-name metadata.
62
+ return `${target.sequence}:${target.recordId}:${target.role}:${target.callId ?? ''}:${target.role === 'tool' ? '' : (target.toolName ?? '')}${target.observationId ? `:${target.observationId}` : ''}`;
62
63
  }
63
64
  export function contextGcReplacementKey(replacement) {
64
65
  return contextGcTargetKey(replacement.target);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.9.164",
3
+ "version": "0.9.165",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",