@moxxy/sdk 0.5.0 → 0.5.1
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/compactor-helpers.d.ts +22 -3
- package/dist/compactor-helpers.d.ts.map +1 -1
- package/dist/compactor-helpers.js +35 -11
- package/dist/compactor-helpers.js.map +1 -1
- package/dist/elision-helpers.d.ts.map +1 -1
- package/dist/elision-helpers.js +7 -4
- package/dist/elision-helpers.js.map +1 -1
- package/package.json +3 -3
- package/src/compactor-helpers.test.ts +105 -9
- package/src/compactor-helpers.ts +37 -11
- package/src/elision-helpers.ts +7 -4
|
@@ -15,6 +15,26 @@ import type { ModeContext } from './mode.js';
|
|
|
15
15
|
* touch the network and is safe to run on every iteration.
|
|
16
16
|
*/
|
|
17
17
|
export declare function estimateContextTokens(log: EventLogReader): number;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the active model's context window for the proactive
|
|
20
|
+
* compaction / elision thresholds.
|
|
21
|
+
*
|
|
22
|
+
* `config.model` is a free-form, unvalidated string, and providers happily
|
|
23
|
+
* serve ids that aren't in their fixed descriptor list — a newer release
|
|
24
|
+
* (`claude-opus-4-8`), a dated id, or a model registered at runtime via
|
|
25
|
+
* provider-admin. So an exact `models.find(m => m.id === ctx.model)` often
|
|
26
|
+
* MISSES, and both auto-compaction and auto-elision used to silently turn into
|
|
27
|
+
* permanent no-ops for the whole session (the context then grows unbounded and
|
|
28
|
+
* the agent "loses its context"). Falling back to the provider's first
|
|
29
|
+
* descriptor — exactly what the TUI context meter already does
|
|
30
|
+
* (`resolveContextWindow` in @moxxy/plugin-cli) — keeps both features alive on
|
|
31
|
+
* an unrecognised id. Returns null only when the provider exposes no usable
|
|
32
|
+
* window at all (no models / a zero window).
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveModelContext(ctx: ModeContext): {
|
|
35
|
+
readonly contextWindow: number;
|
|
36
|
+
readonly reserveForOutput: number;
|
|
37
|
+
} | null;
|
|
18
38
|
/**
|
|
19
39
|
* Auto-compaction hook every mode calls once per iteration, right
|
|
20
40
|
* before building messages for the next provider call. Reads the
|
|
@@ -25,9 +45,8 @@ export declare function estimateContextTokens(log: EventLogReader): number;
|
|
|
25
45
|
* already honors compaction events, so the next provider call sees
|
|
26
46
|
* the summarized prefix automatically.
|
|
27
47
|
*
|
|
28
|
-
* Designed to be tolerant: no compactor
|
|
29
|
-
*
|
|
30
|
-
* compactor bug can't kill the turn. Failures emit a non-fatal
|
|
48
|
+
* Designed to be tolerant: no compactor or a compactor throw degrade to a
|
|
49
|
+
* no-op so a compactor bug can't kill the turn. Failures emit a non-fatal
|
|
31
50
|
* `error` event for observability.
|
|
32
51
|
*/
|
|
33
52
|
export declare function runCompactionIfNeeded(ctx: ModeContext, opts?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compactor-helpers.d.ts","sourceRoot":"","sources":["../src/compactor-helpers.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,CA8BjE;AAqCD
|
|
1
|
+
{"version":3,"file":"compactor-helpers.d.ts","sourceRoot":"","sources":["../src/compactor-helpers.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,CA8BjE;AAqCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,WAAW,GACf;IAAE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAK9E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,WAAW,EAChB,IAAI,GAAE;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,OAAO,CAAC,OAAO,CAAC,CA0ElB;AAqBD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE/D"}
|
|
@@ -82,6 +82,29 @@ function safeJsonLen(v) {
|
|
|
82
82
|
return 0;
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolve the active model's context window for the proactive
|
|
87
|
+
* compaction / elision thresholds.
|
|
88
|
+
*
|
|
89
|
+
* `config.model` is a free-form, unvalidated string, and providers happily
|
|
90
|
+
* serve ids that aren't in their fixed descriptor list — a newer release
|
|
91
|
+
* (`claude-opus-4-8`), a dated id, or a model registered at runtime via
|
|
92
|
+
* provider-admin. So an exact `models.find(m => m.id === ctx.model)` often
|
|
93
|
+
* MISSES, and both auto-compaction and auto-elision used to silently turn into
|
|
94
|
+
* permanent no-ops for the whole session (the context then grows unbounded and
|
|
95
|
+
* the agent "loses its context"). Falling back to the provider's first
|
|
96
|
+
* descriptor — exactly what the TUI context meter already does
|
|
97
|
+
* (`resolveContextWindow` in @moxxy/plugin-cli) — keeps both features alive on
|
|
98
|
+
* an unrecognised id. Returns null only when the provider exposes no usable
|
|
99
|
+
* window at all (no models / a zero window).
|
|
100
|
+
*/
|
|
101
|
+
export function resolveModelContext(ctx) {
|
|
102
|
+
const descriptor = ctx.provider.models.find((m) => m.id === ctx.model) ?? ctx.provider.models[0];
|
|
103
|
+
const contextWindow = descriptor?.contextWindow;
|
|
104
|
+
if (!contextWindow || contextWindow <= 0)
|
|
105
|
+
return null;
|
|
106
|
+
return { contextWindow, reserveForOutput: descriptor?.maxOutputTokens ?? 0 };
|
|
107
|
+
}
|
|
85
108
|
/**
|
|
86
109
|
* Auto-compaction hook every mode calls once per iteration, right
|
|
87
110
|
* before building messages for the next provider call. Reads the
|
|
@@ -92,29 +115,30 @@ function safeJsonLen(v) {
|
|
|
92
115
|
* already honors compaction events, so the next provider call sees
|
|
93
116
|
* the summarized prefix automatically.
|
|
94
117
|
*
|
|
95
|
-
* Designed to be tolerant: no compactor
|
|
96
|
-
*
|
|
97
|
-
* compactor bug can't kill the turn. Failures emit a non-fatal
|
|
118
|
+
* Designed to be tolerant: no compactor or a compactor throw degrade to a
|
|
119
|
+
* no-op so a compactor bug can't kill the turn. Failures emit a non-fatal
|
|
98
120
|
* `error` event for observability.
|
|
99
121
|
*/
|
|
100
122
|
export async function runCompactionIfNeeded(ctx, opts = {}) {
|
|
101
123
|
const compactor = ctx.compactor;
|
|
102
124
|
if (!compactor)
|
|
103
125
|
return false;
|
|
104
|
-
// Resolve the active model's
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
126
|
+
// Resolve the active model's real context window (with a models[0] fallback
|
|
127
|
+
// for unlisted ids). A reactive `force` compaction must still run when the
|
|
128
|
+
// window can't be resolved — the provider has already told us the prompt
|
|
129
|
+
// overflowed — so only the proactive threshold path requires a window. The
|
|
130
|
+
// shipped compactor ignores `contextWindow` inside `compact()`, so the
|
|
131
|
+
// sentinel below only affects `shouldCompact`, which `force` bypasses.
|
|
132
|
+
const resolved = resolveModelContext(ctx);
|
|
133
|
+
if (!resolved && !opts.force)
|
|
110
134
|
return false;
|
|
111
135
|
const events = ctx.log.slice();
|
|
112
136
|
if (events.length === 0)
|
|
113
137
|
return false;
|
|
114
138
|
const budget = {
|
|
115
|
-
contextWindow,
|
|
139
|
+
contextWindow: resolved?.contextWindow ?? Number.MAX_SAFE_INTEGER,
|
|
116
140
|
estimatedTokens: estimateContextTokens(ctx.log),
|
|
117
|
-
reserveForOutput:
|
|
141
|
+
reserveForOutput: resolved?.reserveForOutput ?? 0,
|
|
118
142
|
};
|
|
119
143
|
// `force` skips the threshold gate — used reactively after the provider
|
|
120
144
|
// rejects a request for being over the context window (our estimate can
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compactor-helpers.js","sourceRoot":"","sources":["../src/compactor-helpers.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"compactor-helpers.js","sourceRoot":"","sources":["../src/compactor-helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elision-helpers.d.ts","sourceRoot":"","sources":["../src/elision-helpers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE9D;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,6BAA6B,EAAE,MAAM,CAAC;IAC/C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACjD;AAgBD,wBAAgB,sBAAsB,CAAC,CAAC,CAAC,EAAE,eAAe,GAAG,uBAAuB,CAYnF;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"elision-helpers.d.ts","sourceRoot":"","sources":["../src/elision-helpers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE9D;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,6BAA6B,EAAE,MAAM,CAAC;IAC/C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACjD;AAgBD,wBAAgB,sBAAsB,CAAC,CAAC,CAAC,EAAE,eAAe,GAAG,uBAAuB,CAYnF;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAgExE"}
|
package/dist/elision-helpers.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { estimateContextTokens } from './compactor-helpers.js';
|
|
1
|
+
import { estimateContextTokens, resolveModelContext } from './compactor-helpers.js';
|
|
2
2
|
import { toolResultBytes } from './elision-state.js';
|
|
3
3
|
const DEFAULTS = {
|
|
4
4
|
enabled: true,
|
|
@@ -30,10 +30,13 @@ export async function runElisionIfNeeded(ctx) {
|
|
|
30
30
|
const s = resolveElisionSettings(ctx.elision);
|
|
31
31
|
if (!s.enabled)
|
|
32
32
|
return;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
// Resolve the model's real context window, with a models[0] fallback for
|
|
34
|
+
// ids not in the provider's fixed descriptor list — otherwise an
|
|
35
|
+
// unrecognised model id silently disabled elision for the whole session.
|
|
36
|
+
const resolved = resolveModelContext(ctx);
|
|
37
|
+
if (!resolved)
|
|
36
38
|
return;
|
|
39
|
+
const contextWindow = resolved.contextWindow;
|
|
37
40
|
// Gate: below this fill the whole history fits comfortably — eliding would
|
|
38
41
|
// only add missed-context risk for no token benefit.
|
|
39
42
|
if (estimateContextTokens(ctx.log) < s.minContextRatioToElide * contextWindow)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elision-helpers.js","sourceRoot":"","sources":["../src/elision-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"elision-helpers.js","sourceRoot":"","sources":["../src/elision-helpers.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moxxy/sdk",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Typed public surface for the moxxy framework: event types, define* factories, lifecycle hook signatures.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"moxxy",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"typescript": "^5.7.3",
|
|
62
62
|
"vitest": "^2.1.8",
|
|
63
63
|
"zod": "^3.24.0",
|
|
64
|
-
"@moxxy/
|
|
65
|
-
"@moxxy/
|
|
64
|
+
"@moxxy/tsconfig": "0.0.0",
|
|
65
|
+
"@moxxy/vitest-preset": "0.0.0"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"build": "tsc -p tsconfig.json",
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
estimateContextTokens,
|
|
7
7
|
isContextOverflowError,
|
|
8
8
|
runCompactionIfNeeded,
|
|
9
|
+
runElisionIfNeeded,
|
|
9
10
|
type CompactorDef,
|
|
10
11
|
type EmittedEvent,
|
|
11
12
|
type EventLogReader,
|
|
@@ -131,6 +132,58 @@ describe('runCompactionIfNeeded', () => {
|
|
|
131
132
|
expect(ctx.emitted[0]).toMatchObject({ type: 'error', kind: 'retryable' });
|
|
132
133
|
});
|
|
133
134
|
|
|
135
|
+
it('still resolves a context window for a model id not in the descriptor list', async () => {
|
|
136
|
+
// Regression: an unlisted model id (e.g. a newer release the provider's
|
|
137
|
+
// fixed descriptor list doesn't enumerate) used to make shouldCompact never
|
|
138
|
+
// run, silently disabling auto-compaction for the whole session.
|
|
139
|
+
let observedWindow = -1;
|
|
140
|
+
const compactor: CompactorDef = {
|
|
141
|
+
name: 'inspect',
|
|
142
|
+
shouldCompact: (_log, budget) => {
|
|
143
|
+
observedWindow = budget.contextWindow;
|
|
144
|
+
return false;
|
|
145
|
+
},
|
|
146
|
+
compact: async () => {
|
|
147
|
+
throw new Error('should not run');
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
const ctx = makeCtx({
|
|
151
|
+
compactor,
|
|
152
|
+
model: 'claude-opus-4-7', // the listed descriptor
|
|
153
|
+
contextWindow: 800_000,
|
|
154
|
+
ctxModelId: 'claude-opus-4-8', // …but the session runs an unlisted id
|
|
155
|
+
});
|
|
156
|
+
await runCompactionIfNeeded(ctx);
|
|
157
|
+
// Falls back to models[0].contextWindow instead of bailing.
|
|
158
|
+
expect(observedWindow).toBe(800_000);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('force-compacts even when the provider exposes no usable context window', async () => {
|
|
162
|
+
// Reactive overflow recovery: the provider already said the prompt is too
|
|
163
|
+
// big, so a missing/zero window must NOT block the forced compaction.
|
|
164
|
+
const compact = vi.fn(async () => ({
|
|
165
|
+
type: 'compaction' as const,
|
|
166
|
+
replacedRange: [0, 1] as [number, number],
|
|
167
|
+
summary: 'summary',
|
|
168
|
+
tokensSaved: 500,
|
|
169
|
+
}));
|
|
170
|
+
const compactor: CompactorDef = { name: 'gated', shouldCompact: () => false, compact };
|
|
171
|
+
const ctx = makeCtx({ compactor, emptyModels: true });
|
|
172
|
+
const did = await runCompactionIfNeeded(ctx, { force: true });
|
|
173
|
+
expect(did).toBe(true);
|
|
174
|
+
expect(compact).toHaveBeenCalledOnce();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('is a no-op (non-force) when the provider exposes no usable context window', async () => {
|
|
178
|
+
const compact = vi.fn();
|
|
179
|
+
const shouldCompact = vi.fn().mockReturnValue(true);
|
|
180
|
+
const ctx = makeCtx({ compactor: { name: 'x', shouldCompact, compact }, emptyModels: true });
|
|
181
|
+
const did = await runCompactionIfNeeded(ctx);
|
|
182
|
+
expect(did).toBe(false);
|
|
183
|
+
expect(shouldCompact).not.toHaveBeenCalled();
|
|
184
|
+
expect(compact).not.toHaveBeenCalled();
|
|
185
|
+
});
|
|
186
|
+
|
|
134
187
|
it('force compacts even when shouldCompact returns false', async () => {
|
|
135
188
|
const compact = vi.fn(async () => ({
|
|
136
189
|
type: 'compaction' as const,
|
|
@@ -172,11 +225,50 @@ describe('isContextOverflowError', () => {
|
|
|
172
225
|
});
|
|
173
226
|
});
|
|
174
227
|
|
|
228
|
+
describe('runElisionIfNeeded', () => {
|
|
229
|
+
// Six completed turns of bulky prompts, none matching ctx.turnId (so all are
|
|
230
|
+
// "completed"). With keepRecentTurns=4 the two oldest turns are elidable.
|
|
231
|
+
const bulkyTurns: MoxxyEvent[] = Array.from({ length: 6 }, (_, i) =>
|
|
232
|
+
event(i, {
|
|
233
|
+
type: 'user_prompt',
|
|
234
|
+
turnId: asTurnId(`turn-${i}`),
|
|
235
|
+
source: 'user',
|
|
236
|
+
text: 'x'.repeat(400),
|
|
237
|
+
}),
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
it('elides old turns for a model id not in the descriptor list', async () => {
|
|
241
|
+
// Regression: an unlisted model id used to disable elision entirely, so the
|
|
242
|
+
// context grew unbounded and the agent lost its earlier context. With the
|
|
243
|
+
// models[0] fallback the elision high-water mark advances as expected.
|
|
244
|
+
const ctx = makeCtx({
|
|
245
|
+
compactor: null,
|
|
246
|
+
events: bulkyTurns,
|
|
247
|
+
model: 'listed-model',
|
|
248
|
+
ctxModelId: 'unlisted-model-xyz',
|
|
249
|
+
contextWindow: 1_000, // estimate (~600 tok) is well over 0.3 * window (300)
|
|
250
|
+
});
|
|
251
|
+
await runElisionIfNeeded(ctx);
|
|
252
|
+
expect(ctx.emitted.some((e) => e.type === 'elision')).toBe(true);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('is a no-op when the provider exposes no usable context window', async () => {
|
|
256
|
+
const ctx = makeCtx({ compactor: null, events: bulkyTurns, emptyModels: true });
|
|
257
|
+
await runElisionIfNeeded(ctx);
|
|
258
|
+
expect(ctx.emitted).toHaveLength(0);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
175
262
|
interface MakeCtxOpts {
|
|
176
263
|
readonly compactor: CompactorDef | null;
|
|
177
264
|
readonly model?: string;
|
|
178
265
|
readonly contextWindow?: number;
|
|
179
266
|
readonly events?: ReadonlyArray<MoxxyEvent>;
|
|
267
|
+
/** Set ctx.model to an id the provider's descriptor list does NOT contain,
|
|
268
|
+
* so `models.find(...)` misses and the models[0] fallback must kick in. */
|
|
269
|
+
readonly ctxModelId?: string;
|
|
270
|
+
/** Give the provider an empty descriptor list (no usable context window). */
|
|
271
|
+
readonly emptyModels?: boolean;
|
|
180
272
|
}
|
|
181
273
|
|
|
182
274
|
function makeCtx(opts: MakeCtxOpts): ModeContext & { emitted: EmittedEvent[] } {
|
|
@@ -186,14 +278,16 @@ function makeCtx(opts: MakeCtxOpts): ModeContext & { emitted: EmittedEvent[] } {
|
|
|
186
278
|
const log = reader(events);
|
|
187
279
|
const provider = {
|
|
188
280
|
name: 'fake',
|
|
189
|
-
models:
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
281
|
+
models: opts.emptyModels
|
|
282
|
+
? []
|
|
283
|
+
: [
|
|
284
|
+
{
|
|
285
|
+
id: opts.model ?? 'fake-model',
|
|
286
|
+
contextWindow: opts.contextWindow ?? 100_000,
|
|
287
|
+
supportsTools: true,
|
|
288
|
+
supportsStreaming: true,
|
|
289
|
+
},
|
|
290
|
+
],
|
|
197
291
|
stream: async function* () { /* unused */ },
|
|
198
292
|
countTokens: async () => 0,
|
|
199
293
|
} as unknown as LLMProvider;
|
|
@@ -202,7 +296,9 @@ function makeCtx(opts: MakeCtxOpts): ModeContext & { emitted: EmittedEvent[] } {
|
|
|
202
296
|
const ctx = {
|
|
203
297
|
sessionId: sid,
|
|
204
298
|
turnId: tid,
|
|
205
|
-
|
|
299
|
+
// ctx.model may intentionally differ from the descriptor id to exercise the
|
|
300
|
+
// unlisted-model fallback.
|
|
301
|
+
model: opts.ctxModelId ?? opts.model ?? 'fake-model',
|
|
206
302
|
provider,
|
|
207
303
|
tools: { list: () => [], get: () => undefined, execute: async () => undefined },
|
|
208
304
|
skills: { list: () => [], get: () => undefined, byName: () => undefined, filterByTriggers: () => [] },
|
package/src/compactor-helpers.ts
CHANGED
|
@@ -91,6 +91,31 @@ function safeJsonLen(v: unknown): number {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Resolve the active model's context window for the proactive
|
|
96
|
+
* compaction / elision thresholds.
|
|
97
|
+
*
|
|
98
|
+
* `config.model` is a free-form, unvalidated string, and providers happily
|
|
99
|
+
* serve ids that aren't in their fixed descriptor list — a newer release
|
|
100
|
+
* (`claude-opus-4-8`), a dated id, or a model registered at runtime via
|
|
101
|
+
* provider-admin. So an exact `models.find(m => m.id === ctx.model)` often
|
|
102
|
+
* MISSES, and both auto-compaction and auto-elision used to silently turn into
|
|
103
|
+
* permanent no-ops for the whole session (the context then grows unbounded and
|
|
104
|
+
* the agent "loses its context"). Falling back to the provider's first
|
|
105
|
+
* descriptor — exactly what the TUI context meter already does
|
|
106
|
+
* (`resolveContextWindow` in @moxxy/plugin-cli) — keeps both features alive on
|
|
107
|
+
* an unrecognised id. Returns null only when the provider exposes no usable
|
|
108
|
+
* window at all (no models / a zero window).
|
|
109
|
+
*/
|
|
110
|
+
export function resolveModelContext(
|
|
111
|
+
ctx: ModeContext,
|
|
112
|
+
): { readonly contextWindow: number; readonly reserveForOutput: number } | null {
|
|
113
|
+
const descriptor = ctx.provider.models.find((m) => m.id === ctx.model) ?? ctx.provider.models[0];
|
|
114
|
+
const contextWindow = descriptor?.contextWindow;
|
|
115
|
+
if (!contextWindow || contextWindow <= 0) return null;
|
|
116
|
+
return { contextWindow, reserveForOutput: descriptor?.maxOutputTokens ?? 0 };
|
|
117
|
+
}
|
|
118
|
+
|
|
94
119
|
/**
|
|
95
120
|
* Auto-compaction hook every mode calls once per iteration, right
|
|
96
121
|
* before building messages for the next provider call. Reads the
|
|
@@ -101,9 +126,8 @@ function safeJsonLen(v: unknown): number {
|
|
|
101
126
|
* already honors compaction events, so the next provider call sees
|
|
102
127
|
* the summarized prefix automatically.
|
|
103
128
|
*
|
|
104
|
-
* Designed to be tolerant: no compactor
|
|
105
|
-
*
|
|
106
|
-
* compactor bug can't kill the turn. Failures emit a non-fatal
|
|
129
|
+
* Designed to be tolerant: no compactor or a compactor throw degrade to a
|
|
130
|
+
* no-op so a compactor bug can't kill the turn. Failures emit a non-fatal
|
|
107
131
|
* `error` event for observability.
|
|
108
132
|
*/
|
|
109
133
|
export async function runCompactionIfNeeded(
|
|
@@ -113,20 +137,22 @@ export async function runCompactionIfNeeded(
|
|
|
113
137
|
const compactor = ctx.compactor;
|
|
114
138
|
if (!compactor) return false;
|
|
115
139
|
|
|
116
|
-
// Resolve the active model's
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
140
|
+
// Resolve the active model's real context window (with a models[0] fallback
|
|
141
|
+
// for unlisted ids). A reactive `force` compaction must still run when the
|
|
142
|
+
// window can't be resolved — the provider has already told us the prompt
|
|
143
|
+
// overflowed — so only the proactive threshold path requires a window. The
|
|
144
|
+
// shipped compactor ignores `contextWindow` inside `compact()`, so the
|
|
145
|
+
// sentinel below only affects `shouldCompact`, which `force` bypasses.
|
|
146
|
+
const resolved = resolveModelContext(ctx);
|
|
147
|
+
if (!resolved && !opts.force) return false;
|
|
122
148
|
|
|
123
149
|
const events = ctx.log.slice();
|
|
124
150
|
if (events.length === 0) return false;
|
|
125
151
|
|
|
126
152
|
const budget = {
|
|
127
|
-
contextWindow,
|
|
153
|
+
contextWindow: resolved?.contextWindow ?? Number.MAX_SAFE_INTEGER,
|
|
128
154
|
estimatedTokens: estimateContextTokens(ctx.log),
|
|
129
|
-
reserveForOutput:
|
|
155
|
+
reserveForOutput: resolved?.reserveForOutput ?? 0,
|
|
130
156
|
} as const;
|
|
131
157
|
|
|
132
158
|
// `force` skips the threshold gate — used reactively after the provider
|
package/src/elision-helpers.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { estimateContextTokens } from './compactor-helpers.js';
|
|
1
|
+
import { estimateContextTokens, resolveModelContext } from './compactor-helpers.js';
|
|
2
2
|
import { toolResultBytes } from './elision-state.js';
|
|
3
3
|
import type { EmittedEvent, MoxxyEvent } from './events.js';
|
|
4
4
|
import type { ElisionSettings, ModeContext } from './mode.js';
|
|
@@ -60,9 +60,12 @@ export async function runElisionIfNeeded(ctx: ModeContext): Promise<void> {
|
|
|
60
60
|
const s = resolveElisionSettings(ctx.elision);
|
|
61
61
|
if (!s.enabled) return;
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
// Resolve the model's real context window, with a models[0] fallback for
|
|
64
|
+
// ids not in the provider's fixed descriptor list — otherwise an
|
|
65
|
+
// unrecognised model id silently disabled elision for the whole session.
|
|
66
|
+
const resolved = resolveModelContext(ctx);
|
|
67
|
+
if (!resolved) return;
|
|
68
|
+
const contextWindow = resolved.contextWindow;
|
|
66
69
|
|
|
67
70
|
// Gate: below this fill the whole history fits comfortably — eliding would
|
|
68
71
|
// only add missed-context risk for no token benefit.
|