@myagentroam/agent 0.11.52 → 0.11.53

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.
Files changed (39) hide show
  1. package/dist/model/anthropic-messages.d.ts +4 -0
  2. package/dist/model/anthropic-messages.js +1 -1
  3. package/dist/model/native-context.d.ts +2 -0
  4. package/dist/model/native-context.js +26 -0
  5. package/dist/runtime/context/gc-candidates-code.js +36 -21
  6. package/dist/runtime/context/gc-candidates-exec.js +33 -56
  7. package/dist/runtime/context/gc-candidates-read-patch.d.ts +1 -1
  8. package/dist/runtime/context/gc-candidates-read-patch.js +98 -15
  9. package/dist/runtime/context/gc-candidates-web.js +172 -8
  10. package/dist/runtime/context/gc-contracts.d.ts +2 -0
  11. package/dist/runtime/context/gc-economics.d.ts +0 -6
  12. package/dist/runtime/context/gc-economics.js +1 -23
  13. package/dist/runtime/context/gc-tokens.js +18 -8
  14. package/dist/runtime/context/gc.d.ts +1 -3
  15. package/dist/runtime/context/gc.js +19 -96
  16. package/dist/runtime/context/projection.js +11 -1
  17. package/dist/session/code-mode-search-projection.d.ts +5 -0
  18. package/dist/session/code-mode-search-projection.js +46 -0
  19. package/dist/session/context-gc-checkpoint.js +233 -17
  20. package/dist/session/context-gc-contract.d.ts +3 -2
  21. package/dist/session/context-gc-exec-lifecycle.d.ts +11 -0
  22. package/dist/session/context-gc-exec-lifecycle.js +41 -0
  23. package/dist/session/context-gc-markup.d.ts +1 -0
  24. package/dist/session/context-gc-markup.js +4 -1
  25. package/dist/session/context-gc-observations.d.ts +5 -1
  26. package/dist/session/context-gc-observations.js +186 -33
  27. package/dist/session/context-gc-protection.d.ts +8 -0
  28. package/dist/session/context-gc-protection.js +83 -0
  29. package/dist/session/context-gc-replacements.d.ts +6 -1
  30. package/dist/session/context-gc-replacements.js +54 -6
  31. package/dist/session/context-gc-todo.d.ts +13 -0
  32. package/dist/session/context-gc-todo.js +47 -0
  33. package/dist/session/tool-callers.d.ts +7 -0
  34. package/dist/session/tool-callers.js +28 -0
  35. package/dist/tools/callers.d.ts +1 -7
  36. package/dist/tools/callers.js +1 -28
  37. package/dist/tools/code-mode/index.d.ts +1 -3
  38. package/dist/tools/code-mode/index.js +3 -45
  39. package/package.json +1 -1
@@ -1,7 +1,8 @@
1
1
  import { contextGcTargetKey } from '../../session/context-gc-checkpoint.js';
2
- import { contextGcWebOutputReplacement, contextGcToolOutputReplacement, isGenericContextGcTool } from '../../session/context-gc-replacements.js';
2
+ import { completedTodoWrite, todoInputReplacement, todoSnapshotReplacement, visibleTodoSnapshot } from '../../session/context-gc-todo.js';
3
+ import { contextGcWebOutputReplacement, contextGcToolOutputReplacement, contextGcSkillOutputReplacement, hasGenericContextGcPreview, isGenericContextGcTool } from '../../session/context-gc-replacements.js';
3
4
  import { CONTEXT_GC_POLICY } from './gc-economics.js';
