@oh-my-pi/pi-agent-core 18.8.7 → 18.8.8
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/CHANGELOG.md +13 -0
- package/dist/types/agent.d.ts +6 -0
- package/dist/types/compaction/compaction.d.ts +8 -0
- package/package.json +8 -8
- package/src/agent.ts +15 -2
- package/src/compaction/compaction.ts +33 -10
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.8.8] - 2026-10-10
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `Agent.setModelResolver()` to fit every model an agent adopts (via `setModel`, starting with the current one) before it is used ([#15048](https://github.com/can1357/oh-my-pi/pull/15048) by [@H4vC](https://github.com/H4vC))
|
|
10
|
+
- Added `CompactionSettings.baseWindowTokens`: when set, `resolveThresholdTokens()` scales its percentage or reserve-based threshold from that base instead of the full context window ([#15065](https://github.com/can1357/oh-my-pi/pull/15065) by [@H4vC](https://github.com/H4vC))
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Fixed OpenAI V1 remote compaction re-attaching a prior Anthropic native payload after a successful compact ([#15041](https://github.com/can1357/oh-my-pi/pull/15041) by [@PaleRoses](https://github.com/PaleRoses))
|
|
15
|
+
- Fixed `resolveThresholdTokens()` clamping a positive `thresholdTokens` to `baseWindowTokens`; a fixed threshold is checked against the real window, and the base only rescales the percentage and reserve policies ([#15074](https://github.com/can1357/oh-my-pi/pull/15074) by [@H4vC](https://github.com/H4vC))
|
|
16
|
+
- Fixed `resolveThresholdTokens()` clamping a fixed `thresholdTokens` at or past the context window to one token below it, so compaction fired only after the next request overflowed; it now clamps to the window less the reserve ([#15146](https://github.com/can1357/oh-my-pi/pull/15146) by [@will-bogusz](https://github.com/will-bogusz))
|
|
17
|
+
|
|
5
18
|
## [18.8.6] - 2026-10-08
|
|
6
19
|
|
|
7
20
|
### Added
|
package/dist/types/agent.d.ts
CHANGED
|
@@ -469,6 +469,12 @@ export declare class Agent {
|
|
|
469
469
|
emitExternalEvent(event: AgentEvent): void;
|
|
470
470
|
setSystemPrompt(v: string[] | string): void;
|
|
471
471
|
setModel(model: Model): void;
|
|
472
|
+
/**
|
|
473
|
+
* Route every model this agent adopts through `resolver` (e.g. to fit a
|
|
474
|
+
* shared catalog row to this agent's own settings), starting with the
|
|
475
|
+
* current one; `undefined` adopts models as given from now on.
|
|
476
|
+
*/
|
|
477
|
+
setModelResolver(resolver: ((model: Model) => Model) | undefined): void;
|
|
472
478
|
setThinkingLevel(l: Effort | undefined): void;
|
|
473
479
|
setDisableReasoning(disabled: boolean): void;
|
|
474
480
|
setSteeringMode(mode: "all" | "one-at-a-time"): void;
|
|
@@ -34,6 +34,14 @@ export interface CompactionSettings {
|
|
|
34
34
|
strategy?: "context-full" | "handoff" | "shake" | "snapcompact" | "off";
|
|
35
35
|
thresholdPercent?: number;
|
|
36
36
|
thresholdTokens?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Stands in for the context window when the percentage or reserve-based
|
|
39
|
+
* threshold is computed (when `> 0` and smaller than the window); a positive
|
|
40
|
+
* `thresholdTokens` still wins and is checked against the real window (at
|
|
41
|
+
* or past it, the window less its reserve applies). Set by per-model
|
|
42
|
+
* compaction limits; request and overflow budgets keep the real window.
|
|
43
|
+
*/
|
|
44
|
+
baseWindowTokens?: number;
|
|
37
45
|
midTurnEnabled?: boolean;
|
|
38
46
|
/**
|
|
39
47
|
* Tokens reserved below the context window for the next prompt + response.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-agent-core",
|
|
4
|
-
"version": "18.8.
|
|
4
|
+
"version": "18.8.8",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": {
|
|
@@ -38,16 +38,16 @@
|
|
|
38
38
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@oh-my-pi/pi-ai": "18.8.
|
|
42
|
-
"@oh-my-pi/pi-catalog": "18.8.
|
|
43
|
-
"@oh-my-pi/pi-natives": "18.8.
|
|
44
|
-
"@oh-my-pi/pi-utils": "18.8.
|
|
45
|
-
"@oh-my-pi/pi-wire": "18.8.
|
|
46
|
-
"@oh-my-pi/snapcompact": "18.8.
|
|
41
|
+
"@oh-my-pi/pi-ai": "18.8.8",
|
|
42
|
+
"@oh-my-pi/pi-catalog": "18.8.8",
|
|
43
|
+
"@oh-my-pi/pi-natives": "18.8.8",
|
|
44
|
+
"@oh-my-pi/pi-utils": "18.8.8",
|
|
45
|
+
"@oh-my-pi/pi-wire": "18.8.8",
|
|
46
|
+
"@oh-my-pi/snapcompact": "18.8.8",
|
|
47
47
|
"@opentelemetry/api": "^1.9.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@oh-my-pi/omptype": "18.8.
|
|
50
|
+
"@oh-my-pi/omptype": "18.8.8",
|
|
51
51
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
|
52
52
|
"@opentelemetry/sdk-trace-base": "^2.9.0",
|
|
53
53
|
"@types/bun": "^1.3.14"
|
package/src/agent.ts
CHANGED
|
@@ -453,6 +453,7 @@ export class Agent {
|
|
|
453
453
|
#promptCacheKey?: string;
|
|
454
454
|
#metadata?: Record<string, unknown>;
|
|
455
455
|
#metadataResolver?: (provider: string) => Record<string, unknown> | undefined;
|
|
456
|
+
#modelResolver?: (model: Model) => Model;
|
|
456
457
|
#providerSessionState?: Map<string, ProviderSessionState>;
|
|
457
458
|
#thinkingBudgets?: ThinkingBudgets;
|
|
458
459
|
#temperature?: number;
|
|
@@ -1213,8 +1214,20 @@ export class Agent {
|
|
|
1213
1214
|
}
|
|
1214
1215
|
|
|
1215
1216
|
setModel(model: Model) {
|
|
1216
|
-
|
|
1217
|
-
this.#
|
|
1217
|
+
// Sessions may start with no model selected (`initialState.model: undefined`).
|
|
1218
|
+
const resolved = this.#modelResolver && model ? this.#modelResolver(model) : model;
|
|
1219
|
+
this.#state.model = resolved;
|
|
1220
|
+
this.#syncTokenizer(resolved);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* Route every model this agent adopts through `resolver` (e.g. to fit a
|
|
1225
|
+
* shared catalog row to this agent's own settings), starting with the
|
|
1226
|
+
* current one; `undefined` adopts models as given from now on.
|
|
1227
|
+
*/
|
|
1228
|
+
setModelResolver(resolver: ((model: Model) => Model) | undefined): void {
|
|
1229
|
+
this.#modelResolver = resolver;
|
|
1230
|
+
if (resolver && this.#state.model) this.setModel(this.#state.model);
|
|
1218
1231
|
}
|
|
1219
1232
|
|
|
1220
1233
|
setThinkingLevel(l: Effort | undefined) {
|
|
@@ -163,6 +163,14 @@ export interface CompactionSettings {
|
|
|
163
163
|
strategy?: "context-full" | "handoff" | "shake" | "snapcompact" | "off";
|
|
164
164
|
thresholdPercent?: number;
|
|
165
165
|
thresholdTokens?: number;
|
|
166
|
+
/**
|
|
167
|
+
* Stands in for the context window when the percentage or reserve-based
|
|
168
|
+
* threshold is computed (when `> 0` and smaller than the window); a positive
|
|
169
|
+
* `thresholdTokens` still wins and is checked against the real window (at
|
|
170
|
+
* or past it, the window less its reserve applies). Set by per-model
|
|
171
|
+
* compaction limits; request and overflow budgets keep the real window.
|
|
172
|
+
*/
|
|
173
|
+
baseWindowTokens?: number;
|
|
166
174
|
midTurnEnabled?: boolean;
|
|
167
175
|
/**
|
|
168
176
|
* Tokens reserved below the context window for the next prompt + response.
|
|
@@ -358,27 +366,42 @@ export function compactionContextTokens(providerContextTokens: number, storedCon
|
|
|
358
366
|
return Math.max(Math.max(0, providerContextTokens), Math.max(0, storedConversationEstimate));
|
|
359
367
|
}
|
|
360
368
|
|
|
369
|
+
/**
|
|
370
|
+
* The prompt budget below `contextWindow`: the window less
|
|
371
|
+
* {@link resolveBudgetReserveTokens}, capped at `contextWindow - 1` so it never
|
|
372
|
+
* reaches the whole window even when the reserve resolves to 0.
|
|
373
|
+
*/
|
|
374
|
+
function promptBudgetTokens(contextWindow: number, settings: CompactionSettings): number {
|
|
375
|
+
return Math.max(0, Math.min(contextWindow - 1, contextWindow - resolveBudgetReserveTokens(contextWindow, settings)));
|
|
376
|
+
}
|
|
377
|
+
|
|
361
378
|
export function resolveThresholdTokens(contextWindow: number, settings: CompactionSettings): number {
|
|
362
|
-
// Fixed token limit takes priority over percentage
|
|
379
|
+
// Fixed token limit takes priority over percentage, and is checked against
|
|
380
|
+
// the real window: `baseWindowTokens` only rescales the policies below.
|
|
363
381
|
const thresholdTokens = settings.thresholdTokens;
|
|
364
382
|
if (typeof thresholdTokens === "number" && Number.isFinite(thresholdTokens) && thresholdTokens > 0) {
|
|
365
|
-
//
|
|
383
|
+
// A trigger at or past the window can never fire before the request
|
|
384
|
+
// overflows (a provider may cap the window below the configured point);
|
|
385
|
+
// fall back to the window's prompt budget, as the reserve policy does.
|
|
386
|
+
// A trigger below the window is exact, as the `f` model entries promise,
|
|
387
|
+
// even one inside the reserve: the user chose that headroom. The step at
|
|
388
|
+
// the window is inherent; a monotone fallback would sit at window - 1.
|
|
389
|
+
if (thresholdTokens >= contextWindow) return promptBudgetTokens(contextWindow, settings);
|
|
366
390
|
return Math.min(contextWindow - 1, Math.max(1, thresholdTokens));
|
|
367
391
|
}
|
|
392
|
+
const baseWindowTokens = settings.baseWindowTokens;
|
|
393
|
+
if (typeof baseWindowTokens === "number" && Number.isFinite(baseWindowTokens) && baseWindowTokens > 0) {
|
|
394
|
+
contextWindow = Math.min(contextWindow, baseWindowTokens);
|
|
395
|
+
}
|
|
368
396
|
|
|
369
397
|
// Percentage-based threshold. The default absolute reserve can exceed bundled
|
|
370
398
|
// small-context windows, or nearly consume a 16k-class window; in those
|
|
371
399
|
// known-impossible default configurations, fall back to the proportional
|
|
372
400
|
// reserve so threshold/recovery-band checks stay usable. Explicit valid
|
|
373
|
-
// configured reserves still define the usable prompt budget.
|
|
374
|
-
// contextWindow - 1 (matching the fixed-token clamp above) so the threshold
|
|
375
|
-
// never reaches the whole window even when the reserve resolves to 0.
|
|
401
|
+
// configured reserves still define the usable prompt budget.
|
|
376
402
|
const thresholdPercent = settings.thresholdPercent;
|
|
377
403
|
if (typeof thresholdPercent !== "number" || !Number.isFinite(thresholdPercent) || thresholdPercent <= 0) {
|
|
378
|
-
return
|
|
379
|
-
0,
|
|
380
|
-
Math.min(contextWindow - 1, contextWindow - resolveBudgetReserveTokens(contextWindow, settings)),
|
|
381
|
-
);
|
|
404
|
+
return promptBudgetTokens(contextWindow, settings);
|
|
382
405
|
}
|
|
383
406
|
const clampedThresholdPercent = Math.min(99, Math.max(1, thresholdPercent));
|
|
384
407
|
return Math.floor(contextWindow * (clampedThresholdPercent / 100));
|
|
@@ -1845,7 +1868,7 @@ export async function compact(
|
|
|
1845
1868
|
),
|
|
1846
1869
|
{ signal },
|
|
1847
1870
|
);
|
|
1848
|
-
preserveData = withOpenAiRemoteCompactionPreserveData(
|
|
1871
|
+
preserveData = withOpenAiRemoteCompactionPreserveData(preserveData, remote);
|
|
1849
1872
|
usedRemoteCompaction = true;
|
|
1850
1873
|
} catch (err) {
|
|
1851
1874
|
// A user/session abort is a cancellation, not a remote failure —
|