feat: implemented long-context pricing and configuration support

- Added long-context pricing tiers and billing policies for subscription Codex models in the catalog.
- Introduced the `extendedContext` configuration setting to control premium long-context windows.
- Implemented runtime policy refresh and model re-binding when context settings change.
- Added comprehensive unit tests for pricing tiers, context capping, and policy toggling behavior.
This commit is contained in:
can1357
2026-08-20 02:47:32 +02:00
parent 577c0d795f
commit 6d2bae2c41
11 changed files with 2842 additions and 899 deletions
@@ -105,7 +105,7 @@ import type { ResolvedModelRoleValue } from "../config/model-resolver";
import { expandPromptTemplate, type PromptTemplate } from "../config/prompt-templates";
import { buildServiceTierByFamily } from "../config/service-tier";
import type { Settings, SkillsSettings } from "../config/settings";
import { onAppendOnlyModeChanged, onModelRolesChanged } from "../config/settings";
import { onAppendOnlyModeChanged, onExtendedContextChanged, onModelRolesChanged } from "../config/settings";
import { RawSseDebugBuffer } from "../debug/raw-sse-buffer";
import { getFileSnapshotStore } from "../edit/file-snapshot-store";
import type { PythonResult } from "../eval/py/executor";
@@ -489,6 +489,7 @@ export class AgentSession {
#exitRecorded = false;
#unsubscribeAppendOnly?: () => void;
#unsubscribeModelRoles?: () => void;
#unsubscribeExtendedContext?: () => void;
/** Last (enable, providerId) tuple resolved by `#syncAppendOnlyContext` — used to skip no-op invalidations. */
#lastAppendOnlyResolution?: { enable: boolean; providerId: string | undefined };
#eventListeners: AgentSessionEventListener[] = [];
@@ -1638,6 +1639,11 @@ export class AgentSession {
// Re-evaluate append-only context mode when the setting changes at runtime.
this.#unsubscribeAppendOnly = onAppendOnlyModeChanged(_value => this.#syncAppendOnlyContext(this.model));
this.#unsubscribeModelRoles = onModelRolesChanged(() => this.#advisors.onModelRolesChanged());
// Re-derive the active model's effective context window when the
// extended-context setting flips at runtime: the registry re-clamps (or
// restores) premium long-context windows, and the live model object must
// follow so compaction thresholds and context display react immediately.
this.#unsubscribeExtendedContext = onExtendedContextChanged(() => void this.#reapplyExtendedContextPolicy());
}
/** Model registry for API key resolution and model discovery */
get modelRegistry(): ModelRegistry {
@@ -4094,6 +4100,10 @@ export class AgentSession {
this.#unsubscribeModelRoles();
this.#unsubscribeModelRoles = undefined;
}
if (this.#unsubscribeExtendedContext) {
this.#unsubscribeExtendedContext();
this.#unsubscribeExtendedContext = undefined;
}
this.#eventListeners = [];
this.#runStateListeners.clear();
this.#sessionChangeCallbacks.clear();
@@ -7319,6 +7329,26 @@ export class AgentSession {
return true;
}
/**
* Rebuild the model catalog after an `extendedContext` toggle and rebind the
* active model when its effective context window changed. Same-model rebinds
* skip provider-session resets (`modelsAreEqual` sees no change), so this
* only refreshes metadata consumers (compaction thresholds, context display).
*/
async #reapplyExtendedContextPolicy(): Promise<void> {
try {
await this.#modelRegistry.reapplyModelPolicies();
const currentModel = this.model;
if (!currentModel || this.#isDisposed) return;
const updated = this.#modelRegistry.find(currentModel.provider, currentModel.id);
if (updated && updated.contextWindow !== currentModel.contextWindow) {
await this.#setModelWithProviderSessionReset(updated);
}
} catch (error) {
logger.warn("extended-context policy reapply failed", { error: String(error) });
}
}
async #setModelWithProviderSessionReset(model: Model): Promise<void> {
const currentModel = this.model;
const isChanging = !currentModel || !modelsAreEqual(currentModel, model);