@myagentroam/agent 0.9.62 → 0.9.64
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/LICENSE +202 -0
- package/NOTICE +4 -0
- package/dist/cli/codex-auth-file.js +268 -3
- package/dist/cli/main.js +427 -2
- package/dist/error.js +66 -2
- package/dist/host/contracts.js +20 -2
- package/dist/host/local-agent-host.js +146 -2
- package/dist/host/local-process-executor.js +39 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +12 -2
- package/dist/model/anthropic-messages.js +474 -2
- package/dist/model/anthropic-thinking.js +33 -2
- package/dist/model/catalog-file.js +88 -2
- package/dist/model/configuration.js +137 -2
- package/dist/model/contracts.d.ts +2 -0
- package/dist/model/contracts.js +1 -2
- package/dist/model/http.js +529 -2
- package/dist/model/openai-responses.d.ts +2 -0
- package/dist/model/openai-responses.js +1602 -2
- package/dist/model/runtime-credential.js +35 -2
- package/dist/model/sse.js +199 -2
- package/dist/prompts/compact.js +25 -2
- package/dist/prompts/core.js +37 -2
- package/dist/prompts/execution-budget.js +10 -2
- package/dist/prompts/index.d.ts +3 -2
- package/dist/prompts/index.js +47 -2
- package/dist/prompts/modes.js +25 -2
- package/dist/prompts/output.js +1 -2
- package/dist/prompts/resources.d.ts +1 -1
- package/dist/prompts/resources.js +31 -2
- package/dist/prompts/subagent.d.ts +6 -0
- package/dist/prompts/subagent.js +29 -2
- package/dist/prompts/title.js +9 -2
- package/dist/prompts/workflow.js +32 -2
- package/dist/runtime/async-queue.js +62 -2
- package/dist/runtime/builtin-skills.js +19 -2
- package/dist/runtime/byted-ark-imagegen-skill.js +18 -2
- package/dist/runtime/codex-imagegen-skill.js +35 -2
- package/dist/runtime/compact.js +225 -2
- package/dist/runtime/context-gc-runtime.d.ts +35 -0
- package/dist/runtime/context-gc-runtime.js +163 -0
- package/dist/runtime/context-gc-tokens.d.ts +24 -0
- package/dist/runtime/context-gc-tokens.js +83 -0
- package/dist/runtime/context-gc.d.ts +66 -0
- package/dist/runtime/context-gc.js +909 -0
- package/dist/runtime/context-projection.d.ts +23 -0
- package/dist/runtime/context-projection.js +203 -0
- package/dist/runtime/environment-context.js +41 -2
- package/dist/runtime/execution-policy.js +13 -2
- package/dist/runtime/instructions.js +298 -2
- package/dist/runtime/normalize.js +40 -2
- package/dist/runtime/plan-output.js +95 -2
- package/dist/runtime/rollout-budget.d.ts +2 -0
- package/dist/runtime/rollout-budget.js +92 -2
- package/dist/runtime/token-budget.d.ts +2 -0
- package/dist/runtime/token-budget.js +44 -2
- package/dist/sdk/agent.js +1804 -2
- package/dist/sdk/types.d.ts +15 -5
- package/dist/sdk/types.js +1 -2
- package/dist/session/catalog.d.ts +5 -4
- package/dist/session/catalog.js +133 -2
- package/dist/session/context-gc-checkpoint.d.ts +148 -0
- package/dist/session/context-gc-checkpoint.js +665 -0
- package/dist/session/context-gc-replacements.d.ts +52 -0
- package/dist/session/context-gc-replacements.js +226 -0
- package/dist/session/context-gc-subagent.d.ts +43 -0
- package/dist/session/context-gc-subagent.js +267 -0
- package/dist/session/jsonl-store.d.ts +42 -4
- package/dist/session/jsonl-store.js +1657 -2
- package/dist/subagent/scheduler.d.ts +35 -2
- package/dist/subagent/scheduler.js +249 -2
- package/dist/subagent/session-controller.d.ts +94 -0
- package/dist/subagent/session-controller.js +675 -0
- package/dist/tools/agent-cancel.js +28 -2
- package/dist/tools/{task.d.ts → agent-message.d.ts} +2 -2
- package/dist/tools/agent-message.js +51 -0
- package/dist/tools/agent-start.js +84 -2
- package/dist/tools/agent-wait.js +53 -2
- package/dist/tools/apply-patch.js +478 -2
- package/dist/tools/code-mode.js +370 -2
- package/dist/tools/codex-image-gen.js +312 -2
- package/dist/tools/exec.js +455 -2
- package/dist/tools/execution-limits.d.ts +1 -0
- package/dist/tools/execution-limits.js +44 -2
- package/dist/tools/local-registry.js +233 -2
- package/dist/tools/local-workspace-tools.js +28 -2
- package/dist/tools/question.js +124 -2
- package/dist/tools/read-context.d.ts +16 -0
- package/dist/tools/read-context.js +85 -0
- package/dist/tools/read.d.ts +13 -0
- package/dist/tools/read.js +294 -2
- package/dist/tools/registry.js +76 -2
- package/dist/tools/runtime-context.d.ts +9 -2
- package/dist/tools/runtime-context.js +1 -2
- package/dist/tools/runtime.js +21 -2
- package/dist/tools/scheduler.js +41 -2
- package/dist/tools/shared/path-resolver.js +133 -2
- package/dist/tools/skill.js +37 -2
- package/dist/tools/todo-read.js +10 -2
- package/dist/tools/todo-write.js +65 -2
- package/dist/tools/todo.js +67 -2
- package/dist/tools/view-image.js +81 -2
- package/dist/tools/web-fetch.js +11 -2
- package/dist/tools/web-search.js +14 -2
- package/package.json +9 -7
- package/README.md +0 -3
- package/dist/runtime/prompt.d.ts +0 -9
- package/dist/runtime/prompt.js +0 -2
- package/dist/tools/shared/secret-redaction.d.ts +0 -4
- package/dist/tools/shared/secret-redaction.js +0 -2
- package/dist/tools/task-cancel.d.ts +0 -7
- package/dist/tools/task-cancel.js +0 -2
- package/dist/tools/task-output.d.ts +0 -7
- package/dist/tools/task-output.js +0 -2
- package/dist/tools/task-tools.d.ts +0 -36
- package/dist/tools/task-tools.js +0 -2
- package/dist/tools/task.js +0 -2
- package/dist/tools/todo-tools.d.ts +0 -25
- package/dist/tools/todo-tools.js +0 -2
|
@@ -0,0 +1,909 @@
|
|
|
1
|
+
import { ContextGcCheckpointCache, contextGcBaseFromRecords, contextGcLogicalPathKey, contextGcMinimumUsageGrowth, contextGcRecordSourceHash, contextGcTargetKey, normalizeContextGcReplacements } from '../session/context-gc-checkpoint.js';
|
|
2
|
+
import { contextGcApplyPatchInputReplacement, contextGcExecInputReplacement, contextGcExecOutputReplacement, contextGcPatchResultFromCompletedPayload, contextGcStaleReadReplacement, normalizeContextGcExecResult } from '../session/context-gc-replacements.js';
|
|
3
|
+
import { contextGcSubagentNeedsRetainedPriorFinal, contextGcSubagentOutputReplacement, contextGcSubagentPriorFinalIsRetained, contextGcSubagentSnapshotIsSuperseded, contextGcSubagentSnapshotTurnId, normalizeContextGcSubagentSnapshot } from '../session/context-gc-subagent.js';
|
|
4
|
+
import { parseReadContextOutput } from '../tools/read-context.js';
|
|
5
|
+
import { CONTEXT_GC_TOKEN_ESTIMATOR, contextGcCacheImpact, contextGcMessageTokens, contextGcPrefixTokens, contextGcReclaimedTokens } from './context-gc-tokens.js';
|
|
6
|
+
import { applyContextGcReplacementsToMessages, providerToolCallId, providerToolName } from './context-projection.js';
|
|
7
|
+
export const CONTEXT_GC_POLICY = {
|
|
8
|
+
version: 1,
|
|
9
|
+
immediatePaybackRollouts: 3,
|
|
10
|
+
maximumPaybackRollouts: 20,
|
|
11
|
+
contextGrowthSampleWindow: 16,
|
|
12
|
+
minimumContextGrowthSamples: 6,
|
|
13
|
+
minimumObservationRollouts: 1,
|
|
14
|
+
stageAProtectionRollouts: 2,
|
|
15
|
+
largeExecProtectionRollouts: 6,
|
|
16
|
+
normalInputCostRate: 1,
|
|
17
|
+
cacheReadCostRate: 0.1,
|
|
18
|
+
cacheWriteCostRate: 1.25,
|
|
19
|
+
outputCostRate: 1,
|
|
20
|
+
minimumGrossReductionRate: 0.2,
|
|
21
|
+
minimumCacheInvestmentRoi: 0.1
|
|
22
|
+
};
|
|
23
|
+
const CONTEXT_GC_START_RATIO = 0.5;
|
|
24
|
+
const HIGH_WATER_GROSS_REDUCTION_START_RATIO = 0.7;
|
|
25
|
+
const HIGH_WATER_MINIMUM_GROSS_REDUCTION_RATE = 0.1;
|
|
26
|
+
const MINIMUM_CONTEXT_WINDOW_REDUCTION_RATIO = 0.05;
|
|
27
|
+
const COMPACT_CREDIT_ACTIVATION_RATIO = 0.8;
|
|
28
|
+
const COMPACT_THRESHOLD_RATIO = 0.9;
|
|
29
|
+
const IMMINENT_COMPACT_CREDIT_DISCOUNT = 0.8;
|
|
30
|
+
const LARGE_EXEC_TOKENS = 4_096;
|
|
31
|
+
export function evaluateContextGcEconomics(input) {
|
|
32
|
+
const reclaimedTokens = Math.max(0, input.reclaimedTokens);
|
|
33
|
+
const cacheReadTokens = Math.max(0, input.cacheReadTokens);
|
|
34
|
+
const preservedCachedPrefixTokens = clamp(input.preservedCachedPrefixTokens, 0, cacheReadTokens);
|
|
35
|
+
const invalidatedCachedSuffixTokens = cacheReadTokens - preservedCachedPrefixTokens;
|
|
36
|
+
const reclaimedCachedTokens = clamp(input.reclaimedCachedTokens, 0, invalidatedCachedSuffixTokens);
|
|
37
|
+
const invalidatedRetainedCachedTokens = Math.max(0, invalidatedCachedSuffixTokens - reclaimedCachedTokens);
|
|
38
|
+
const cacheRebuildPenaltyCostUnits = invalidatedRetainedCachedTokens *
|
|
39
|
+
(CONTEXT_GC_POLICY.cacheWriteCostRate - CONTEXT_GC_POLICY.cacheReadCostRate);
|
|
40
|
+
const reclaimedUncachedTokens = Math.max(0, reclaimedTokens - reclaimedCachedTokens);
|
|
41
|
+
const firstRolloutSavingsCostUnits = reclaimedCachedTokens * CONTEXT_GC_POLICY.cacheReadCostRate +
|
|
42
|
+
reclaimedUncachedTokens * CONTEXT_GC_POLICY.normalInputCostRate;
|
|
43
|
+
const steadyRolloutSavingsCostUnits = reclaimedTokens * CONTEXT_GC_POLICY.cacheReadCostRate;
|
|
44
|
+
const netAt = (rollouts) => input.compactDeferralCreditCostUnits -
|
|
45
|
+
cacheRebuildPenaltyCostUnits +
|
|
46
|
+
(rollouts <= 0
|
|
47
|
+
? 0
|
|
48
|
+
: firstRolloutSavingsCostUnits + Math.max(0, rollouts - 1) * steadyRolloutSavingsCostUnits);
|
|
49
|
+
const maximumSearch = Math.max(CONTEXT_GC_POLICY.maximumPaybackRollouts + 1, 10_000);
|
|
50
|
+
const requiredPositiveNetRollouts = firstMatchingRollout(maximumSearch, (rollouts) => netAt(rollouts) > 0);
|
|
51
|
+
const requiredTargetRoiRollouts = cacheRebuildPenaltyCostUnits === 0
|
|
52
|
+
? requiredPositiveNetRollouts
|
|
53
|
+
: firstMatchingRollout(maximumSearch, (rollouts) => netAt(rollouts) > 0 &&
|
|
54
|
+
netAt(rollouts) / cacheRebuildPenaltyCostUnits >=
|
|
55
|
+
CONTEXT_GC_POLICY.minimumCacheInvestmentRoi);
|
|
56
|
+
const targetRollouts = Number.isFinite(requiredTargetRoiRollouts)
|
|
57
|
+
? requiredTargetRoiRollouts
|
|
58
|
+
: CONTEXT_GC_POLICY.maximumPaybackRollouts;
|
|
59
|
+
const expectedNetSavedCostUnits = netAt(targetRollouts);
|
|
60
|
+
const cacheInvestmentRoi = cacheRebuildPenaltyCostUnits === 0
|
|
61
|
+
? 0
|
|
62
|
+
: expectedNetSavedCostUnits / cacheRebuildPenaltyCostUnits;
|
|
63
|
+
const grossReductionRate = reclaimedTokens / Math.max(1, Math.max(0, input.contextUsageTokens));
|
|
64
|
+
const minimumGrossReductionRate = Math.max(0, input.minimumGrossReductionRate ?? CONTEXT_GC_POLICY.minimumGrossReductionRate);
|
|
65
|
+
const minimumReclaimedTokens = Math.max(0, input.minimumReclaimedTokens ??
|
|
66
|
+
Math.ceil(Math.max(0, input.contextUsageTokens) * minimumGrossReductionRate));
|
|
67
|
+
const immediate = requiredTargetRoiRollouts <= CONTEXT_GC_POLICY.immediatePaybackRollouts;
|
|
68
|
+
const passes = reclaimedTokens > 0 &&
|
|
69
|
+
reclaimedTokens >= minimumReclaimedTokens &&
|
|
70
|
+
Number.isFinite(requiredTargetRoiRollouts) &&
|
|
71
|
+
(immediate ||
|
|
72
|
+
(requiredTargetRoiRollouts <= CONTEXT_GC_POLICY.maximumPaybackRollouts &&
|
|
73
|
+
input.contextGrowthSampleCount >= CONTEXT_GC_POLICY.minimumContextGrowthSamples &&
|
|
74
|
+
requiredTargetRoiRollouts <= input.projectedRolloutsUntilCompactAfterGc));
|
|
75
|
+
return {
|
|
76
|
+
cacheRebuildPenaltyCostUnits,
|
|
77
|
+
requiredPositiveNetRollouts,
|
|
78
|
+
requiredTargetRoiRollouts,
|
|
79
|
+
expectedNetSavedCostUnits,
|
|
80
|
+
cacheInvestmentRoi,
|
|
81
|
+
grossReductionRate,
|
|
82
|
+
passes
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function contextGcMinimumGrossReductionRate(usageRatio) {
|
|
86
|
+
if (!Number.isFinite(usageRatio) || usageRatio <= HIGH_WATER_GROSS_REDUCTION_START_RATIO)
|
|
87
|
+
return CONTEXT_GC_POLICY.minimumGrossReductionRate;
|
|
88
|
+
if (usageRatio >= COMPACT_THRESHOLD_RATIO)
|
|
89
|
+
return HIGH_WATER_MINIMUM_GROSS_REDUCTION_RATE;
|
|
90
|
+
const progress = (usageRatio - HIGH_WATER_GROSS_REDUCTION_START_RATIO) /
|
|
91
|
+
(COMPACT_THRESHOLD_RATIO - HIGH_WATER_GROSS_REDUCTION_START_RATIO);
|
|
92
|
+
return (CONTEXT_GC_POLICY.minimumGrossReductionRate -
|
|
93
|
+
progress *
|
|
94
|
+
(CONTEXT_GC_POLICY.minimumGrossReductionRate - HIGH_WATER_MINIMUM_GROSS_REDUCTION_RATE));
|
|
95
|
+
}
|
|
96
|
+
export function contextGcMinimumReclaimedTokens(contextUsageTokens, contextWindowTokens) {
|
|
97
|
+
if (!Number.isFinite(contextUsageTokens) ||
|
|
98
|
+
contextUsageTokens < 0 ||
|
|
99
|
+
!Number.isFinite(contextWindowTokens) ||
|
|
100
|
+
contextWindowTokens <= 0)
|
|
101
|
+
return 0;
|
|
102
|
+
const usageRatio = contextUsageTokens / contextWindowTokens;
|
|
103
|
+
const required = Math.max(contextUsageTokens * contextGcMinimumGrossReductionRate(usageRatio), contextWindowTokens * MINIMUM_CONTEXT_WINDOW_REDUCTION_RATIO);
|
|
104
|
+
return Math.ceil(required - 1e-9);
|
|
105
|
+
}
|
|
106
|
+
export function contextGcEligibleUsage(usage, contextWindowTokens) {
|
|
107
|
+
const contextUsageTokens = modelContextUsage(usage);
|
|
108
|
+
if (contextUsageTokens === undefined ||
|
|
109
|
+
!Number.isSafeInteger(contextWindowTokens) ||
|
|
110
|
+
contextWindowTokens <= 0)
|
|
111
|
+
return undefined;
|
|
112
|
+
const usageRatio = contextUsageTokens / contextWindowTokens;
|
|
113
|
+
if (usageRatio < CONTEXT_GC_START_RATIO || usageRatio >= COMPACT_THRESHOLD_RATIO)
|
|
114
|
+
return undefined;
|
|
115
|
+
return contextUsageTokens;
|
|
116
|
+
}
|
|
117
|
+
export function planContextGc(input) {
|
|
118
|
+
return inspectContextGc(input).plan;
|
|
119
|
+
}
|
|
120
|
+
export function inspectContextGc(input) {
|
|
121
|
+
const contextUsageTokens = contextGcEligibleUsage(input.usage, input.contextWindowTokens);
|
|
122
|
+
if (contextUsageTokens === undefined)
|
|
123
|
+
return {};
|
|
124
|
+
input = {
|
|
125
|
+
...input,
|
|
126
|
+
tokenCache: new Map(),
|
|
127
|
+
checkpointCache: input.checkpointCache ?? new ContextGcCheckpointCache()
|
|
128
|
+
};
|
|
129
|
+
const usageRatio = contextUsageTokens / input.contextWindowTokens;
|
|
130
|
+
const usageRecord = latestUsageRecord(input.records, input.modelId);
|
|
131
|
+
if (!usageRecord)
|
|
132
|
+
return {};
|
|
133
|
+
const normalRolloutOrdinal = countSuccessfulNormalRollouts(input.records, input.modelId, input.contextWindowTokens);
|
|
134
|
+
const previous = input.checkpointCache.read(input.records, undefined, input.workspace);
|
|
135
|
+
if (previous) {
|
|
136
|
+
const minimumUsageGrowth = contextGcMinimumUsageGrowth(input.contextWindowTokens);
|
|
137
|
+
if (normalRolloutOrdinal - previous.payload.trigger.normalRolloutOrdinal < 2 ||
|
|
138
|
+
contextUsageTokens - previous.payload.trigger.contextInputTokens < minimumUsageGrowth)
|
|
139
|
+
return {};
|
|
140
|
+
}
|
|
141
|
+
if (input.messages.length !== input.messageSources.length)
|
|
142
|
+
return {};
|
|
143
|
+
const groups = buildToolGroups(input.records, input.messages, input.messageSources);
|
|
144
|
+
const existing = previous?.payload.replacements ?? [];
|
|
145
|
+
const existingTargets = new Set(existing.flatMap((replacement) => [
|
|
146
|
+
contextGcTargetKey(replacement.target),
|
|
147
|
+
...(replacement.kind === 'TOOL_CALL_INPUT'
|
|
148
|
+
? replacement.providerAliasTargets.map(contextGcTargetKey)
|
|
149
|
+
: [])
|
|
150
|
+
]));
|
|
151
|
+
const stageA = collectStageACandidates(input, groups, existing, existingTargets);
|
|
152
|
+
const subagent = collectSubagentCandidates(input, groups, existingTargets);
|
|
153
|
+
const deterministicCandidates = [...stageA.eligible, ...subagent.eligible];
|
|
154
|
+
const deterministicProtectedTokens = stageA.protectedTokens + subagent.protectedTokens;
|
|
155
|
+
const stageAPlan = evaluateCandidateSet(input, existing, deterministicCandidates, {
|
|
156
|
+
stage: 'STAGE_A',
|
|
157
|
+
contextUsageTokens,
|
|
158
|
+
usageRatio,
|
|
159
|
+
usageRecord,
|
|
160
|
+
normalRolloutOrdinal,
|
|
161
|
+
protectedCandidateTokens: deterministicProtectedTokens
|
|
162
|
+
});
|
|
163
|
+
if (stageAPlan?.economics.passes) {
|
|
164
|
+
const plan = finalizePlan(input, stageAPlan);
|
|
165
|
+
return plan ? { plan } : {};
|
|
166
|
+
}
|
|
167
|
+
const exec = collectExecCandidates(input, groups, existingTargets);
|
|
168
|
+
const combined = evaluateCandidateSet(input, existing, [...deterministicCandidates, ...exec.eligible], {
|
|
169
|
+
stage: 'STAGE_A_AND_EXEC',
|
|
170
|
+
contextUsageTokens,
|
|
171
|
+
usageRatio,
|
|
172
|
+
usageRecord,
|
|
173
|
+
normalRolloutOrdinal,
|
|
174
|
+
protectedCandidateTokens: deterministicProtectedTokens + exec.protectedTokens
|
|
175
|
+
});
|
|
176
|
+
if (combined?.economics.passes) {
|
|
177
|
+
const plan = finalizePlan(input, combined);
|
|
178
|
+
return plan ? { plan } : {};
|
|
179
|
+
}
|
|
180
|
+
const retryAfterRollouts = Math.min(stageA.retryAfterRollouts, subagent.retryAfterRollouts, exec.retryAfterRollouts);
|
|
181
|
+
return deterministicProtectedTokens + exec.protectedTokens >=
|
|
182
|
+
contextGcMinimumReclaimedTokens(contextUsageTokens, input.contextWindowTokens) &&
|
|
183
|
+
Number.isFinite(retryAfterRollouts)
|
|
184
|
+
? { retryAfterRollouts }
|
|
185
|
+
: {};
|
|
186
|
+
}
|
|
187
|
+
function finalizePlan(input, evaluated) {
|
|
188
|
+
if (!evaluated)
|
|
189
|
+
return undefined;
|
|
190
|
+
const base = contextGcBaseFromRecords(input.records, evaluated.boundarySequence);
|
|
191
|
+
if (!base)
|
|
192
|
+
return undefined;
|
|
193
|
+
const replacements = normalizeContextGcReplacements(evaluated.replacements);
|
|
194
|
+
if (!replacements || replacements.length === 0)
|
|
195
|
+
return undefined;
|
|
196
|
+
const metrics = {
|
|
197
|
+
stage: evaluated.stage,
|
|
198
|
+
tokenEstimator: CONTEXT_GC_TOKEN_ESTIMATOR,
|
|
199
|
+
beforeTokens: evaluated.beforeTokens,
|
|
200
|
+
afterTokens: evaluated.afterTokens,
|
|
201
|
+
reclaimedTokens: evaluated.reclaimedTokens,
|
|
202
|
+
grossReductionRate: evaluated.economics.grossReductionRate,
|
|
203
|
+
effectiveMinimumGrossReductionRate: evaluated.minimumGrossReductionRate,
|
|
204
|
+
minimumRequiredReclaimedTokens: evaluated.minimumReclaimedTokens,
|
|
205
|
+
cacheRebuildPenaltyCostUnits: evaluated.economics.cacheRebuildPenaltyCostUnits,
|
|
206
|
+
compactDeferralCreditCostUnits: evaluated.compactDeferralCreditCostUnits,
|
|
207
|
+
imminentCompactAvoidanceCreditCostUnits: evaluated.imminentCompactAvoidanceCreditCostUnits,
|
|
208
|
+
appliedCompactCreditCostUnits: evaluated.appliedCompactCreditCostUnits,
|
|
209
|
+
requiredPositiveNetRollouts: evaluated.economics.requiredPositiveNetRollouts,
|
|
210
|
+
requiredTargetRoiRollouts: evaluated.economics.requiredTargetRoiRollouts,
|
|
211
|
+
projectedRolloutsUntilCompactAfterGc: evaluated.projectedRolloutsUntilCompactAfterGc,
|
|
212
|
+
projectedTokensBeforeGc: evaluated.projectedTokensBeforeGc,
|
|
213
|
+
projectedTokensAfterGc: evaluated.projectedTokensAfterGc,
|
|
214
|
+
contextGrowthSampleCount: evaluated.contextGrowthSamples.length,
|
|
215
|
+
contextGrowthP75Tokens: evaluated.contextGrowthP75Tokens,
|
|
216
|
+
expectedNetSavedCostUnits: evaluated.economics.expectedNetSavedCostUnits,
|
|
217
|
+
cacheInvestmentRoi: evaluated.economics.cacheInvestmentRoi,
|
|
218
|
+
minimumObservedRollouts: evaluated.minimumObservedRollouts,
|
|
219
|
+
protectedCandidateTokens: evaluated.protectedCandidateTokens,
|
|
220
|
+
eligibleCandidateCount: evaluated.eligibleCandidateCount,
|
|
221
|
+
eligibleTargetCount: replacements.reduce((count, replacement) => count +
|
|
222
|
+
1 +
|
|
223
|
+
(replacement.kind === 'TOOL_CALL_INPUT' ? replacement.providerAliasTargets.length : 0), 0),
|
|
224
|
+
cachePenaltyKnown: input.usage.cacheReadTokens !== undefined
|
|
225
|
+
};
|
|
226
|
+
const draft = {
|
|
227
|
+
version: 1,
|
|
228
|
+
base,
|
|
229
|
+
boundarySequence: evaluated.boundarySequence,
|
|
230
|
+
sourceProjectionHash: '',
|
|
231
|
+
replacements,
|
|
232
|
+
trigger: {
|
|
233
|
+
usageSequence: evaluated.usageRecord.sequence,
|
|
234
|
+
contextInputTokens: evaluated.contextUsageTokens,
|
|
235
|
+
contextWindowTokens: input.contextWindowTokens,
|
|
236
|
+
normalRolloutOrdinal: evaluated.normalRolloutOrdinal,
|
|
237
|
+
modelId: input.modelId,
|
|
238
|
+
platform: input.platform
|
|
239
|
+
},
|
|
240
|
+
economics: CONTEXT_GC_POLICY,
|
|
241
|
+
metrics
|
|
242
|
+
};
|
|
243
|
+
return { stage: evaluated.stage, draft, metrics };
|
|
244
|
+
}
|
|
245
|
+
function evaluateCandidateSet(input, existing, candidates, state) {
|
|
246
|
+
if (candidates.length === 0)
|
|
247
|
+
return undefined;
|
|
248
|
+
const replacements = normalizeContextGcReplacements([
|
|
249
|
+
...existing,
|
|
250
|
+
...candidates.flatMap((candidate) => candidate.replacements)
|
|
251
|
+
]);
|
|
252
|
+
if (!replacements)
|
|
253
|
+
return undefined;
|
|
254
|
+
const projected = applyContextGcReplacementsToMessages(input.messages, input.messageSources, replacements);
|
|
255
|
+
const reclaimedTokens = contextGcReclaimedTokens(input, input.messages, projected.messages);
|
|
256
|
+
const minimumGrossReductionRate = contextGcMinimumGrossReductionRate(state.usageRatio);
|
|
257
|
+
const minimumReclaimedTokens = contextGcMinimumReclaimedTokens(state.contextUsageTokens, input.contextWindowTokens);
|
|
258
|
+
if (reclaimedTokens < minimumReclaimedTokens)
|
|
259
|
+
return undefined;
|
|
260
|
+
const beforeTokens = contextGcPrefixTokens(input) + contextGcMessageTokens(input, input.messages);
|
|
261
|
+
const afterTokens = contextGcPrefixTokens(input) + contextGcMessageTokens(input, projected.messages);
|
|
262
|
+
if (reclaimedTokens === 0)
|
|
263
|
+
return undefined;
|
|
264
|
+
const cache = contextGcCacheImpact(input, input.messages, projected.messages, input.usage.cacheReadTokens ?? 0, reclaimedTokens);
|
|
265
|
+
const growthSamples = collectContextGrowthSamples(input.records, input.modelId, input.contextWindowTokens, input.workspace, input.checkpointCache);
|
|
266
|
+
const contextGrowthP75Tokens = percentile75(growthSamples);
|
|
267
|
+
const compactThresholdTokens = Math.floor(input.contextWindowTokens * COMPACT_THRESHOLD_RATIO);
|
|
268
|
+
// Usage describes the request before its generated output and tool results.
|
|
269
|
+
const pendingMessages = input.messages.filter((_message, index) => input.messageSources[index].sequence > state.usageRecord.sequence);
|
|
270
|
+
const pendingTokens = Math.max(0, input.usage.outputTokens ?? 0) + contextGcMessageTokens(input, pendingMessages);
|
|
271
|
+
const projectedTokensBeforeGc = state.contextUsageTokens + pendingTokens;
|
|
272
|
+
const projectedTokensAfterGc = Math.max(0, projectedTokensBeforeGc - reclaimedTokens);
|
|
273
|
+
if (projectedTokensAfterGc >= compactThresholdTokens)
|
|
274
|
+
return undefined;
|
|
275
|
+
const projectedRolloutsUntilCompactAfterGc = contextGrowthP75Tokens <= 0
|
|
276
|
+
? 0
|
|
277
|
+
: Math.max(0, Math.floor((compactThresholdTokens - projectedTokensAfterGc) / Math.max(1, contextGrowthP75Tokens)));
|
|
278
|
+
const compactDeferralCreditCostUnits = state.usageRatio >= COMPACT_CREDIT_ACTIVATION_RATIO
|
|
279
|
+
? compactDeferralCredit(input, reclaimedTokens, compactThresholdTokens)
|
|
280
|
+
: 0;
|
|
281
|
+
const imminentCompactAvoidanceCreditCostUnits = contextGcImminentCompactAvoidanceCredit({
|
|
282
|
+
contextUsageTokens: state.contextUsageTokens,
|
|
283
|
+
contextWindowTokens: input.contextWindowTokens,
|
|
284
|
+
compactThresholdTokens,
|
|
285
|
+
projectedTokensBeforeGc,
|
|
286
|
+
projectedTokensAfterGc,
|
|
287
|
+
contextGrowthP75Tokens
|
|
288
|
+
});
|
|
289
|
+
const appliedCompactCreditCostUnits = contextGcAppliedCompactCreditCostUnits(compactDeferralCreditCostUnits, imminentCompactAvoidanceCreditCostUnits);
|
|
290
|
+
const economics = evaluateContextGcEconomics({
|
|
291
|
+
contextUsageTokens: state.contextUsageTokens,
|
|
292
|
+
reclaimedTokens,
|
|
293
|
+
reclaimedCachedTokens: cache.reclaimedCachedTokens,
|
|
294
|
+
cacheReadTokens: input.usage.cacheReadTokens ?? 0,
|
|
295
|
+
preservedCachedPrefixTokens: cache.preservedCachedPrefixTokens,
|
|
296
|
+
compactDeferralCreditCostUnits: appliedCompactCreditCostUnits,
|
|
297
|
+
projectedRolloutsUntilCompactAfterGc,
|
|
298
|
+
contextGrowthSampleCount: growthSamples.length,
|
|
299
|
+
minimumGrossReductionRate,
|
|
300
|
+
minimumReclaimedTokens
|
|
301
|
+
});
|
|
302
|
+
return {
|
|
303
|
+
...state,
|
|
304
|
+
economics,
|
|
305
|
+
replacements,
|
|
306
|
+
beforeTokens,
|
|
307
|
+
afterTokens,
|
|
308
|
+
reclaimedTokens,
|
|
309
|
+
contextGrowthSamples: growthSamples,
|
|
310
|
+
contextGrowthP75Tokens,
|
|
311
|
+
minimumGrossReductionRate,
|
|
312
|
+
minimumReclaimedTokens,
|
|
313
|
+
projectedRolloutsUntilCompactAfterGc,
|
|
314
|
+
projectedTokensBeforeGc,
|
|
315
|
+
projectedTokensAfterGc,
|
|
316
|
+
compactDeferralCreditCostUnits,
|
|
317
|
+
imminentCompactAvoidanceCreditCostUnits,
|
|
318
|
+
appliedCompactCreditCostUnits,
|
|
319
|
+
boundarySequence: input.records.at(-1)?.sequence ?? state.usageRecord.sequence,
|
|
320
|
+
minimumObservedRollouts: Math.min(...candidates.map((candidate) => candidate.observedRollouts)),
|
|
321
|
+
eligibleCandidateCount: candidates.length
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function collectStageACandidates(input, groups, existingReplacements, existingTargets) {
|
|
325
|
+
const eligible = [];
|
|
326
|
+
let protectedTokens = 0;
|
|
327
|
+
let retryAfterRollouts = Infinity;
|
|
328
|
+
const existingReadReplacements = new Map(existingReplacements.flatMap((replacement) => replacement.kind === 'READ_OUTPUT_ITEMS'
|
|
329
|
+
? [[contextGcTargetKey(replacement.target), replacement]]
|
|
330
|
+
: []));
|
|
331
|
+
const reads = [...groups.values()].filter((group) => group.toolName === 'read' &&
|
|
332
|
+
group.call?.message.role === 'tool_call' &&
|
|
333
|
+
group.result?.message.role === 'tool');
|
|
334
|
+
const claimedReadItems = new Set([...existingReadReplacements.values()].flatMap((replacement) => replacement.items.map((item) => `${replacement.target.recordId}:${item.index}`)));
|
|
335
|
+
const patches = [...groups.values()]
|
|
336
|
+
.flatMap((patch) => {
|
|
337
|
+
if (patch.toolName !== 'apply_patch' ||
|
|
338
|
+
!patch.call ||
|
|
339
|
+
!patch.result ||
|
|
340
|
+
patch.failed ||
|
|
341
|
+
!patch.completed)
|
|
342
|
+
return [];
|
|
343
|
+
const result = contextGcPatchResultFromCompletedPayload(patch.completed.payload);
|
|
344
|
+
return result ? [{ patch, result, evidenceSequence: patch.result.record.sequence }] : [];
|
|
345
|
+
})
|
|
346
|
+
.sort((left, right) => left.evidenceSequence - right.evidenceSequence);
|
|
347
|
+
for (const { patch, result, evidenceSequence } of patches) {
|
|
348
|
+
const patchCall = patch.call;
|
|
349
|
+
const observedRollouts = successfulRolloutsAfter(input.records, evidenceSequence, input.modelId, input.contextWindowTokens);
|
|
350
|
+
const replacements = [];
|
|
351
|
+
const patchTarget = targetFor(patchCall.record, patchCall.message);
|
|
352
|
+
if (patchTarget && !existingTargets.has(contextGcTargetKey(patchTarget)))
|
|
353
|
+
replacements.push({
|
|
354
|
+
kind: 'TOOL_CALL_INPUT',
|
|
355
|
+
toolName: 'apply_patch',
|
|
356
|
+
callId: patch.callId,
|
|
357
|
+
target: patchTarget,
|
|
358
|
+
replacementArguments: contextGcApplyPatchInputReplacement(patchCall.message.arguments, result, evidenceSequence),
|
|
359
|
+
providerAliasTargets: patch.aliases
|
|
360
|
+
.map(({ record, message }) => targetFor(record, message))
|
|
361
|
+
.filter((target) => target !== undefined),
|
|
362
|
+
evidenceSequence
|
|
363
|
+
});
|
|
364
|
+
for (const read of reads) {
|
|
365
|
+
if (read.result.record.sequence >= evidenceSequence)
|
|
366
|
+
continue;
|
|
367
|
+
const parsed = parseReadContextOutput(read.result.message.content);
|
|
368
|
+
if (!parsed)
|
|
369
|
+
continue;
|
|
370
|
+
const selected = parsed.items.flatMap((item) => {
|
|
371
|
+
const itemKey = `${read.result.record.recordId}:${item.index}`;
|
|
372
|
+
if (claimedReadItems.has(itemKey))
|
|
373
|
+
return [];
|
|
374
|
+
const pathKey = contextGcLogicalPathKey(item.path, input.workspace, input.platform);
|
|
375
|
+
const matched = result.files.find((file) => file.operation !== 'added' &&
|
|
376
|
+
contextGcLogicalPathKey(file.path, input.workspace, input.platform) === pathKey);
|
|
377
|
+
if (!pathKey || !matched || matched.operation === 'added')
|
|
378
|
+
return [];
|
|
379
|
+
claimedReadItems.add(itemKey);
|
|
380
|
+
return [
|
|
381
|
+
{
|
|
382
|
+
index: item.index,
|
|
383
|
+
path: item.path,
|
|
384
|
+
replacement: contextGcStaleReadReplacement({
|
|
385
|
+
path: item.path,
|
|
386
|
+
content: item.content,
|
|
387
|
+
evidenceSequence,
|
|
388
|
+
supersededByCallId: patch.callId,
|
|
389
|
+
operation: matched.operation
|
|
390
|
+
}),
|
|
391
|
+
evidenceSequence
|
|
392
|
+
}
|
|
393
|
+
];
|
|
394
|
+
});
|
|
395
|
+
if (selected.length === 0)
|
|
396
|
+
continue;
|
|
397
|
+
const target = targetFor(read.result.record, read.result.message);
|
|
398
|
+
if (!target)
|
|
399
|
+
continue;
|
|
400
|
+
const existingRead = existingReadReplacements.get(contextGcTargetKey(target));
|
|
401
|
+
replacements.push({
|
|
402
|
+
kind: 'READ_OUTPUT_ITEMS',
|
|
403
|
+
callId: read.callId,
|
|
404
|
+
target,
|
|
405
|
+
items: [...(existingRead?.items ?? []), ...selected],
|
|
406
|
+
evidenceSequences: [
|
|
407
|
+
...new Set([...(existingRead?.evidenceSequences ?? []), evidenceSequence])
|
|
408
|
+
]
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
if (replacements.length === 0)
|
|
412
|
+
continue;
|
|
413
|
+
const candidate = {
|
|
414
|
+
replacements,
|
|
415
|
+
observedRollouts,
|
|
416
|
+
evidenceSequence
|
|
417
|
+
};
|
|
418
|
+
if (observedRollouts >= CONTEXT_GC_POLICY.stageAProtectionRollouts)
|
|
419
|
+
eligible.push(candidate);
|
|
420
|
+
else {
|
|
421
|
+
protectedTokens += candidateTokens(input, candidate);
|
|
422
|
+
retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.stageAProtectionRollouts - observedRollouts);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return { eligible, protectedTokens, retryAfterRollouts };
|
|
426
|
+
}
|
|
427
|
+
function collectSubagentCandidates(input, groups, existingTargets) {
|
|
428
|
+
const eligible = [];
|
|
429
|
+
let protectedTokens = 0;
|
|
430
|
+
let retryAfterRollouts = Infinity;
|
|
431
|
+
const invocations = [...groups.values()]
|
|
432
|
+
.flatMap((group) => {
|
|
433
|
+
if (!isContextGcSubagentToolName(group.toolName) ||
|
|
434
|
+
!group.call ||
|
|
435
|
+
!group.result ||
|
|
436
|
+
group.failed ||
|
|
437
|
+
!group.completed ||
|
|
438
|
+
group.call.message.role !== 'tool_call' ||
|
|
439
|
+
group.result.message.role !== 'tool')
|
|
440
|
+
return [];
|
|
441
|
+
const snapshot = normalizeContextGcSubagentSnapshot(group.result.message.content, group.completed.payload, group.toolName);
|
|
442
|
+
const turnId = snapshot && contextGcSubagentSnapshotTurnId(snapshot);
|
|
443
|
+
return snapshot && turnId ? [{ group, snapshot, turnId }] : [];
|
|
444
|
+
})
|
|
445
|
+
.sort((left, right) => left.group.result.record.sequence - right.group.result.record.sequence);
|
|
446
|
+
for (const [index, source] of invocations.entries()) {
|
|
447
|
+
if (source.group.toolName !== 'agent_wait' && source.group.toolName !== 'agent_message')
|
|
448
|
+
continue;
|
|
449
|
+
const sourceTarget = targetFor(source.group.result.record, source.group.result.message);
|
|
450
|
+
if (!sourceTarget || existingTargets.has(contextGcTargetKey(sourceTarget)))
|
|
451
|
+
continue;
|
|
452
|
+
const needsRetainedPriorFinal = contextGcSubagentNeedsRetainedPriorFinal(source.snapshot);
|
|
453
|
+
const retainedPriorFinal = needsRetainedPriorFinal
|
|
454
|
+
? invocations
|
|
455
|
+
.slice(0, index)
|
|
456
|
+
.findLast((candidate) => contextGcSubagentPriorFinalIsRetained(source.snapshot, candidate.snapshot))
|
|
457
|
+
: undefined;
|
|
458
|
+
if (needsRetainedPriorFinal && retainedPriorFinal === undefined)
|
|
459
|
+
continue;
|
|
460
|
+
const terminal = invocations
|
|
461
|
+
.slice(index + 1)
|
|
462
|
+
.find((candidate) => candidate.snapshot.agentId === source.snapshot.agentId &&
|
|
463
|
+
candidate.turnId === source.turnId &&
|
|
464
|
+
contextGcSubagentSnapshotIsSuperseded(source.snapshot, candidate.snapshot, retainedPriorFinal?.snapshot));
|
|
465
|
+
if (!terminal)
|
|
466
|
+
continue;
|
|
467
|
+
const sourceContent = source.group.result.message.content;
|
|
468
|
+
if (typeof sourceContent !== 'string')
|
|
469
|
+
continue;
|
|
470
|
+
const evidenceSequence = terminal.group.result.record.sequence;
|
|
471
|
+
const observedRollouts = successfulRolloutsAfter(input.records, evidenceSequence, input.modelId, input.contextWindowTokens);
|
|
472
|
+
const replacement = {
|
|
473
|
+
kind: 'SUBAGENT_OUTPUT',
|
|
474
|
+
toolName: source.group.toolName,
|
|
475
|
+
callId: source.group.callId,
|
|
476
|
+
target: sourceTarget,
|
|
477
|
+
replacementContent: contextGcSubagentOutputReplacement({
|
|
478
|
+
sourceContent,
|
|
479
|
+
source: source.snapshot,
|
|
480
|
+
terminal: terminal.snapshot,
|
|
481
|
+
terminalCallId: terminal.group.callId,
|
|
482
|
+
evidenceSequence
|
|
483
|
+
}),
|
|
484
|
+
terminalToolName: terminal.group.toolName,
|
|
485
|
+
terminalCallId: terminal.group.callId,
|
|
486
|
+
evidenceSequence,
|
|
487
|
+
...(retainedPriorFinal === undefined
|
|
488
|
+
? {}
|
|
489
|
+
: {
|
|
490
|
+
retainedPriorFinal: {
|
|
491
|
+
toolName: retainedPriorFinal.group.toolName,
|
|
492
|
+
callId: retainedPriorFinal.group.callId,
|
|
493
|
+
evidenceSequence: retainedPriorFinal.group.result.record.sequence
|
|
494
|
+
}
|
|
495
|
+
})
|
|
496
|
+
};
|
|
497
|
+
const candidate = {
|
|
498
|
+
replacements: [replacement],
|
|
499
|
+
observedRollouts,
|
|
500
|
+
evidenceSequence
|
|
501
|
+
};
|
|
502
|
+
if (observedRollouts >= CONTEXT_GC_POLICY.stageAProtectionRollouts)
|
|
503
|
+
eligible.push(candidate);
|
|
504
|
+
else {
|
|
505
|
+
protectedTokens += candidateTokens(input, candidate);
|
|
506
|
+
retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.stageAProtectionRollouts - observedRollouts);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return { eligible, protectedTokens, retryAfterRollouts };
|
|
510
|
+
}
|
|
511
|
+
function collectExecCandidates(input, groups, existingTargets) {
|
|
512
|
+
const eligible = [];
|
|
513
|
+
let protectedTokens = 0;
|
|
514
|
+
let retryAfterRollouts = Infinity;
|
|
515
|
+
const execGroups = [...groups.values()]
|
|
516
|
+
.filter((group) => group.toolName === 'exec')
|
|
517
|
+
.sort((left, right) => (left.call?.record.sequence ?? Number.MAX_SAFE_INTEGER) -
|
|
518
|
+
(right.call?.record.sequence ?? Number.MAX_SAFE_INTEGER));
|
|
519
|
+
const invocations = new Map();
|
|
520
|
+
for (const group of execGroups) {
|
|
521
|
+
if (!group.call ||
|
|
522
|
+
!group.result ||
|
|
523
|
+
group.failed ||
|
|
524
|
+
!group.completed ||
|
|
525
|
+
group.call.message.role !== 'tool_call' ||
|
|
526
|
+
group.result.message.role !== 'tool' ||
|
|
527
|
+
!isRecord(group.call.message.arguments))
|
|
528
|
+
continue;
|
|
529
|
+
const result = normalizeContextGcExecResult(group.result.message.content, group.completed.payload);
|
|
530
|
+
if (result)
|
|
531
|
+
invocations.set(group.callId, {
|
|
532
|
+
group,
|
|
533
|
+
input: group.call.message.arguments,
|
|
534
|
+
result
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
for (const startGroup of execGroups) {
|
|
538
|
+
const start = invocations.get(startGroup.callId);
|
|
539
|
+
if (!start || start.input.action !== 'start')
|
|
540
|
+
continue;
|
|
541
|
+
const lifecycle = [start];
|
|
542
|
+
let processId;
|
|
543
|
+
let terminal = start.result.status !== 'running';
|
|
544
|
+
if (!terminal) {
|
|
545
|
+
processId = typeof start.result.processId === 'string' ? start.result.processId : undefined;
|
|
546
|
+
if (!processId)
|
|
547
|
+
continue;
|
|
548
|
+
const relatedGroups = execGroups.filter((group) => (group.call?.record.sequence ?? 0) > start.group.call.record.sequence &&
|
|
549
|
+
isRecord(group.call?.message.arguments) &&
|
|
550
|
+
group.call.message.arguments.processId === processId);
|
|
551
|
+
if (relatedGroups.some((group) => !invocations.has(group.callId)) ||
|
|
552
|
+
relatedGroups.length === 0)
|
|
553
|
+
continue;
|
|
554
|
+
for (const relatedGroup of relatedGroups) {
|
|
555
|
+
const invocation = invocations.get(relatedGroup.callId);
|
|
556
|
+
lifecycle.push(invocation);
|
|
557
|
+
if (invocation.result.status !== 'running') {
|
|
558
|
+
terminal = true;
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
if (!terminal || (processId && input.activeProcessIds.has(processId)))
|
|
564
|
+
continue;
|
|
565
|
+
const final = lifecycle.at(-1);
|
|
566
|
+
const visibleTokens = contextGcMessageTokens(input, lifecycle.flatMap((invocation) => [
|
|
567
|
+
invocation.group.call.message,
|
|
568
|
+
invocation.group.result.message,
|
|
569
|
+
...invocation.group.aliases.map((alias) => alias.message)
|
|
570
|
+
]));
|
|
571
|
+
if (visibleTokens < LARGE_EXEC_TOKENS)
|
|
572
|
+
continue;
|
|
573
|
+
const evidenceSequence = final.group.result.record.sequence;
|
|
574
|
+
const observedRollouts = successfulRolloutsAfter(input.records, evidenceSequence, input.modelId, input.contextWindowTokens);
|
|
575
|
+
const replacements = [];
|
|
576
|
+
for (const invocation of lifecycle) {
|
|
577
|
+
const callTarget = targetFor(invocation.group.call.record, invocation.group.call.message);
|
|
578
|
+
const resultTarget = targetFor(invocation.group.result.record, invocation.group.result.message);
|
|
579
|
+
if (!callTarget || !resultTarget) {
|
|
580
|
+
replacements.splice(0);
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
if (!existingTargets.has(contextGcTargetKey(callTarget)))
|
|
584
|
+
replacements.push({
|
|
585
|
+
kind: 'TOOL_CALL_INPUT',
|
|
586
|
+
toolName: 'exec',
|
|
587
|
+
callId: invocation.group.callId,
|
|
588
|
+
target: callTarget,
|
|
589
|
+
replacementArguments: contextGcExecInputReplacement(invocation.input),
|
|
590
|
+
providerAliasTargets: invocation.group.aliases
|
|
591
|
+
.map(({ record, message }) => targetFor(record, message))
|
|
592
|
+
.filter((target) => target !== undefined),
|
|
593
|
+
evidenceSequence
|
|
594
|
+
});
|
|
595
|
+
if (!existingTargets.has(contextGcTargetKey(resultTarget)))
|
|
596
|
+
replacements.push({
|
|
597
|
+
kind: 'EXEC_OUTPUT',
|
|
598
|
+
callId: invocation.group.callId,
|
|
599
|
+
target: resultTarget,
|
|
600
|
+
replacementContent: contextGcExecOutputReplacement(invocation.result),
|
|
601
|
+
evidenceSequence
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
if (replacements.length === 0)
|
|
605
|
+
continue;
|
|
606
|
+
const candidate = {
|
|
607
|
+
replacements,
|
|
608
|
+
observedRollouts,
|
|
609
|
+
evidenceSequence
|
|
610
|
+
};
|
|
611
|
+
if (observedRollouts >= CONTEXT_GC_POLICY.largeExecProtectionRollouts)
|
|
612
|
+
eligible.push(candidate);
|
|
613
|
+
else {
|
|
614
|
+
protectedTokens += candidateTokens(input, candidate);
|
|
615
|
+
retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - observedRollouts);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return { eligible, protectedTokens, retryAfterRollouts };
|
|
619
|
+
}
|
|
620
|
+
function isContextGcSubagentToolName(value) {
|
|
621
|
+
return (value === 'agent_start' ||
|
|
622
|
+
value === 'agent_wait' ||
|
|
623
|
+
value === 'agent_message' ||
|
|
624
|
+
value === 'agent_cancel');
|
|
625
|
+
}
|
|
626
|
+
function buildToolGroups(records, messages, sources) {
|
|
627
|
+
const groups = new Map();
|
|
628
|
+
const group = (callId, toolName = '') => {
|
|
629
|
+
const current = groups.get(callId);
|
|
630
|
+
if (current) {
|
|
631
|
+
if (!current.toolName && toolName)
|
|
632
|
+
current.toolName = toolName;
|
|
633
|
+
return current;
|
|
634
|
+
}
|
|
635
|
+
const created = { callId, toolName, aliases: [] };
|
|
636
|
+
groups.set(callId, created);
|
|
637
|
+
return created;
|
|
638
|
+
};
|
|
639
|
+
for (const [index, message] of messages.entries()) {
|
|
640
|
+
const record = sources[index];
|
|
641
|
+
if (message.role === 'tool_call' && message.callId && message.name)
|
|
642
|
+
group(message.callId, message.name).call = { message, record };
|
|
643
|
+
else if (message.role === 'tool' && message.callId)
|
|
644
|
+
group(message.callId).result = { message, record };
|
|
645
|
+
else if (message.role === 'provider') {
|
|
646
|
+
const callId = providerToolCallId(message);
|
|
647
|
+
if (callId)
|
|
648
|
+
group(callId, providerToolName(message)).aliases.push({ message, record });
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
for (const record of records) {
|
|
652
|
+
if (!isRecord(record.payload))
|
|
653
|
+
continue;
|
|
654
|
+
const type = record.payload.type;
|
|
655
|
+
const callId = record.payload.callId;
|
|
656
|
+
const toolName = record.payload.toolName;
|
|
657
|
+
if (typeof callId !== 'string' || typeof toolName !== 'string')
|
|
658
|
+
continue;
|
|
659
|
+
const current = group(callId, toolName);
|
|
660
|
+
if (type === 'tool.completed')
|
|
661
|
+
current.completed = record;
|
|
662
|
+
else if (type === 'tool.failed')
|
|
663
|
+
current.failed = record;
|
|
664
|
+
}
|
|
665
|
+
return groups;
|
|
666
|
+
}
|
|
667
|
+
function successfulRolloutsAfter(records, evidenceSequence, modelId, contextWindowTokens) {
|
|
668
|
+
return countSuccessfulNormalRollouts(records.filter((record) => record.sequence > evidenceSequence), modelId, contextWindowTokens, modelConfigurationAt(records, evidenceSequence, modelId, contextWindowTokens));
|
|
669
|
+
}
|
|
670
|
+
function countSuccessfulNormalRollouts(records, modelId, contextWindowTokens, initial = {
|
|
671
|
+
modelId,
|
|
672
|
+
contextWindowTokens
|
|
673
|
+
}) {
|
|
674
|
+
let activeModelId = initial.modelId;
|
|
675
|
+
let activeContextWindowTokens = initial.contextWindowTokens;
|
|
676
|
+
let count = 0;
|
|
677
|
+
for (const record of records) {
|
|
678
|
+
if (record.type === 'execution.header' && isRecord(record.payload)) {
|
|
679
|
+
activeModelId =
|
|
680
|
+
typeof record.payload.modelId === 'string' ? record.payload.modelId : activeModelId;
|
|
681
|
+
activeContextWindowTokens =
|
|
682
|
+
typeof record.payload.contextWindowTokens === 'number' &&
|
|
683
|
+
Number.isSafeInteger(record.payload.contextWindowTokens) &&
|
|
684
|
+
record.payload.contextWindowTokens > 0
|
|
685
|
+
? record.payload.contextWindowTokens
|
|
686
|
+
: activeContextWindowTokens;
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
if (activeModelId === modelId &&
|
|
690
|
+
activeContextWindowTokens === contextWindowTokens &&
|
|
691
|
+
record.type === 'model.attempt' &&
|
|
692
|
+
isRecord(record.payload) &&
|
|
693
|
+
record.payload.purpose === 'EXECUTION' &&
|
|
694
|
+
record.payload.state === 'SUCCEEDED')
|
|
695
|
+
count++;
|
|
696
|
+
}
|
|
697
|
+
return count;
|
|
698
|
+
}
|
|
699
|
+
function modelConfigurationAt(records, boundarySequence, fallbackModelId, fallbackContextWindowTokens) {
|
|
700
|
+
let modelId = fallbackModelId;
|
|
701
|
+
let contextWindowTokens = fallbackContextWindowTokens;
|
|
702
|
+
for (const record of records) {
|
|
703
|
+
if (record.sequence > boundarySequence)
|
|
704
|
+
break;
|
|
705
|
+
if (record.type !== 'execution.header' || !isRecord(record.payload))
|
|
706
|
+
continue;
|
|
707
|
+
if (typeof record.payload.modelId === 'string')
|
|
708
|
+
modelId = record.payload.modelId;
|
|
709
|
+
if (typeof record.payload.contextWindowTokens === 'number' &&
|
|
710
|
+
Number.isSafeInteger(record.payload.contextWindowTokens) &&
|
|
711
|
+
record.payload.contextWindowTokens > 0)
|
|
712
|
+
contextWindowTokens = record.payload.contextWindowTokens;
|
|
713
|
+
}
|
|
714
|
+
return { modelId, contextWindowTokens };
|
|
715
|
+
}
|
|
716
|
+
function latestUsageRecord(records, selectedModelId) {
|
|
717
|
+
let activeModelId = selectedModelId;
|
|
718
|
+
let latest;
|
|
719
|
+
for (const record of records) {
|
|
720
|
+
if (record.type === 'execution.header') {
|
|
721
|
+
const modelId = modelIdFromRecord(record);
|
|
722
|
+
if (modelId)
|
|
723
|
+
activeModelId = modelId;
|
|
724
|
+
}
|
|
725
|
+
else if (activeModelId === selectedModelId &&
|
|
726
|
+
isRecord(record.payload) &&
|
|
727
|
+
record.payload.type === 'usage.updated' &&
|
|
728
|
+
modelContextUsage(record.payload) !== undefined)
|
|
729
|
+
latest = record;
|
|
730
|
+
}
|
|
731
|
+
return latest;
|
|
732
|
+
}
|
|
733
|
+
function collectContextGrowthSamples(records, modelId, contextWindowTokens, workspace, checkpointCache) {
|
|
734
|
+
const previous = checkpointCache.read(records, undefined, workspace);
|
|
735
|
+
const base = contextGcBaseFromRecords(records);
|
|
736
|
+
const startSequence = previous?.record.sequence ?? (base?.type === 'COMPACT' ? base.sequence : base?.firstSequence);
|
|
737
|
+
let activeModelId = modelId;
|
|
738
|
+
let activeWindow = contextWindowTokens;
|
|
739
|
+
let previousUsage;
|
|
740
|
+
const samples = [];
|
|
741
|
+
for (const record of records) {
|
|
742
|
+
if (startSequence !== undefined && record.sequence <= startSequence)
|
|
743
|
+
continue;
|
|
744
|
+
if (record.type === 'execution.header' && isRecord(record.payload)) {
|
|
745
|
+
const nextModel = modelIdFromRecord(record);
|
|
746
|
+
const nextWindow = record.payload.contextWindowTokens;
|
|
747
|
+
if (nextModel !== modelId ||
|
|
748
|
+
(typeof nextWindow === 'number' && nextWindow !== contextWindowTokens)) {
|
|
749
|
+
activeModelId = nextModel ?? activeModelId;
|
|
750
|
+
activeWindow =
|
|
751
|
+
typeof nextWindow === 'number' && Number.isSafeInteger(nextWindow)
|
|
752
|
+
? nextWindow
|
|
753
|
+
: activeWindow;
|
|
754
|
+
previousUsage = undefined;
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
activeModelId = modelId;
|
|
758
|
+
activeWindow = contextWindowTokens;
|
|
759
|
+
}
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
if (activeModelId !== modelId ||
|
|
763
|
+
activeWindow !== contextWindowTokens ||
|
|
764
|
+
!isRecord(record.payload) ||
|
|
765
|
+
record.payload.type !== 'usage.updated')
|
|
766
|
+
continue;
|
|
767
|
+
const usage = modelContextUsage(record.payload);
|
|
768
|
+
if (usage === undefined) {
|
|
769
|
+
previousUsage = undefined;
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
if (previousUsage !== undefined && usage > previousUsage)
|
|
773
|
+
samples.push(usage - previousUsage);
|
|
774
|
+
previousUsage = usage;
|
|
775
|
+
}
|
|
776
|
+
return samples.slice(-CONTEXT_GC_POLICY.contextGrowthSampleWindow);
|
|
777
|
+
}
|
|
778
|
+
export function contextGcImminentCompactAvoidanceCredit(input) {
|
|
779
|
+
if (input.contextUsageTokens >= input.compactThresholdTokens ||
|
|
780
|
+
input.contextGrowthP75Tokens <= 0 ||
|
|
781
|
+
input.projectedTokensBeforeGc < input.compactThresholdTokens)
|
|
782
|
+
return 0;
|
|
783
|
+
const safetyMarginTokens = Math.max(input.contextWindowTokens * MINIMUM_CONTEXT_WINDOW_REDUCTION_RATIO, input.contextGrowthP75Tokens * CONTEXT_GC_POLICY.immediatePaybackRollouts);
|
|
784
|
+
if (input.projectedTokensAfterGc > input.compactThresholdTokens - safetyMarginTokens)
|
|
785
|
+
return 0;
|
|
786
|
+
return (input.contextUsageTokens *
|
|
787
|
+
CONTEXT_GC_POLICY.normalInputCostRate *
|
|
788
|
+
IMMINENT_COMPACT_CREDIT_DISCOUNT);
|
|
789
|
+
}
|
|
790
|
+
export function contextGcAppliedCompactCreditCostUnits(compactDeferralCreditCostUnits, imminentCompactAvoidanceCreditCostUnits) {
|
|
791
|
+
return Math.max(0, compactDeferralCreditCostUnits, imminentCompactAvoidanceCreditCostUnits);
|
|
792
|
+
}
|
|
793
|
+
function compactDeferralCredit(input, reclaimedTokens, compactThresholdTokens) {
|
|
794
|
+
const base = contextGcBaseFromRecords(input.records);
|
|
795
|
+
if (base?.type !== 'COMPACT')
|
|
796
|
+
return 0;
|
|
797
|
+
const compact = input.records.find((record) => record.sequence === base.sequence);
|
|
798
|
+
if (!compact || !isRecord(compact.payload))
|
|
799
|
+
return 0;
|
|
800
|
+
if (compact.payload.modelId !== input.modelId ||
|
|
801
|
+
compact.payload.contextWindowTokens !== input.contextWindowTokens ||
|
|
802
|
+
!isRecord(compact.payload.usage))
|
|
803
|
+
return 0;
|
|
804
|
+
const usage = compact.payload.usage;
|
|
805
|
+
const contextInput = modelContextUsage(usage);
|
|
806
|
+
const cacheRead = nonnegativeNumber(usage.cacheReadTokens);
|
|
807
|
+
const cacheWrite = nonnegativeNumber(usage.cacheWriteTokens);
|
|
808
|
+
const output = nonnegativeNumber(usage.outputTokens);
|
|
809
|
+
if (contextInput === undefined ||
|
|
810
|
+
cacheRead === undefined ||
|
|
811
|
+
cacheWrite === undefined ||
|
|
812
|
+
output === undefined)
|
|
813
|
+
return 0;
|
|
814
|
+
const baseline = firstMatchingUsageAfter(input.records, compact.sequence, input.modelId, input.contextWindowTokens);
|
|
815
|
+
if (baseline === undefined || baseline >= compactThresholdTokens)
|
|
816
|
+
return 0;
|
|
817
|
+
const fresh = Math.max(0, contextInput - cacheRead - cacheWrite);
|
|
818
|
+
const compactCost = fresh * CONTEXT_GC_POLICY.normalInputCostRate +
|
|
819
|
+
cacheRead * CONTEXT_GC_POLICY.cacheReadCostRate +
|
|
820
|
+
cacheWrite * CONTEXT_GC_POLICY.cacheWriteCostRate +
|
|
821
|
+
output * CONTEXT_GC_POLICY.outputCostRate;
|
|
822
|
+
const cycleCapacity = Math.max(1, compactThresholdTokens - baseline);
|
|
823
|
+
return compactCost * clamp(reclaimedTokens / cycleCapacity, 0, 1);
|
|
824
|
+
}
|
|
825
|
+
function firstMatchingUsageAfter(records, sequence, modelId, contextWindowTokens) {
|
|
826
|
+
let activeModelId = modelId;
|
|
827
|
+
let activeContextWindowTokens = contextWindowTokens;
|
|
828
|
+
for (const record of records) {
|
|
829
|
+
if (record.sequence <= sequence)
|
|
830
|
+
continue;
|
|
831
|
+
if (record.type === 'execution.header' && isRecord(record.payload)) {
|
|
832
|
+
if (typeof record.payload.modelId === 'string')
|
|
833
|
+
activeModelId = record.payload.modelId;
|
|
834
|
+
if (typeof record.payload.contextWindowTokens === 'number' &&
|
|
835
|
+
Number.isSafeInteger(record.payload.contextWindowTokens) &&
|
|
836
|
+
record.payload.contextWindowTokens > 0)
|
|
837
|
+
activeContextWindowTokens = record.payload.contextWindowTokens;
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
if (activeModelId !== modelId ||
|
|
841
|
+
activeContextWindowTokens !== contextWindowTokens ||
|
|
842
|
+
!isRecord(record.payload) ||
|
|
843
|
+
record.payload.type !== 'usage.updated')
|
|
844
|
+
continue;
|
|
845
|
+
const usage = modelContextUsage(record.payload);
|
|
846
|
+
if (usage !== undefined)
|
|
847
|
+
return usage;
|
|
848
|
+
}
|
|
849
|
+
return undefined;
|
|
850
|
+
}
|
|
851
|
+
function candidateTokens(input, candidate) {
|
|
852
|
+
const targets = new Set(candidate.replacements.flatMap((replacement) => [
|
|
853
|
+
contextGcTargetKey(replacement.target),
|
|
854
|
+
...(replacement.kind === 'TOOL_CALL_INPUT'
|
|
855
|
+
? replacement.providerAliasTargets.map(contextGcTargetKey)
|
|
856
|
+
: [])
|
|
857
|
+
]));
|
|
858
|
+
return contextGcMessageTokens(input, input.messages.filter((message, index) => {
|
|
859
|
+
const target = targetFor(input.messageSources[index], message);
|
|
860
|
+
return target !== undefined && targets.has(contextGcTargetKey(target));
|
|
861
|
+
}));
|
|
862
|
+
}
|
|
863
|
+
function targetFor(record, message) {
|
|
864
|
+
if (record.type !== 'model.context' ||
|
|
865
|
+
(message.role !== 'tool_call' && message.role !== 'tool' && message.role !== 'provider'))
|
|
866
|
+
return undefined;
|
|
867
|
+
const callId = message.role === 'provider' ? providerToolCallId(message) : message.callId;
|
|
868
|
+
const toolName = message.role === 'provider' ? providerToolName(message) : message.name;
|
|
869
|
+
return {
|
|
870
|
+
sequence: record.sequence,
|
|
871
|
+
recordId: record.recordId,
|
|
872
|
+
role: message.role,
|
|
873
|
+
...(callId === undefined ? {} : { callId }),
|
|
874
|
+
...(toolName === undefined ? {} : { toolName }),
|
|
875
|
+
sourceHash: contextGcRecordSourceHash(record)
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
function modelContextUsage(value) {
|
|
879
|
+
const context = value.contextInputTokens ?? value.inputTokens;
|
|
880
|
+
return typeof context === 'number' && Number.isSafeInteger(context) && context >= 0
|
|
881
|
+
? context
|
|
882
|
+
: undefined;
|
|
883
|
+
}
|
|
884
|
+
function modelIdFromRecord(record) {
|
|
885
|
+
if (!isRecord(record.payload))
|
|
886
|
+
return undefined;
|
|
887
|
+
return typeof record.payload.modelId === 'string' ? record.payload.modelId : undefined;
|
|
888
|
+
}
|
|
889
|
+
function percentile75(values) {
|
|
890
|
+
if (values.length === 0)
|
|
891
|
+
return 0;
|
|
892
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
893
|
+
return sorted[Math.max(0, Math.ceil(sorted.length * 0.75) - 1)];
|
|
894
|
+
}
|
|
895
|
+
function firstMatchingRollout(limit, predicate) {
|
|
896
|
+
for (let rollouts = 0; rollouts <= limit; rollouts++)
|
|
897
|
+
if (predicate(rollouts))
|
|
898
|
+
return rollouts;
|
|
899
|
+
return Number.POSITIVE_INFINITY;
|
|
900
|
+
}
|
|
901
|
+
function nonnegativeNumber(value) {
|
|
902
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
903
|
+
}
|
|
904
|
+
function clamp(value, minimum, maximum) {
|
|
905
|
+
return Math.min(maximum, Math.max(minimum, value));
|
|
906
|
+
}
|
|
907
|
+
function isRecord(value) {
|
|
908
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
909
|
+
}
|