@myagentroam/agent 0.11.61 → 0.11.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/catalog-file.d.ts +2 -0
- package/dist/cli/main.js +26 -15
- package/dist/cli/subagent/control.js +2 -2
- package/dist/cli/subagent/start.js +1 -1
- package/dist/cli/terminal-input.d.ts +12 -0
- package/dist/cli/terminal-input.js +86 -0
- package/dist/host/local-tool-registry.js +2 -2
- package/dist/model/configuration.d.ts +1 -0
- package/dist/model/configuration.js +1 -0
- package/dist/runtime/context/gc-admission.d.ts +5 -0
- package/dist/runtime/context/gc-admission.js +23 -0
- package/dist/runtime/context/gc-candidates-code.js +5 -6
- package/dist/runtime/context/gc-candidates-exec.js +3 -4
- package/dist/runtime/context/gc-candidates-read-patch.js +3 -4
- package/dist/runtime/context/gc-candidates-web.d.ts +1 -1
- package/dist/runtime/context/gc-candidates-web.js +6 -7
- package/dist/runtime/context/gc-runtime.js +3 -57
- package/dist/runtime/context/gc-tokens.d.ts +0 -4
- package/dist/runtime/context/gc-tokens.js +0 -18
- package/dist/runtime/context/gc.d.ts +1 -1
- package/dist/runtime/context/gc.js +24 -139
- package/dist/sdk/execution-context-coordinator.d.ts +1 -0
- package/dist/sdk/execution-context-coordinator.js +3 -1
- package/dist/sdk/session-execution-factory.js +4 -1
- package/dist/session/context-gc-checkpoint.d.ts +1 -4
- package/dist/session/context-gc-checkpoint.js +10 -22
- package/dist/session/context-gc-contract.d.ts +8 -8
- package/dist/session/store/jsonl/jsonl-store.js +1 -4
- package/dist/tools/agent/question.js +1 -1
- package/dist/tools/agent/sleep.js +1 -1
- package/dist/tools/agent/todo-write.js +1 -1
- package/dist/tools/code-mode/index.js +1 -1
- package/dist/tools/contracts.d.ts +1 -0
- package/dist/tools/local/apply-patch.d.ts +1 -1
- package/dist/tools/local/apply-patch.js +11 -2
- package/dist/tools/mcp/tool-search.js +1 -1
- package/dist/tools/scheduler.js +4 -4
- package/package.json +1 -1
- package/dist/runtime/context/gc-economics.d.ts +0 -35
- package/dist/runtime/context/gc-economics.js +0 -93
|
@@ -30,6 +30,7 @@ export declare function serializeModel(model: CliModelConfiguration): {
|
|
|
30
30
|
defaultReasoningEffort?: "low" | "medium" | "high" | "xhigh" | "max" | "ultra" | undefined;
|
|
31
31
|
titleGeneration: boolean;
|
|
32
32
|
codeMode: boolean;
|
|
33
|
+
gcEnabled?: boolean | undefined;
|
|
33
34
|
fastMode?: boolean | undefined;
|
|
34
35
|
serviceTier?: "default" | "auto" | "flex" | "priority" | "fast" | "ultrafast" | undefined;
|
|
35
36
|
hostedWebSearch: boolean;
|
|
@@ -65,6 +66,7 @@ export declare function serializeModel(model: CliModelConfiguration): {
|
|
|
65
66
|
defaultReasoningEffort?: "low" | "medium" | "high" | "xhigh" | "max" | "ultra" | undefined;
|
|
66
67
|
titleGeneration: boolean;
|
|
67
68
|
codeMode: boolean;
|
|
69
|
+
gcEnabled?: boolean | undefined;
|
|
68
70
|
fastMode?: boolean | undefined;
|
|
69
71
|
serviceTier?: "default" | "auto" | "flex" | "priority" | "fast" | "ultrafast" | undefined;
|
|
70
72
|
hostedWebSearch: boolean;
|
package/dist/cli/main.js
CHANGED
|
@@ -9,6 +9,7 @@ import { readModelCatalog, writeModelCatalog } from './catalog-file.js';
|
|
|
9
9
|
import { AgentError, createAgent, agentReasoningEffortSchema, resolveModelDefaultReasoningEffort, SecretValue, testAgentModelTransport } from '../index.js';
|
|
10
10
|
import { CodexAuthFileCredentialProvider } from './codex-auth-file.js';
|
|
11
11
|
import { generateSessionTitle } from '../shared/auto-title.js';
|
|
12
|
+
import { TerminalInput, TerminalInputClosed } from './terminal-input.js';
|
|
12
13
|
const HELP = `maragent - MAR coding agent
|
|
13
14
|
|
|
14
15
|
Usage:
|
|
@@ -146,6 +147,7 @@ async function modelsCommand(home, arguments_, codexAuthFile) {
|
|
|
146
147
|
const defaultReasoningEffortOption = option('--default-reasoning-effort');
|
|
147
148
|
const model = {
|
|
148
149
|
useResourceUrl: existing?.useResourceUrl ?? false,
|
|
150
|
+
gcEnabled: existing?.gcEnabled ?? true,
|
|
149
151
|
id,
|
|
150
152
|
name: required(option('--name', existing?.name), '--name'),
|
|
151
153
|
protocol,
|
|
@@ -261,21 +263,22 @@ async function main() {
|
|
|
261
263
|
// `run` does not need stdin until a question occurs. Create
|
|
262
264
|
// readline lazily so a closed or pseudo-TTY stream in an external harness
|
|
263
265
|
// cannot fail the execution before its first model request.
|
|
264
|
-
|
|
265
|
-
const requireTerminal = () => (terminal ??= createInterface({ input: stdin, output: stdout }));
|
|
266
|
+
const terminal = new TerminalInput(() => createInterface({ input: stdin, output: stdout }));
|
|
266
267
|
const host = new LocalAgentHost({
|
|
267
268
|
homeDirectory: home,
|
|
268
269
|
workspace,
|
|
269
|
-
question: async (request) => {
|
|
270
|
+
question: async (request, signal) => {
|
|
270
271
|
if (!stdin.isTTY)
|
|
271
272
|
throw new AgentError('MAR_AGENT_USER_INPUT_UNAVAILABLE', 'Interactive input is unavailable.');
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
273
|
+
return terminal.run(signal, async (ask) => {
|
|
274
|
+
const answers = {};
|
|
275
|
+
for (const question of request.questions) {
|
|
276
|
+
const value = question;
|
|
277
|
+
const id = typeof value.id === 'string' ? value.id : String(Object.keys(answers).length);
|
|
278
|
+
answers[id] = await ask(`${typeof value.question === 'string' ? value.question : JSON.stringify(question)} `);
|
|
279
|
+
}
|
|
280
|
+
return answers;
|
|
281
|
+
});
|
|
279
282
|
}
|
|
280
283
|
});
|
|
281
284
|
const runtime = runtimeModels(catalog.models, codexAuthFile);
|
|
@@ -311,7 +314,7 @@ async function main() {
|
|
|
311
314
|
const listed = (await agent.listSessions());
|
|
312
315
|
for (const item of listed.sessions ?? [])
|
|
313
316
|
stdout.write(`${item.id}\t${item.title}\t${item.lastActivityAt}\n`);
|
|
314
|
-
resumeId = (await
|
|
317
|
+
resumeId = (await terminal.run(undefined, (ask) => ask('Session ID: '))).trim();
|
|
315
318
|
if (!resumeId)
|
|
316
319
|
throw new AgentError('MAR_AGENT_CLI_ARGUMENT_INVALID', 'Session id is required.');
|
|
317
320
|
}
|
|
@@ -329,7 +332,7 @@ async function main() {
|
|
|
329
332
|
if (activeHandle)
|
|
330
333
|
activeHandle.cancel();
|
|
331
334
|
else
|
|
332
|
-
terminal
|
|
335
|
+
terminal.close();
|
|
333
336
|
titleController?.abort();
|
|
334
337
|
process.exitCode = 130;
|
|
335
338
|
if (interrupts > 1)
|
|
@@ -338,8 +341,16 @@ async function main() {
|
|
|
338
341
|
process.on('SIGINT', interrupt);
|
|
339
342
|
try {
|
|
340
343
|
do {
|
|
341
|
-
if (interactive)
|
|
342
|
-
|
|
344
|
+
if (interactive) {
|
|
345
|
+
try {
|
|
346
|
+
prompt = await terminal.run(undefined, (ask) => ask('> '));
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
if (error instanceof TerminalInputClosed)
|
|
350
|
+
break;
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
343
354
|
if (!prompt.trim())
|
|
344
355
|
break;
|
|
345
356
|
const invocation = parseExecutionInvocation(prompt);
|
|
@@ -409,7 +420,7 @@ async function main() {
|
|
|
409
420
|
}
|
|
410
421
|
}
|
|
411
422
|
finally {
|
|
412
|
-
terminal
|
|
423
|
+
terminal.close();
|
|
413
424
|
await agent.dispose();
|
|
414
425
|
}
|
|
415
426
|
}
|
|
@@ -49,7 +49,7 @@ export function registerControlTools(context, agent, observations, warmObservati
|
|
|
49
49
|
name: 'agent_send',
|
|
50
50
|
description: 'Send a message to a direct child or report to the direct parent.',
|
|
51
51
|
allowedCallers: ['direct', 'programmatic'],
|
|
52
|
-
parallelSafety: '
|
|
52
|
+
parallelSafety: 'safe',
|
|
53
53
|
inputSchema: {
|
|
54
54
|
type: 'object',
|
|
55
55
|
properties: {
|
|
@@ -70,7 +70,7 @@ export function registerControlTools(context, agent, observations, warmObservati
|
|
|
70
70
|
name: 'agent_stop',
|
|
71
71
|
description: 'Stop a direct child execution and wait for its cleanup.',
|
|
72
72
|
allowedCallers: ['direct', 'programmatic'],
|
|
73
|
-
parallelSafety: '
|
|
73
|
+
parallelSafety: 'safe',
|
|
74
74
|
inputSchema: {
|
|
75
75
|
type: 'object',
|
|
76
76
|
properties: {
|
|
@@ -44,7 +44,7 @@ export function registerStart(context, agent, admission) {
|
|
|
44
44
|
name: 'agent_start',
|
|
45
45
|
description: 'Start a child Session and return its identity without waiting for completion.',
|
|
46
46
|
allowedCallers: ['direct', 'programmatic'],
|
|
47
|
-
parallelSafety: '
|
|
47
|
+
parallelSafety: 'safe',
|
|
48
48
|
inputSchema: {
|
|
49
49
|
type: 'object',
|
|
50
50
|
properties: {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises';
|
|
2
|
+
export declare class TerminalInputClosed extends Error {
|
|
3
|
+
constructor();
|
|
4
|
+
}
|
|
5
|
+
/** One interactive transaction at a time per terminal, including multi-question requests. */
|
|
6
|
+
export declare class TerminalInput {
|
|
7
|
+
#private;
|
|
8
|
+
private readonly create;
|
|
9
|
+
constructor(create: () => ReturnType<typeof createInterface>);
|
|
10
|
+
run<T>(signal: AbortSignal | undefined, work: (ask: (prompt: string) => Promise<string>) => Promise<T>): Promise<T>;
|
|
11
|
+
close(): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export class TerminalInputClosed extends Error {
|
|
2
|
+
constructor() {
|
|
3
|
+
super('Terminal input is closed.');
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
/** One interactive transaction at a time per terminal, including multi-question requests. */
|
|
7
|
+
export class TerminalInput {
|
|
8
|
+
create;
|
|
9
|
+
#terminal;
|
|
10
|
+
#tail = Promise.resolve();
|
|
11
|
+
#lines = [];
|
|
12
|
+
#waiter;
|
|
13
|
+
#closed = false;
|
|
14
|
+
constructor(create) {
|
|
15
|
+
this.create = create;
|
|
16
|
+
}
|
|
17
|
+
run(signal, work) {
|
|
18
|
+
const operation = this.#tail.then(async () => {
|
|
19
|
+
signal?.throwIfAborted();
|
|
20
|
+
return work((prompt) => this.#ask(prompt, signal));
|
|
21
|
+
});
|
|
22
|
+
this.#tail = operation.then(() => undefined, () => undefined);
|
|
23
|
+
if (!signal)
|
|
24
|
+
return operation;
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const aborted = () => reject(signal.reason);
|
|
27
|
+
if (signal.aborted)
|
|
28
|
+
aborted();
|
|
29
|
+
else
|
|
30
|
+
signal.addEventListener('abort', aborted, { once: true });
|
|
31
|
+
operation.then(resolve, reject).finally(() => signal.removeEventListener('abort', aborted));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
#ask(prompt, signal) {
|
|
35
|
+
signal?.throwIfAborted();
|
|
36
|
+
if (this.#closed) {
|
|
37
|
+
const line = this.#lines.shift();
|
|
38
|
+
return line === undefined ? Promise.reject(new TerminalInputClosed()) : Promise.resolve(line);
|
|
39
|
+
}
|
|
40
|
+
if (!this.#terminal) {
|
|
41
|
+
this.#terminal = this.create();
|
|
42
|
+
this.#terminal.on('line', (line) => {
|
|
43
|
+
const waiter = this.#waiter;
|
|
44
|
+
if (waiter) {
|
|
45
|
+
this.#waiter = undefined;
|
|
46
|
+
waiter.resolve(line);
|
|
47
|
+
}
|
|
48
|
+
else
|
|
49
|
+
this.#lines.push(line);
|
|
50
|
+
});
|
|
51
|
+
this.#terminal.on('close', () => {
|
|
52
|
+
this.#closed = true;
|
|
53
|
+
const waiter = this.#waiter;
|
|
54
|
+
this.#waiter = undefined;
|
|
55
|
+
waiter?.reject(new TerminalInputClosed());
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
this.#terminal.setPrompt(prompt);
|
|
59
|
+
this.#terminal.prompt();
|
|
60
|
+
const line = this.#lines.shift();
|
|
61
|
+
if (line !== undefined)
|
|
62
|
+
return Promise.resolve(line);
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const finish = (callback) => {
|
|
65
|
+
signal?.removeEventListener('abort', abort);
|
|
66
|
+
callback();
|
|
67
|
+
};
|
|
68
|
+
const abort = () => {
|
|
69
|
+
this.#waiter = undefined;
|
|
70
|
+
finish(() => reject(signal.reason));
|
|
71
|
+
};
|
|
72
|
+
this.#waiter = {
|
|
73
|
+
resolve: (value) => finish(() => resolve(value)),
|
|
74
|
+
reject: (error) => finish(() => reject(error))
|
|
75
|
+
};
|
|
76
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
77
|
+
if (signal?.aborted)
|
|
78
|
+
abort();
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
close() {
|
|
82
|
+
this.#closed = true;
|
|
83
|
+
this.#lines = [];
|
|
84
|
+
this.#terminal?.close();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -71,8 +71,8 @@ export class LocalToolRegistry {
|
|
|
71
71
|
],
|
|
72
72
|
[
|
|
73
73
|
'apply_patch',
|
|
74
|
-
async (arguments_) => {
|
|
75
|
-
const result = await patch.execute(applyPatchToolInput(arguments_));
|
|
74
|
+
async (arguments_, context) => {
|
|
75
|
+
const result = await patch.execute(applyPatchToolInput(arguments_), context.signal);
|
|
76
76
|
return { content: JSON.stringify(result), data: result };
|
|
77
77
|
}
|
|
78
78
|
],
|
|
@@ -56,6 +56,7 @@ declare const rawModelSchema: z.ZodObject<{
|
|
|
56
56
|
subagentModel: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
57
57
|
titleGeneration: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
58
58
|
codeMode: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
59
|
+
gcEnabled: z.ZodOptional<z.ZodBoolean>;
|
|
59
60
|
useResourceUrl: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
60
61
|
fastMode: z.ZodOptional<z.ZodBoolean>;
|
|
61
62
|
serviceTier: z.ZodOptional<z.ZodEnum<{
|
|
@@ -38,6 +38,7 @@ const modelFieldsSchema = z
|
|
|
38
38
|
subagentModel: z.boolean().optional().default(false),
|
|
39
39
|
titleGeneration: z.boolean().optional().default(false),
|
|
40
40
|
codeMode: z.boolean().optional().default(false),
|
|
41
|
+
gcEnabled: z.boolean().optional(),
|
|
41
42
|
useResourceUrl: z.boolean().optional().default(false),
|
|
42
43
|
fastMode: z.boolean().optional(),
|
|
43
44
|
serviceTier: z.enum(['auto', 'default', 'flex', 'priority', 'fast', 'ultrafast']).optional(),
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ModelUsage } from '../../model/contracts.js';
|
|
2
|
+
export declare const COMPACT_THRESHOLD_RATIO: number;
|
|
3
|
+
export declare function contextGcMinimumReclaimedTokens(contextWindowTokens: number): number;
|
|
4
|
+
export declare function contextGcEligibleUsage(usage: ModelUsage, contextWindowTokens: number): number | undefined;
|
|
5
|
+
export declare function modelContextUsage(value: ModelUsage | Record<string, unknown>): number | undefined;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AGENT_EXECUTION_POLICY } from '../execution-policy.js';
|
|
2
|
+
const CONTEXT_GC_START_RATIO = 0.5;
|
|
3
|
+
export const COMPACT_THRESHOLD_RATIO = AGENT_EXECUTION_POLICY.backgroundCompactionPercent / 100;
|
|
4
|
+
export function contextGcMinimumReclaimedTokens(contextWindowTokens) {
|
|
5
|
+
return Math.floor(contextWindowTokens / 10) + 1;
|
|
6
|
+
}
|
|
7
|
+
export function contextGcEligibleUsage(usage, contextWindowTokens) {
|
|
8
|
+
const contextUsageTokens = modelContextUsage(usage);
|
|
9
|
+
if (contextUsageTokens === undefined ||
|
|
10
|
+
!Number.isSafeInteger(contextWindowTokens) ||
|
|
11
|
+
contextWindowTokens <= 0)
|
|
12
|
+
return undefined;
|
|
13
|
+
const usageRatio = contextUsageTokens / contextWindowTokens;
|
|
14
|
+
if (usageRatio < CONTEXT_GC_START_RATIO || usageRatio >= COMPACT_THRESHOLD_RATIO)
|
|
15
|
+
return undefined;
|
|
16
|
+
return contextUsageTokens;
|
|
17
|
+
}
|
|
18
|
+
export function modelContextUsage(value) {
|
|
19
|
+
const context = value.contextInputTokens ?? value.inputTokens;
|
|
20
|
+
return typeof context === 'number' && Number.isSafeInteger(context) && context >= 0
|
|
21
|
+
? context
|
|
22
|
+
: undefined;
|
|
23
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { retainedCodeInputs } from '../../session/context-gc-observations.js';
|
|
2
2
|
import { contextGcTargetKey } from '../../session/context-gc-checkpoint.js';
|
|
3
3
|
import { codeCellCompletionSequence, contextGcCodeModeInputReplacement, contextGcCodeModeOutputReplacement, hasGenericContextGcPreview, normalizeContextGcCodeModeInput } from '../../session/context-gc-replacements.js';
|
|
4
|
-
import {
|
|
5
|
-
import { contextGcProtectionMature } from '../../session/context-gc-protection.js';
|
|
4
|
+
import { CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS, contextGcProtectionMature } from '../../session/context-gc-protection.js';
|
|
6
5
|
import { targetFor, isRecord } from './gc-candidates.js';
|
|
7
6
|
export function collectCodeModeCandidates(input, groups, existingTargets, candidateSavings) {
|
|
8
7
|
const eligible = [];
|
|
@@ -44,9 +43,9 @@ export function collectCodeModeCandidates(input, groups, existingTargets, candid
|
|
|
44
43
|
continue;
|
|
45
44
|
if (contextGcProtectionMature(protection))
|
|
46
45
|
eligible.push(candidate);
|
|
47
|
-
else if (candidate.observedRollouts <
|
|
46
|
+
else if (candidate.observedRollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
|
|
48
47
|
protectedTokens += reclaimedTokens;
|
|
49
|
-
retryAfterRollouts = Math.min(retryAfterRollouts,
|
|
48
|
+
retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - candidate.observedRollouts);
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
if (group.failed || !group.completed)
|
|
@@ -107,9 +106,9 @@ export function collectCodeModeCandidates(input, groups, existingTargets, candid
|
|
|
107
106
|
continue;
|
|
108
107
|
if (contextGcProtectionMature(protection))
|
|
109
108
|
eligible.push(candidate);
|
|
110
|
-
else if (observedRollouts <
|
|
109
|
+
else if (observedRollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
|
|
111
110
|
protectedTokens += reclaimedTokens;
|
|
112
|
-
retryAfterRollouts = Math.min(retryAfterRollouts,
|
|
111
|
+
retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - observedRollouts);
|
|
113
112
|
}
|
|
114
113
|
}
|
|
115
114
|
return { eligible, protectedTokens, retryAfterRollouts };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { contextGcTargetKey } from '../../session/context-gc-checkpoint.js';
|
|
2
2
|
import { contextGcExecInputReplacement, contextGcExecOutputReplacement, normalizeContextGcExecResult } from '../../session/context-gc-replacements.js';
|
|
3
|
-
import {
|
|
4
|
-
import { contextGcProtectionMature } from '../../session/context-gc-protection.js';
|
|
3
|
+
import { CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS, contextGcProtectionMature } from '../../session/context-gc-protection.js';
|
|
5
4
|
import { targetFor, isRecord } from './gc-candidates.js';
|
|
6
5
|
import { terminalExecLifecycles } from '../../session/context-gc-exec-lifecycle.js';
|
|
7
6
|
export function collectExecCandidates(input, groups, existingTargets, candidateSavings) {
|
|
@@ -85,9 +84,9 @@ export function collectExecCandidates(input, groups, existingTargets, candidateS
|
|
|
85
84
|
mature.push(replacement);
|
|
86
85
|
observedRollouts = Math.min(observedRollouts, protection.rollouts);
|
|
87
86
|
}
|
|
88
|
-
else if (protection.rollouts <
|
|
87
|
+
else if (protection.rollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
|
|
89
88
|
awaitingRollouts.push(replacement);
|
|
90
|
-
retryAfterRollouts = Math.min(retryAfterRollouts,
|
|
89
|
+
retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - protection.rollouts);
|
|
91
90
|
}
|
|
92
91
|
}
|
|
93
92
|
if (awaitingRollouts.length)
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { contextGcLogicalPathKey, contextGcTargetKey } from '../../session/context-gc-checkpoint.js';
|
|
2
2
|
import { contextGcApplyPatchInputReplacement, contextGcPatchResultFromCompletedPayload, contextGcPatchResultFromFailedPayload, contextGcStaleReadReplacement, contextGcReadRangeCovers, contextGcCoveredReadReplacement } from '../../session/context-gc-replacements.js';
|
|
3
3
|
import { parseReadContextOutput } from '../../prompts/read-context.js';
|
|
4
|
-
import {
|
|
5
|
-
import { contextGcProtectionMature } from '../../session/context-gc-protection.js';
|
|
4
|
+
import { CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS, contextGcProtectionMature } from '../../session/context-gc-protection.js';
|
|
6
5
|
import { targetFor } from './gc-candidates.js';
|
|
7
6
|
export function collectDeterministicCandidates(input, groups, existingReplacements, existingTargets, candidateSavings) {
|
|
8
7
|
const eligible = [];
|
|
@@ -108,9 +107,9 @@ export function collectDeterministicCandidates(input, groups, existingReplacemen
|
|
|
108
107
|
mature.push(replacement);
|
|
109
108
|
observedRollouts = Math.min(observedRollouts, protection.rollouts);
|
|
110
109
|
}
|
|
111
|
-
else if (protection.rollouts <
|
|
110
|
+
else if (protection.rollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
|
|
112
111
|
awaitingRollouts.push(replacement);
|
|
113
|
-
retryAfterRollouts = Math.min(retryAfterRollouts,
|
|
112
|
+
retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - protection.rollouts);
|
|
114
113
|
}
|
|
115
114
|
}
|
|
116
115
|
if (awaitingRollouts.length) {
|
|
@@ -4,7 +4,7 @@ export declare function collectWebCandidates(input: ContextGcPlannerInput, group
|
|
|
4
4
|
protectedTokens: number;
|
|
5
5
|
retryAfterRollouts: number;
|
|
6
6
|
};
|
|
7
|
-
/** Generic successful tool results share output
|
|
7
|
+
/** Generic successful tool results share output retention rules, without inferring resource state. */
|
|
8
8
|
export declare function collectToolOutputCandidates(input: ContextGcPlannerInput, groups: ReadonlyMap<string, ToolGroup>, existingTargets: ReadonlySet<string>, candidateSavings: CandidateSavings): {
|
|
9
9
|
eligible: ContextCandidate[];
|
|
10
10
|
protectedTokens: number;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { contextGcTargetKey } from '../../session/context-gc-checkpoint.js';
|
|
2
2
|
import { completedTodoWrite, todoInputReplacement, todoSnapshotReplacement, visibleTodoSnapshot } from '../../session/context-gc-todo.js';
|
|
3
3
|
import { contextGcWebOutputReplacement, contextGcToolOutputReplacement, contextGcSkillOutputReplacement, hasGenericContextGcPreview, isGenericContextGcTool } from '../../session/context-gc-replacements.js';
|
|
4
|
-
import {
|
|
5
|
-
import { contextGcProtectionMature } from '../../session/context-gc-protection.js';
|
|
4
|
+
import { CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS, contextGcProtectionMature } from '../../session/context-gc-protection.js';
|
|
6
5
|
import { targetFor, isRecord } from './gc-candidates.js';
|
|
7
6
|
export function collectWebCandidates(input, groups, existingTargets, candidateSavings) {
|
|
8
7
|
const eligible = [];
|
|
@@ -49,14 +48,14 @@ export function collectWebCandidates(input, groups, existingTargets, candidateSa
|
|
|
49
48
|
continue;
|
|
50
49
|
if (contextGcProtectionMature(protection))
|
|
51
50
|
eligible.push(candidate);
|
|
52
|
-
else if (observedRollouts <
|
|
51
|
+
else if (observedRollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
|
|
53
52
|
protectedTokens += reclaimedTokens;
|
|
54
|
-
retryAfterRollouts = Math.min(retryAfterRollouts,
|
|
53
|
+
retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - observedRollouts);
|
|
55
54
|
}
|
|
56
55
|
}
|
|
57
56
|
return { eligible, protectedTokens, retryAfterRollouts };
|
|
58
57
|
}
|
|
59
|
-
/** Generic successful tool results share output
|
|
58
|
+
/** Generic successful tool results share output retention rules, without inferring resource state. */
|
|
60
59
|
export function collectToolOutputCandidates(input, groups, existingTargets, candidateSavings) {
|
|
61
60
|
const eligible = [];
|
|
62
61
|
let protectedTokens = 0;
|
|
@@ -260,9 +259,9 @@ export function collectToolOutputCandidates(input, groups, existingTargets, cand
|
|
|
260
259
|
continue;
|
|
261
260
|
if (contextGcProtectionMature(protection))
|
|
262
261
|
eligible.push(candidate);
|
|
263
|
-
else if (observedRollouts <
|
|
262
|
+
else if (observedRollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
|
|
264
263
|
protectedTokens += reclaimedTokens;
|
|
265
|
-
retryAfterRollouts = Math.min(retryAfterRollouts,
|
|
264
|
+
retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - observedRollouts);
|
|
266
265
|
}
|
|
267
266
|
}
|
|
268
267
|
return { eligible, protectedTokens, retryAfterRollouts };
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
-
import { ContextGcCheckpointCache,
|
|
3
|
-
import {
|
|
2
|
+
import { ContextGcCheckpointCache, contextGcReplacementKey } from '../../session/context-gc-checkpoint.js';
|
|
3
|
+
import { contextGcEligibleUsage, inspectContextGc } from './gc.js';
|
|
4
4
|
import { buildContextProjection } from './projection.js';
|
|
5
5
|
/** Only scheduling metadata survives a call; records and validation caches do not. */
|
|
6
6
|
export class ContextGcRuntime {
|
|
7
7
|
#scope = '';
|
|
8
8
|
#lastUsage;
|
|
9
|
-
#checkpoint;
|
|
10
|
-
#rolloutsSinceCheckpoint = 0;
|
|
11
9
|
#failures = 0;
|
|
12
10
|
#remaining = 0;
|
|
13
11
|
reset() {
|
|
14
12
|
this.#scope = '';
|
|
15
13
|
this.#lastUsage = undefined;
|
|
16
|
-
this.#checkpoint = undefined;
|
|
17
|
-
this.#rolloutsSinceCheckpoint = 0;
|
|
18
14
|
this.#failures = 0;
|
|
19
15
|
this.#remaining = 0;
|
|
20
16
|
}
|
|
@@ -44,40 +40,18 @@ export class ContextGcRuntime {
|
|
|
44
40
|
this.#remaining = 0;
|
|
45
41
|
}
|
|
46
42
|
this.#lastUsage = usage;
|
|
47
|
-
if (this.#checkpoint)
|
|
48
|
-
this.#rolloutsSinceCheckpoint++;
|
|
49
43
|
const maximumInterval = usage / input.contextWindowTokens >= 0.8 ? 2 : 8;
|
|
50
44
|
this.#remaining = Math.min(this.#remaining, maximumInterval - 1);
|
|
51
45
|
if (this.#remaining > 0 && !mediaRequired) {
|
|
52
46
|
this.#remaining--;
|
|
53
47
|
return false;
|
|
54
48
|
}
|
|
55
|
-
if (!mediaRequired &&
|
|
56
|
-
this.#checkpoint?.trigger.modelId === input.modelId &&
|
|
57
|
-
this.#checkpoint.trigger.contextWindowTokens === input.contextWindowTokens &&
|
|
58
|
-
!this.#checkpoint.protectedCandidatesPending &&
|
|
59
|
-
this.#rolloutsSinceCheckpoint < CONTEXT_GC_POLICY.largeExecProtectionRollouts &&
|
|
60
|
-
usage - this.#checkpoint.trigger.contextInputTokens <
|
|
61
|
-
contextGcMinimumUsageGrowth(input.contextWindowTokens))
|
|
62
|
-
return false;
|
|
63
49
|
const attempt = await attemptContextGc(input);
|
|
64
50
|
if (attempt.result) {
|
|
65
|
-
this.#checkpoint = {
|
|
66
|
-
trigger: { ...attempt.result.projection.checkpoint.trigger },
|
|
67
|
-
protectedCandidatesPending: attempt.result.projection.checkpoint.metrics.protectedCandidateTokens > 0
|
|
68
|
-
};
|
|
69
|
-
this.#rolloutsSinceCheckpoint = 0;
|
|
70
51
|
this.#failures = 0;
|
|
71
|
-
this.#remaining =
|
|
52
|
+
this.#remaining = 0;
|
|
72
53
|
}
|
|
73
54
|
else {
|
|
74
|
-
if (attempt.checkpointTrigger)
|
|
75
|
-
this.#checkpoint = {
|
|
76
|
-
trigger: { ...attempt.checkpointTrigger },
|
|
77
|
-
protectedCandidatesPending: attempt.protectedCandidatesPending === true
|
|
78
|
-
};
|
|
79
|
-
if (attempt.checkpointTrigger)
|
|
80
|
-
this.#rolloutsSinceCheckpoint = 0;
|
|
81
55
|
this.#failures = Math.min(3, this.#failures + 1);
|
|
82
56
|
this.#remaining =
|
|
83
57
|
Math.min(2 ** this.#failures, maximumInterval, attempt.retryAfterRollouts ?? Infinity) - 1;
|
|
@@ -107,30 +81,6 @@ async function attemptContextGc(input) {
|
|
|
107
81
|
records =
|
|
108
82
|
input.records ?? (await input.store.readExecutionState(input.sessionId)).contextRecords;
|
|
109
83
|
const checkpointCache = new ContextGcCheckpointCache();
|
|
110
|
-
const previous = checkpointCache.read(records);
|
|
111
|
-
if (previous) {
|
|
112
|
-
deferred = {
|
|
113
|
-
...deferred,
|
|
114
|
-
checkpointTrigger: previous.payload.trigger,
|
|
115
|
-
protectedCandidatesPending: previous.payload.metrics.protectedCandidateTokens > 0
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
if (!mediaRequired &&
|
|
119
|
-
previous?.payload.trigger.modelId === input.modelId &&
|
|
120
|
-
previous.payload.trigger.contextWindowTokens === input.contextWindowTokens) {
|
|
121
|
-
const rolloutGrowth = records.filter((record) => record.sequence > previous.record.sequence &&
|
|
122
|
-
record.type === 'model.attempt' &&
|
|
123
|
-
isRecord(record.payload) &&
|
|
124
|
-
record.payload.purpose === 'EXECUTION' &&
|
|
125
|
-
record.payload.state === 'SUCCEEDED').length;
|
|
126
|
-
const minimumRollouts = previous.payload.metrics.protectedCandidateTokens > 0
|
|
127
|
-
? 2
|
|
128
|
-
: CONTEXT_GC_POLICY.largeExecProtectionRollouts;
|
|
129
|
-
if (rolloutGrowth < minimumRollouts &&
|
|
130
|
-
contextUsage - previous.payload.trigger.contextInputTokens <
|
|
131
|
-
contextGcMinimumUsageGrowth(input.contextWindowTokens))
|
|
132
|
-
return deferred;
|
|
133
|
-
}
|
|
134
84
|
const projection = buildContextProjection(records, checkpointCache);
|
|
135
85
|
const messages = projection.entries.map((entry) => entry.message);
|
|
136
86
|
const inspection = inspectContextGc({
|
|
@@ -155,11 +105,8 @@ async function attemptContextGc(input) {
|
|
|
155
105
|
if (inspection.retryAfterRollouts !== undefined)
|
|
156
106
|
deferred = {
|
|
157
107
|
...deferred,
|
|
158
|
-
protectedCandidatesPending: true,
|
|
159
108
|
retryAfterRollouts: inspection.retryAfterRollouts
|
|
160
109
|
};
|
|
161
|
-
else if (!inspection.plan)
|
|
162
|
-
deferred = { ...deferred, protectedCandidatesPending: false };
|
|
163
110
|
const plan = inspection.plan;
|
|
164
111
|
if (!plan)
|
|
165
112
|
return deferred;
|
|
@@ -206,7 +153,6 @@ async function matchingCheckpointWasPersisted(store, sessionId, draft, records,
|
|
|
206
153
|
if (latest?.payload.boundarySequence !== draft.boundarySequence ||
|
|
207
154
|
!isDeepStrictEqual(latest.payload.base, draft.base) ||
|
|
208
155
|
!isDeepStrictEqual(latest.payload.trigger, draft.trigger) ||
|
|
209
|
-
!isDeepStrictEqual(latest.payload.economics, draft.economics) ||
|
|
210
156
|
!isDeepStrictEqual(latest.payload.inlineMedia, draft.inlineMedia) ||
|
|
211
157
|
!isDeepStrictEqual(latest.payload.metrics, draft.metrics))
|
|
212
158
|
return false;
|
|
@@ -20,7 +20,3 @@ export declare function contextGcTokenEntries(input: TokenContext, messages: rea
|
|
|
20
20
|
export declare function contextGcReclaimedTokens(input: TokenContext, before: readonly ModelMessage[], after: readonly ModelMessage[]): number;
|
|
21
21
|
export declare function contextGcPrefixTokens(input: TokenContext): number;
|
|
22
22
|
export declare function contextGcMessageTokens(input: TokenContext, messages: readonly ModelMessage[]): number;
|
|
23
|
-
export declare function contextGcCacheImpact(input: TokenContext, before: readonly ModelMessage[], after: readonly ModelMessage[], cacheRead: number, reclaimedTokens: number): {
|
|
24
|
-
preservedCachedPrefixTokens: number;
|
|
25
|
-
reclaimedCachedTokens: number;
|
|
26
|
-
};
|
|
@@ -83,24 +83,6 @@ export function contextGcPrefixTokens(input) {
|
|
|
83
83
|
export function contextGcMessageTokens(input, messages) {
|
|
84
84
|
return contextGcTokenEntries(input, messages).reduce((sum, entry) => sum + entry.tokens, 0);
|
|
85
85
|
}
|
|
86
|
-
export function contextGcCacheImpact(input, before, after, cacheRead, reclaimedTokens) {
|
|
87
|
-
const afterByKey = new Map(contextGcTokenEntries(input, after).map((entry) => [entry.key, entry]));
|
|
88
|
-
let offset = contextGcPrefixTokens(input);
|
|
89
|
-
let earliest = cacheRead;
|
|
90
|
-
let reclaimedCachedTokens = 0;
|
|
91
|
-
for (const entry of contextGcTokenEntries(input, before)) {
|
|
92
|
-
const replacement = afterByKey.get(entry.key);
|
|
93
|
-
if (entry.text !== replacement?.text) {
|
|
94
|
-
earliest = Math.min(earliest, offset);
|
|
95
|
-
reclaimedCachedTokens += Math.min(Math.max(0, entry.tokens - (replacement?.tokens ?? 0)), Math.max(0, cacheRead - offset));
|
|
96
|
-
}
|
|
97
|
-
offset += entry.tokens;
|
|
98
|
-
}
|
|
99
|
-
return {
|
|
100
|
-
preservedCachedPrefixTokens: Math.max(0, Math.min(cacheRead, earliest)),
|
|
101
|
-
reclaimedCachedTokens: Math.min(reclaimedTokens, reclaimedCachedTokens)
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
86
|
function tokensFor(input, text) {
|
|
105
87
|
const cached = input.tokenCache?.get(text);
|
|
106
88
|
if (cached !== undefined)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ContextGcPlan, type ContextGcInspection, type ContextGcPlannerInput } from './gc-contracts.js';
|
|
2
|
-
export {
|
|
2
|
+
export { contextGcMinimumReclaimedTokens, contextGcEligibleUsage } from './gc-admission.js';
|
|
3
3
|
export { type ContextGcPlan, type ContextGcInspection, type ContextGcPlannerInput } from './gc-contracts.js';
|
|
4
4
|
export declare function planContextGc(input: ContextGcPlannerInput): ContextGcPlan | undefined;
|
|
5
5
|
export declare function inspectContextGc(input: ContextGcPlannerInput): ContextGcInspection;
|