4
- import { successfulRolloutsAfter, targetFor, isRecord } from './gc-candidates.js';
5
+ import { targetFor, isRecord } from './gc-candidates.js';
5
6
  export function collectWebCandidates(input, groups, existingTargets, candidateSavings) {
6
7
  const eligible = [];
7
8
  let protectedTokens = 0;
@@ -26,7 +27,8 @@ export function collectWebCandidates(input, groups, existingTargets, candidateSa
26
27
  if (!target || existingTargets.has(contextGcTargetKey(target)))
27
28
  continue;
28
29
  const evidenceSequence = group.result.record.sequence;
29
- const observedRollouts = successfulRolloutsAfter(input.records, evidenceSequence, input.modelId, input.contextWindowTokens);
30
+ const protection = input.protection.after(Math.max(evidenceSequence, target.sequence));
31
+ const observedRollouts = protection.rollouts;
30
32
  const candidate = {
31
33
  replacements: [
32
34
  {
@@ -44,9 +46,10 @@ export function collectWebCandidates(input, groups, existingTargets, candidateSa
44
46
  const reclaimedTokens = candidateSavings(candidate);
45
47
  if (reclaimedTokens <= 0)
46
48
  continue;
47
- if (observedRollouts >= CONTEXT_GC_POLICY.largeExecProtectionRollouts)
49
+ if (observedRollouts >= CONTEXT_GC_POLICY.largeExecProtectionRollouts &&
50
+ protection.thinking >= 3)
48
51
  eligible.push(candidate);
49
- else {
52
+ else if (observedRollouts < CONTEXT_GC_POLICY.largeExecProtectionRollouts) {
50
53
  protectedTokens += reclaimedTokens;
51
54
  retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - observedRollouts);
52
55
  }
@@ -58,6 +61,164 @@ export function collectToolOutputCandidates(input, groups, existingTargets, cand
58
61
  const eligible = [];
59
62
  let protectedTokens = 0;
60
63
  let retryAfterRollouts = Infinity;
64
+ const searches = [...groups.values()]
65
+ .filter((group) => group.toolName === 'tool_search' &&
66
+ !group.failed &&
67
+ group.completed &&
68
+ group.call &&
69
+ group.result?.message.role === 'tool' &&
70
+ group.result.message.toolSearchTools !== undefined)
71
+ .sort((left, right) => left.result.record.sequence - right.result.record.sequence);
72
+ for (const group of searches) {
73
+ const result = group.result;
74
+ const target = targetFor(result.record, result.message);
75
+ const definitions = result.message.toolSearchTools;
76
+ if (!target || definitions.length === 0)
77
+ continue;
78
+ const retainedToolNames = [];
79
+ const evidence = [];
80
+ for (const tool of definitions) {
81
+ const successor = searches.findLast((candidate) => candidate.result.record.sequence > result.record.sequence &&
82
+ candidate.result.message.toolSearchTools.some((item) => item.name === tool.name) &&
83
+ (() => {
84
+ const count = input.protection.after(Math.max(target.sequence, candidate.result.record.sequence));
85
+ return count.rollouts >= 6 && count.thinking >= 3;
86
+ })());
87
+ if (successor)
88
+ evidence.push(successor.result.record.sequence);
89
+ else
90
+ retainedToolNames.push(tool.name);
91
+ }
92
+ if (evidence.length === 0)
93
+ continue;
94
+ const evidenceSequence = Math.max(...evidence);
95
+ const observedRollouts = input.protection.after(Math.max(target.sequence, evidenceSequence)).rollouts;
96
+ const candidate = {
97
+ replacements: [
98
+ {
99
+ kind: 'TOOL_OUTPUT',
100
+ toolName: 'tool_search',
101
+ callId: group.callId,
102
+ target,
103
+ replacementContent: result.message.content,
104
+ retainedToolNames,
105
+ evidenceSequence
106
+ }
107
+ ],
108
+ observedRollouts,
109
+ evidenceSequence
110
+ };
111
+ if (candidateSavings(candidate) > 0)
112
+ eligible.push(candidate);
113
+ }
114
+ const writes = input.records.flatMap((record) => {
115
+ const fact = completedTodoWrite(record);
116
+ return fact ? [{ record, ...fact }] : [];
117
+ });
118
+ for (const group of groups.values()) {
119
+ if (group.failed || !group.completed || !group.call || group.call.message.role !== 'tool_call')
120
+ continue;
121
+ const ownWrite = completedTodoWrite(group.completed);
122
+ const snapshot = group.toolName === 'todo_read' && group.result?.message.role === 'tool'
123
+ ? visibleTodoSnapshot(group.result.message.content)
124
+ : undefined;
125
+ const currentRevision = group.toolName === 'todo_write' ? ownWrite?.revision : snapshot?.revision;
126
+ if (currentRevision === undefined)
127
+ continue;
128
+ const targetEntry = group.toolName === 'todo_write' ? group.call : group.result;
129
+ if (!targetEntry)
130
+ continue;
131
+ const target = targetFor(targetEntry.record, targetEntry.message);
132
+ if (!target || existingTargets.has(contextGcTargetKey(target)))
133
+ continue;
134
+ const successor = writes
135
+ .filter((write) => write.revision > currentRevision &&
136
+ input.protection.after(Math.max(target.sequence, group.completed.sequence, write.record.sequence)).rollouts >= 6 &&
137
+ input.protection.after(Math.max(target.sequence, group.completed.sequence, write.record.sequence)).thinking >= 3)
138
+ .sort((a, b) => b.revision - a.revision)[0];
139
+ if (!successor)
140
+ continue;
141
+ const evidenceSequence = successor.record.sequence;
142
+ const observedRollouts = input.protection.after(Math.max(target.sequence, group.completed.sequence, evidenceSequence)).rollouts;
143
+ let replacement;
144
+ if (group.toolName === 'todo_write') {
145
+ const args = group.call.message.arguments;
146
+ if (!isRecord(args) || !Array.isArray(args.items))
147
+ continue;
148
+ replacement = {
149
+ kind: 'TOOL_CALL_INPUT',
150
+ toolName: 'todo_write',
151
+ callId: group.callId,
152
+ target,
153
+ replacementArguments: todoInputReplacement(currentRevision, args.items.length, successor.revision),
154
+ providerAliasTargets: group.aliases
155
+ .map(({ record, message }) => targetFor(record, message))
156
+ .filter((value) => value !== undefined),
157
+ evidenceSequence
158
+ };
159
+ }
160
+ else {
161
+ if (!snapshot)
162
+ continue;
163
+ replacement = {
164
+ kind: 'TOOL_OUTPUT',
165
+ toolName: 'todo_read',
166
+ callId: group.callId,
167
+ target,
168
+ replacementContent: todoSnapshotReplacement(currentRevision, snapshot.count, successor.revision),
169
+ evidenceSequence
170
+ };
171
+ }
172
+ const candidate = { replacements: [replacement], observedRollouts, evidenceSequence };
173
+ if (candidateSavings(candidate) > 0)
174
+ eligible.push(candidate);
175
+ }
176
+ const skills = [...groups.values()]
177
+ .filter((group) => group.toolName === 'skill' &&
178
+ group.call?.message.role === 'tool_call' &&
179
+ isRecord(group.call.message.arguments) &&
180
+ typeof group.call.message.arguments.name === 'string' &&
181
+ group.result?.message.role === 'tool' &&
182
+ group.completed &&
183
+ !group.failed &&
184
+ !group.evidenceOnly)
185
+ .sort((left, right) => left.result.record.sequence - right.result.record.sequence);
186
+ for (const [index, group] of skills.entries()) {
187
+ const target = targetFor(group.result.record, group.result.message);
188
+ if (!target || existingTargets.has(contextGcTargetKey(target)))
189
+ continue;
190
+ const args = group.call.message.arguments;
191
+ const later = skills.slice(index + 1).filter((candidate) => {
192
+ const laterTarget = targetFor(candidate.result.record, candidate.result.message);
193
+ return (laterTarget &&
194
+ candidate.call.message.arguments.name === args.name &&
195
+ !existingTargets.has(contextGcTargetKey(laterTarget)));
196
+ });
197
+ const successor = later.findLast((candidate) => {
198
+ const count = input.protection.after(Math.max(target.sequence, candidate.result.record.sequence));
199
+ return count.rollouts >= 6 && count.thinking >= 3;
200
+ });
201
+ if (!successor)
202
+ continue;
203
+ const evidenceSequence = successor.result.record.sequence;
204
+ const observedRollouts = input.protection.after(Math.max(target.sequence, evidenceSequence)).rollouts;
205
+ const candidate = {
206
+ replacements: [
207
+ {
208
+ kind: 'TOOL_OUTPUT',
209
+ toolName: 'skill',
210
+ callId: group.callId,
211
+ target,
212
+ replacementContent: contextGcSkillOutputReplacement(args.name, group.result.message.content, successor.callId),
213
+ evidenceSequence
214
+ }
215
+ ],
216
+ observedRollouts,
217
+ evidenceSequence
218
+ };
219
+ if (candidateSavings(candidate) > 0)
220
+ eligible.push(candidate);
221
+ }
61
222
  for (const group of groups.values()) {
62
223
  if (!isGenericContextGcTool(group.toolName) ||
63
224
  group.evidenceOnly ||
@@ -68,6 +229,7 @@ export function collectToolOutputCandidates(input, groups, existingTargets, cand
68
229
  group.call.message.role !== 'tool_call' ||
69
230
  group.result.message.role !== 'tool' ||
70
231
  typeof group.result.message.content !== 'string' ||
232
+ !hasGenericContextGcPreview(group.result.message.content) ||
71
233
  !isRecord(group.completed.payload) ||
72
234
  group.completed.payload.type !== 'tool.completed' ||
73
235
  group.completed.payload.callId !== group.callId ||
@@ -78,7 +240,8 @@ export function collectToolOutputCandidates(input, groups, existingTargets, cand
78
240
  if (!target || existingTargets.has(contextGcTargetKey(target)))
79
241
  continue;
80
242
  const evidenceSequence = group.result.record.sequence;
81
- const observedRollouts = successfulRolloutsAfter(input.records, evidenceSequence, input.modelId, input.contextWindowTokens);
243
+ const protection = input.protection.after(Math.max(evidenceSequence, target.sequence));
244
+ const observedRollouts = protection.rollouts;
82
245
  const candidate = {
83
246
  replacements: [
84
247
  {
@@ -96,9 +259,10 @@ export function collectToolOutputCandidates(input, groups, existingTargets, cand
96
259
  const reclaimedTokens = candidateSavings(candidate);
97
260
  if (reclaimedTokens <= 0)
98
261
  continue;
99
- if (observedRollouts >= CONTEXT_GC_POLICY.largeExecProtectionRollouts)
262
+ if (observedRollouts >= CONTEXT_GC_POLICY.largeExecProtectionRollouts &&
263
+ protection.thinking >= 3)
100
264
  eligible.push(candidate);
101
- else {
265
+ else if (observedRollouts < CONTEXT_GC_POLICY.largeExecProtectionRollouts) {
102
266
  protectedTokens += reclaimedTokens;
103
267
  retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - observedRollouts);
104
268
  }
@@ -3,6 +3,7 @@ import type { SessionModelMessage as ModelMessage } from '../../session/model-re
3
3
  import type { ModelUsage } from '../../model/contracts.js';
4
4
  import { ContextGcCheckpointCache, type ContextGcCheckpointDraft, type ContextGcMetrics, type ContextGcPlatform, type ContextGcReplacement } from '../../session/context-gc-checkpoint.js';
5
5
  import type { SessionRecord } from '../../session/record-contract.js';
6
+ import type { buildContextGcProtection } from '../../session/context-gc-protection.js';
6
7
  export interface ContextGcPlan {
7
8
  readonly stage: ContextGcMetrics['stage'];
8
9
  readonly draft: ContextGcCheckpointDraft;
@@ -29,6 +30,7 @@ export interface ContextGcPlannerInput {
29
30
  readonly activeProcessIds: ReadonlySet<string>;
30
31
  readonly tokenCache?: Map<string, number>;
31
32
  readonly checkpointCache?: ContextGcCheckpointCache;
33
+ readonly protection?: ReturnType<typeof buildContextGcProtection>;
32
34
  }
33
35
  export interface ContextCandidate {
34
36
  readonly replacements: readonly ContextGcReplacement[];
@@ -24,16 +24,10 @@ export interface ContextGcEconomicsResult {
24
24
  readonly grossReductionRate: number;
25
25
  readonly passes: boolean;
26
26
  }
27
- interface ContextGcCacheEconomicsProfile {
28
- readonly reclaimedTokens: number;
29
- readonly reclaimedCachedTokens: number;
30
- readonly cacheRebuildPenaltyCostUnits: number;
31
- }
32
27
  export declare function evaluateContextGcEconomics(input: ContextGcEconomicsInput): ContextGcEconomicsResult;
33
28
  export declare function contextGcMinimumGrossReductionRate(usageRatio: number): number;
34
29
  export declare function contextGcMinimumReclaimedTokens(contextUsageTokens: number, contextWindowTokens: number): number;
35
30
  export declare function contextGcEligibleUsage(usage: ModelUsage, contextWindowTokens: number): number | undefined;
36
- export declare function contextGcMarginalCacheInvestmentPaysBack(current: ContextGcCacheEconomicsProfile, combined: ContextGcCacheEconomicsProfile): boolean;
37
31
  export declare function contextGcImminentCompactAvoidanceCredit(input: {
38
32
  readonly contextUsageTokens: number;
39
33
  readonly contextWindowTokens: number;
@@ -5,7 +5,7 @@ export const CONTEXT_GC_POLICY = {
5
5
  contextGrowthSampleWindow: 16,
6
6
  minimumContextGrowthSamples: 6,
7
7
  minimumObservationRollouts: 1,
8
- stageAProtectionRollouts: 2,
8
+ stageAProtectionRollouts: 6,
9
9
  largeExecProtectionRollouts: 6,
10
10
  normalInputCostRate: 1,
11
11
  cacheReadCostRate: 0.1,
@@ -107,28 +107,6 @@ export function contextGcEligibleUsage(usage, contextWindowTokens) {
107
107
  return undefined;
108
108
  return contextUsageTokens;
109
109
  }
110
- export function contextGcMarginalCacheInvestmentPaysBack(current, combined) {
111
- const marginalPenalty = combined.cacheRebuildPenaltyCostUnits - current.cacheRebuildPenaltyCostUnits;
112
- if (marginalPenalty <= 0)
113
- return true;
114
- const firstRolloutSavings = (profile) => {
115
- const reclaimedCachedTokens = clamp(profile.reclaimedCachedTokens, 0, Math.max(0, profile.reclaimedTokens));
116
- return (reclaimedCachedTokens * CONTEXT_GC_POLICY.cacheReadCostRate +
117
- Math.max(0, profile.reclaimedTokens - reclaimedCachedTokens) *
118
- CONTEXT_GC_POLICY.normalInputCostRate);
119
- };
120
- const marginalFirstRolloutSavings = firstRolloutSavings(combined) - firstRolloutSavings(current);
121
- const marginalSteadyRolloutSavings = Math.max(0, combined.reclaimedTokens - current.reclaimedTokens) *
122
- CONTEXT_GC_POLICY.cacheReadCostRate;
123
- for (let rollouts = 1; rollouts <= CONTEXT_GC_POLICY.immediatePaybackRollouts; rollouts++) {
124
- const net = -marginalPenalty +
125
- marginalFirstRolloutSavings +
126
- Math.max(0, rollouts - 1) * marginalSteadyRolloutSavings;
127
- if (net > 0 && net / marginalPenalty >= CONTEXT_GC_POLICY.minimumCacheInvestmentRoi)
128
- return true;
129
- }
130
- return false;
131
- }
132
110
  export function contextGcImminentCompactAvoidanceCredit(input) {
133
111
  if (input.contextUsageTokens >= input.compactThresholdTokens ||
134
112
  input.contextGrowthP75Tokens <= 0 ||
@@ -1,4 +1,6 @@
1
1
  import { openAiInput } from '../../model/openai-responses.js';
2
+ import { anthropicMessages } from '../../model/anthropic-messages.js';
3
+ import { projectCodeModeSearchResult } from '../../session/code-mode-search-projection.js';
2
4
  import { countModelTokens } from '../token-budget.js';
3
5
  import { observationModelParts, observationModelText } from '../../session/code-observation-projection.js';
4
6
  export { MODEL_TOKEN_ESTIMATOR as CONTEXT_GC_TOKEN_ESTIMATOR } from '../token-budget.js';
@@ -23,6 +25,8 @@ function serializedEntries(input, messages) {
23
25
  }))
24
26
  }
25
27
  : message);
28
+ const codeMode = input.tools.some((tool) => tool.name === 'code_exec');
29
+ messages = messages.map((message) => projectCodeModeSearchResult(message, codeMode));
26
30
  const responses = input.modelProtocol === 'OPENAI_RESPONSES' ||
27
31
  (input.modelProtocol === undefined &&
28
32
  (input.messages ?? messages).some((message) => message.provider === 'OPENAI_RESPONSES'));
@@ -31,14 +35,20 @@ function serializedEntries(input, messages) {
31
35
  const contentParts = message.role === 'tool' ? observationModelParts(message) : undefined;
32
36
  return contentParts === undefined ? message : { ...message, contentParts };
33
37
  }), undefined, input.modelId, true, input.responsesConfigurationUpdates !== false)
34
- : messages
35
- .filter((message) => message.role !== 'provider' ||
36
- (message.provider === 'ANTHROPIC_MESSAGES' && message.providerModelId === input.modelId))
37
- .map((original) => {
38
- const { observations: _observations, observationId: _observationId, ...message } = original;
39
- const content = original.role === 'tool' ? observationModelText(original) : undefined;
40
- return content === undefined ? message : { ...message, content };
41
- });
38
+ : input.modelProtocol === 'ANTHROPIC_MESSAGES'
39
+ ? anthropicMessages(messages.map((message) => {
40
+ const contentParts = message.role === 'tool' ? observationModelParts(message) : undefined;
41
+ return contentParts === undefined ? message : { ...message, contentParts };
42
+ }), undefined, input.tools, input.modelId, false)
43
+ : messages
44
+ .filter((message) => message.role !== 'provider' ||
45
+ (message.provider === 'ANTHROPIC_MESSAGES' &&
46
+ message.providerModelId === input.modelId))
47
+ .map((original) => {
48
+ const { observations: _observations, observationId: _observationId, toolSearchTools: _toolSearchTools, ...message } = original;
49
+ const content = original.role === 'tool' ? observationModelText(original) : undefined;
50
+ return content === undefined ? message : { ...message, content };
51
+ });
42
52
  return values.map((value, index) => {
43
53
  const item = value;
44
54
  const callId = item.call_id ?? item.callId;
@@ -1,7 +1,5 @@
1
- import { type ContextGcReplacement } from '../../session/context-gc-checkpoint.js';
2
1
  import { type ContextGcPlan, type ContextGcInspection, type ContextGcPlannerInput } from './gc-contracts.js';
3
- export { CONTEXT_GC_POLICY, type ContextGcEconomicsResult, evaluateContextGcEconomics, contextGcMinimumGrossReductionRate, contextGcMinimumReclaimedTokens, contextGcEligibleUsage, contextGcMarginalCacheInvestmentPaysBack, contextGcImminentCompactAvoidanceCredit, contextGcAppliedCompactCreditCostUnits } from './gc-economics.js';
2
+ export { CONTEXT_GC_POLICY, type ContextGcEconomicsResult, evaluateContextGcEconomics, contextGcMinimumGrossReductionRate, contextGcMinimumReclaimedTokens, contextGcEligibleUsage, contextGcImminentCompactAvoidanceCredit, contextGcAppliedCompactCreditCostUnits } from './gc-economics.js';
4
3
  export { type ContextGcPlan, type ContextGcInspection, type ContextGcPlannerInput } from './gc-contracts.js';
5
4
  export declare function planContextGc(input: ContextGcPlannerInput): ContextGcPlan | undefined;
6
5
  export declare function inspectContextGc(input: ContextGcPlannerInput): ContextGcInspection;
7
- export declare function contextGcEarliestReplacementSequence(replacements: readonly ContextGcReplacement[]): number;
@@ -1,13 +1,14 @@
1
1
  import { ContextGcCheckpointCache, contextGcBaseFromRecords, contextGcCheckpointIntervalEligible, contextGcMinimumUsageGrowth, contextGcTargetKey, normalizeContextGcReplacements } from '../../session/context-gc-checkpoint.js';
2
+ import { buildContextGcProtection } from '../../session/context-gc-protection.js';
2
3
  import { CONTEXT_GC_TOKEN_ESTIMATOR, contextGcCacheImpact, contextGcMessageTokens, contextGcPrefixTokens, contextGcReclaimedTokens } from './gc-tokens.js';
3
4
  import { applyContextGcReplacementsToMessages, providerToolCallId } from './projection.js';
4
- import { CONTEXT_GC_POLICY, COMPACT_CREDIT_ACTIVATION_RATIO, COMPACT_THRESHOLD_RATIO, evaluateContextGcEconomics, contextGcMinimumGrossReductionRate, contextGcMinimumReclaimedTokens, contextGcEligibleUsage, contextGcMarginalCacheInvestmentPaysBack, contextGcImminentCompactAvoidanceCredit, contextGcAppliedCompactCreditCostUnits, modelContextUsage, clamp } from './gc-economics.js';
5
+ import { CONTEXT_GC_POLICY, COMPACT_CREDIT_ACTIVATION_RATIO, COMPACT_THRESHOLD_RATIO, evaluateContextGcEconomics, contextGcMinimumGrossReductionRate, contextGcMinimumReclaimedTokens, contextGcEligibleUsage, contextGcImminentCompactAvoidanceCredit, contextGcAppliedCompactCreditCostUnits, modelContextUsage, clamp } from './gc-economics.js';
5
6
  import { buildToolGroups, countSuccessfulNormalRollouts, isRecord } from './gc-candidates.js';
6
7
  import { collectCodeModeCandidates } from './gc-candidates-code.js';
7
- import { collectStageACandidates } from './gc-candidates-read-patch.js';
8
+ import { collectDeterministicCandidates } from './gc-candidates-read-patch.js';
8
9
  import { collectExecCandidates } from './gc-candidates-exec.js';
9
10
  import { collectToolOutputCandidates, collectWebCandidates } from './gc-candidates-web.js';
10
- export { CONTEXT_GC_POLICY, evaluateContextGcEconomics, contextGcMinimumGrossReductionRate, contextGcMinimumReclaimedTokens, contextGcEligibleUsage, contextGcMarginalCacheInvestmentPaysBack, contextGcImminentCompactAvoidanceCredit, contextGcAppliedCompactCreditCostUnits } from './gc-economics.js';
11
+ export { CONTEXT_GC_POLICY, evaluateContextGcEconomics, contextGcMinimumGrossReductionRate, contextGcMinimumReclaimedTokens, contextGcEligibleUsage, contextGcImminentCompactAvoidanceCredit, contextGcAppliedCompactCreditCostUnits } from './gc-economics.js';
11
12
  export function planContextGc(input) {
12
13
  return inspectContextGc(input).plan;
13
14
  }
@@ -69,7 +70,8 @@ function inspectTextGc(input) {
69
70
  input = {
70
71
  ...input,
71
72
  tokenCache: new Map(),
72
- checkpointCache: input.checkpointCache ?? new ContextGcCheckpointCache()
73
+ checkpointCache: input.checkpointCache ?? new ContextGcCheckpointCache(),
74
+ protection: buildContextGcProtection(input.records, input.modelId, input.contextWindowTokens)
73
75
  };
74
76
  const usageRatio = contextUsageTokens / input.contextWindowTokens;
75
77
  const usageRecord = latestUsageRecord(input.records, input.modelId);
@@ -108,85 +110,42 @@ function inspectTextGc(input) {
108
110
  ? replacement.providerAliasTargets.map(contextGcTargetKey)
109
111
  : [])
110
112
  ]));
111
- const stageA = collectStageACandidates(input, groups, existing, existingTargets, candidateSavings);
112
- const deterministicCandidates = stageA.eligible;
113
- const deterministicProtectedTokens = stageA.protectedTokens;
114
- const stageAPlan = evaluateCandidateSet(input, existing, deterministicCandidates, {
115
- stage: 'STAGE_A',
116
- contextUsageTokens,
117
- usageRatio,
118
- usageRecord,
119
- normalRolloutOrdinal,
120
- protectedCandidateTokens: deterministicProtectedTokens
121
- });
122
- if (stageAPlan?.economics.passes) {
123
- const exec = collectExecCandidates(input, groups, existingTargets, candidateSavings);
124
- const codeMode = collectCodeModeCandidates(input, groups, existingTargets, candidateSavings);
125
- const web = collectWebCandidates(input, groups, existingTargets, candidateSavings);
126
- const tools = collectToolOutputCandidates(input, groups, existingTargets, candidateSavings);
127
- const protectedCandidateTokens = deterministicProtectedTokens +
128
- exec.protectedTokens +
129
- codeMode.protectedTokens +
130
- web.protectedTokens +
131
- tools.protectedTokens;
132
- const stageAPlanWithProtectedCandidates = evaluateCandidateSet(input, existing, deterministicCandidates, {
133
- stage: 'STAGE_A',
134
- contextUsageTokens,
135
- usageRatio,
136
- usageRecord,
137
- normalRolloutOrdinal,
138
- protectedCandidateTokens
139
- }) ?? stageAPlan;
140
- const marginal = mergeMarginalLargeOutputCandidates(input, existing, deterministicCandidates, [...exec.eligible, ...codeMode.eligible, ...web.eligible, ...tools.eligible], stageAPlanWithProtectedCandidates, {
141
- contextUsageTokens,
142
- usageRatio,
143
- usageRecord,
144
- normalRolloutOrdinal,
145
- protectedCandidateTokens
146
- });
147
- const plan = checkpointEligible
148
- ? finalizePlan(input, marginal ?? stageAPlanWithProtectedCandidates)
149
- : undefined;
150
- const retryAfterRollouts = Math.min(exec.retryAfterRollouts, codeMode.retryAfterRollouts, web.retryAfterRollouts, tools.retryAfterRollouts);
151
- if (!plan && checkpointEligible)
152
- return {};
153
- return {
154
- ...(plan ? { plan } : {}),
155
- ...(protectedCandidateTokens > 0 && Number.isFinite(retryAfterRollouts)
156
- ? { retryAfterRollouts }
157
- : {})
158
- };
159
- }
113
+ const deterministic = collectDeterministicCandidates(input, groups, existing, existingTargets, candidateSavings);
160
114
  const exec = collectExecCandidates(input, groups, existingTargets, candidateSavings);
161
115
  const codeMode = collectCodeModeCandidates(input, groups, existingTargets, candidateSavings);
162
116
  const web = collectWebCandidates(input, groups, existingTargets, candidateSavings);
163
117
  const tools = collectToolOutputCandidates(input, groups, existingTargets, candidateSavings);
164
- const protectedCandidateTokens = deterministicProtectedTokens +
118
+ const protectedCandidateTokens = deterministic.protectedTokens +
165
119
  exec.protectedTokens +
166
120
  codeMode.protectedTokens +
167
121
  web.protectedTokens +
168
122
  tools.protectedTokens;
169
123
  const combined = evaluateCandidateSet(input, existing, [
170
- ...deterministicCandidates,
124
+ ...deterministic.eligible,
171
125
  ...exec.eligible,
172
126
  ...codeMode.eligible,
173
127
  ...web.eligible,
174
128
  ...tools.eligible
175
129
  ], {
176
- stage: 'STAGE_A_AND_EXEC',
130
+ stage: 'UNIFIED',
177
131
  contextUsageTokens,
178
132
  usageRatio,
179
133
  usageRecord,
180
134
  normalRolloutOrdinal,
181
135
  protectedCandidateTokens
182
136
  });
137
+ const retryAfterRollouts = Math.min(deterministic.retryAfterRollouts, exec.retryAfterRollouts, codeMode.retryAfterRollouts, web.retryAfterRollouts, tools.retryAfterRollouts);
183
138
  if (combined?.economics.passes && checkpointEligible) {
184
139
  const plan = finalizePlan(input, combined);
185
- return plan ? { plan } : {};
140
+ return plan
141
+ ? {
142
+ plan,
143
+ ...(protectedCandidateTokens > 0 && Number.isFinite(retryAfterRollouts)
144
+ ? { retryAfterRollouts }
145
+ : {})
146
+ }
147
+ : {};
186
148
  }
187
- const retryAfterRollouts = Math.min(stageA.retryAfterRollouts, exec.retryAfterRollouts, codeMode.retryAfterRollouts, web.retryAfterRollouts, tools.retryAfterRollouts);
188
- // A worthwhile mixed set retains every positive protected candidate's retry
189
- // hint even when the checkpoint interval prevents a durable draft.
190
149
  return protectedCandidateTokens > 0 &&
191
150
  (combined?.economics.passes ||
192
151
  protectedCandidateTokens >=
@@ -195,42 +154,6 @@ function inspectTextGc(input) {
195
154
  ? { retryAfterRollouts }
196
155
  : {};
197
156
  }
198
- function mergeMarginalLargeOutputCandidates(input, existing, deterministicCandidates, execCandidates, stageAPlan, state) {
199
- let selected = [...deterministicCandidates];
200
- let current = stageAPlan;
201
- const stageAEarliestSequence = contextGcEarliestReplacementSequence(deterministicCandidates.flatMap((candidate) => candidate.replacements));
202
- for (const candidate of [...execCandidates].sort((left, right) => right.evidenceSequence - left.evidenceSequence)) {
203
- const evaluated = evaluateCandidateSet(input, existing, [...selected, candidate], {
204
- stage: 'STAGE_A_AND_EXEC',
205
- ...state
206
- });
207
- if (!evaluated?.economics.passes || evaluated.reclaimedTokens <= current.reclaimedTokens)
208
- continue;
209
- const doesNotMoveInvalidationEarlier = contextGcEarliestReplacementSequence(candidate.replacements) >= stageAEarliestSequence;
210
- if (!doesNotMoveInvalidationEarlier &&
211
- !contextGcMarginalCacheInvestmentPaysBack({
212
- reclaimedTokens: current.reclaimedTokens,
213
- reclaimedCachedTokens: current.reclaimedCachedTokens,
214
- cacheRebuildPenaltyCostUnits: current.economics.cacheRebuildPenaltyCostUnits
215
- }, {
216
- reclaimedTokens: evaluated.reclaimedTokens,
217
- reclaimedCachedTokens: evaluated.reclaimedCachedTokens,
218
- cacheRebuildPenaltyCostUnits: evaluated.economics.cacheRebuildPenaltyCostUnits
219
- }))
220
- continue;
221
- selected = [...selected, candidate];
222
- current = evaluated;
223
- }
224
- return current === stageAPlan ? undefined : current;
225
- }
226
- export function contextGcEarliestReplacementSequence(replacements) {
227
- return Math.min(...replacements.flatMap((replacement) => [
228
- replacement.target.sequence,
229
- ...(replacement.kind === 'TOOL_CALL_INPUT'
230
- ? replacement.providerAliasTargets.map((target) => target.sequence)
231
- : [])
232
- ]));
233
- }
234
157
  function finalizePlan(input, evaluated, includeMedia = false) {
235
158
  if (!evaluated)
236
159
  return undefined;
@@ -105,7 +105,17 @@ export function applyContextGcReplacementsToMessages(messages, messageSources, r
105
105
  replacement.kind === 'SUBAGENT_OUTPUT' ||
106
106
  replacement.kind === 'WEB_OUTPUT') &&
107
107
  next.role === 'tool')
108
- next = { ...next, content: replacement.replacementContent };
108
+ next = {
109
+ ...next,
110
+ content: replacement.replacementContent,
111
+ ...(replacement.kind === 'TOOL_OUTPUT' &&
112
+ replacement.retainedToolNames !== undefined &&
113
+ next.toolSearchTools !== undefined
114
+ ? {
115
+ toolSearchTools: next.toolSearchTools.filter((tool) => replacement.retainedToolNames.includes(tool.name))
116
+ }
117
+ : {})
118
+ };
109
119
  }
110
120
  if (next.textBlocks !== undefined && next.content !== message.content)
111
121
  next = {
@@ -0,0 +1,5 @@
1
+ import type { ClientToolDefinition } from '../tools/contracts.js';
2
+ import type { SessionModelMessage as ModelMessage } from './model-request.js';
3
+ export declare function projectProgrammaticTool(tool: ClientToolDefinition): Pick<ClientToolDefinition, 'name' | 'description' | 'inputSchema'>;
4
+ /** Preserve native search definitions while providing the same discovery evidence to other protocols. */
5
+ export declare function projectCodeModeSearchResult(message: ModelMessage, codeModeEnabled: boolean): ModelMessage;
@@ -0,0 +1,46 @@
1
+ import { stableToolDefinition, stableToolSchema } from '../model/tool-definition-projection.js';
2
+ import { allowsDirectTool, allowsToolCaller } from './tool-callers.js';
3
+ export function projectProgrammaticTool(tool) {
4
+ return {
5
+ name: tool.name,
6
+ description: `${allowsDirectTool(tool, true) ? `Call directly or as tools.${tool.name} inside code_exec.` : `Call only as tools.${tool.name} inside code_exec, never as a direct model tool.`}\n${tool.description}`,
7
+ inputSchema: stableToolSchema(tool.inputSchema)
8
+ };
9
+ }
10
+ /** Preserve native search definitions while providing the same discovery evidence to other protocols. */
11
+ export function projectCodeModeSearchResult(message, codeModeEnabled) {
12
+ if (!message.toolSearchTools)
13
+ return message;
14
+ if (!codeModeEnabled)
15
+ return {
16
+ ...message,
17
+ toolSearchTools: message.toolSearchTools
18
+ .filter((tool) => allowsToolCaller(tool, 'direct'))
19
+ .map(stableToolDefinition)
20
+ };
21
+ const tools = message.toolSearchTools.map((tool) => ({
22
+ ...(allowsToolCaller(tool, 'programmatic')
23
+ ? {
24
+ ...projectProgrammaticTool(tool),
25
+ ...(allowsDirectTool(tool, true)
26
+ ? { inputMode: tool.inputMode ?? 'json' }
27
+ : {})
28
+ }
29
+ : {
30
+ name: tool.name,
31
+ description: tool.description,
32
+ inputSchema: stableToolSchema(tool.inputSchema),
33
+ inputMode: tool.inputMode ?? 'json'
34
+ }),
35
+ caller: allowsToolCaller(tool, 'programmatic')
36
+ ? allowsDirectTool(tool, true)
37
+ ? 'both'
38
+ : 'programmatic'
39
+ : 'direct'
40
+ }));
41
+ return {
42
+ ...message,
43
+ content: JSON.stringify({ tools }),
44
+ toolSearchTools: tools.map(({ caller: _caller, ...tool }) => tool)
45
+ };
46
+ }