sidekick-shared 0.24.1 → 0.24.2
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/README.md +23 -14
- package/dist/index.d.ts +3 -1
- package/dist/index.js +8 -2
- package/dist/modelContext.d.ts +50 -3
- package/dist/modelContext.js +133 -16
- package/dist/modelInfo.js +51 -6
- package/dist/node.d.ts +3 -1
- package/dist/node.js +6 -1
- package/dist/observedContextWindows.d.ts +54 -0
- package/dist/observedContextWindows.js +150 -0
- package/dist/pricingCatalog.d.ts +12 -0
- package/dist/pricingCatalog.js +83 -22
- package/dist/providers/codex.js +13 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,17 +69,17 @@ npm install sidekick-shared
|
|
|
69
69
|
|
|
70
70
|
`sidekick-shared` ships three public entry points plus a few convenience subpaths. Pick the one that matches your runtime.
|
|
71
71
|
|
|
72
|
-
| Path | Runtime | What it exposes
|
|
73
|
-
| ------------------------------ | -------------------------- |
|
|
74
|
-
| `sidekick-shared` | Node (CLI, extension host) | Full public API (readers, providers, parsers, pricing, …).
|
|
75
|
-
| `sidekick-shared/browser` | **Browser / webview** | Pure helpers: usage normalization/pricing, token estimation, transcript and assistant-turn projection, model/context math.
|
|
76
|
-
| `sidekick-shared/node` | Node only | LiteLLM pricing hydration, recent-session/transcript reads, and resilient observed-session collection (`fs` + providers).
|
|
77
|
-
| `sidekick-shared/phrases` | Any runtime | Phrase arrays + `getRandomPhrase()`.
|
|
78
|
-
| `sidekick-shared/modelContext` | Any runtime | Direct access to the context-window module.
|
|
79
|
-
| `sidekick-shared/modelInfo` | Any runtime | Direct access to model parsing and cost math.
|
|
80
|
-
| `sidekick-shared/formatting` | Any runtime | Direct access to pure token and duration display helpers.
|
|
81
|
-
| `sidekick-shared/schemas` | Any runtime | Pure Zod boundary schemas (session events, assistant turns, observed-session V1, quota, account status, quota history) — fs-free, no Node builtins.
|
|
82
|
-
| `sidekick-shared/statusline` | Node only | Cache-only statusline surface: account status + quota snapshot readers, `formatStatusline()`, and burn-rate estimates.
|
|
72
|
+
| Path | Runtime | What it exposes |
|
|
73
|
+
| ------------------------------ | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
74
|
+
| `sidekick-shared` | Node (CLI, extension host) | Full public API (readers, providers, parsers, pricing, …). |
|
|
75
|
+
| `sidekick-shared/browser` | **Browser / webview** | Pure helpers: usage normalization/pricing, token estimation, transcript and assistant-turn projection, model/context math. |
|
|
76
|
+
| `sidekick-shared/node` | Node only | LiteLLM pricing and context-window hydration, observed context-window persistence, recent-session/transcript reads, and resilient observed-session collection (`fs` + providers). |
|
|
77
|
+
| `sidekick-shared/phrases` | Any runtime | Phrase arrays + `getRandomPhrase()`. |
|
|
78
|
+
| `sidekick-shared/modelContext` | Any runtime | Direct access to the context-window module. |
|
|
79
|
+
| `sidekick-shared/modelInfo` | Any runtime | Direct access to model parsing and cost math. |
|
|
80
|
+
| `sidekick-shared/formatting` | Any runtime | Direct access to pure token and duration display helpers. |
|
|
81
|
+
| `sidekick-shared/schemas` | Any runtime | Pure Zod boundary schemas (session events, assistant turns, observed-session V1, quota, account status, quota history) — fs-free, no Node builtins. |
|
|
82
|
+
| `sidekick-shared/statusline` | Node only | Cache-only statusline surface: account status + quota snapshot readers, `formatStatusline()`, and burn-rate estimates. |
|
|
83
83
|
|
|
84
84
|
### Browser / webview runtimes
|
|
85
85
|
|
|
@@ -103,14 +103,23 @@ import {
|
|
|
103
103
|
|
|
104
104
|
### Node / CLI / extension host
|
|
105
105
|
|
|
106
|
-
Hydrate the
|
|
106
|
+
Hydrate the model catalog from the `node` subpath. One fetch covers both prices and context window sizes:
|
|
107
107
|
|
|
108
108
|
```typescript
|
|
109
|
-
import { hydratePricingCatalog } from 'sidekick-shared/node';
|
|
109
|
+
import { hydratePricingCatalog, loadObservedContextWindows } from 'sidekick-shared/node';
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
const { entries, contextWindowEntries } = await hydratePricingCatalog({
|
|
112
|
+
cacheDir: '~/.config/sidekick',
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Context windows a provider reported on an earlier run. These outrank the
|
|
116
|
+
// catalog, which only knows each model's published maximum — Codex reports the
|
|
117
|
+
// window your account tier actually gets. Local read, offline-safe.
|
|
118
|
+
await loadObservedContextWindows();
|
|
112
119
|
```
|
|
113
120
|
|
|
121
|
+
Both calls always resolve; a network failure, missing cache, or malformed store leaves lookups on the static tables. To record a window a provider reports at runtime, call `recordObservedContextWindow(modelId, window)` — it persists only when the value changes.
|
|
122
|
+
|
|
114
123
|
### Normalize and price provider usage
|
|
115
124
|
|
|
116
125
|
```typescript
|
package/dist/index.d.ts
CHANGED
|
@@ -164,8 +164,10 @@ export type { StatuslineInput, StatuslineSelection } from './statusline/formatte
|
|
|
164
164
|
export { getModelContextWindowSize, DEFAULT_CONTEXT_WINDOW } from './modelContext';
|
|
165
165
|
export { parseModelId, getModelPricing, getModelInfo, calculateCost, calculateCostWithPricing, calculateCostWithProvenance, mergeCostSources, shortModelName, getModelDisplayInfo, compareModelIds, sortModelIds, formatCost, } from './modelInfo';
|
|
166
166
|
export type { ModelPricing, CostTokenUsage, CostSource, CostProvenanceInput, CostWithProvenance, ModelInfo, ModelProvider, ParsedModelId, ModelDisplayInfo, } from './modelInfo';
|
|
167
|
-
export { hydratePricingCatalog, normalizeLiteLlmCatalog, LITELLM_CATALOG_URL, } from './pricingCatalog';
|
|
167
|
+
export { hydratePricingCatalog, normalizeLiteLlmCatalog, normalizeLiteLlmContextWindows, LITELLM_CATALOG_URL, } from './pricingCatalog';
|
|
168
168
|
export type { HydrateOptions, HydrateResult } from './pricingCatalog';
|
|
169
|
+
export { loadObservedContextWindows, recordObservedContextWindow, getObservedContextWindowPath, } from './observedContextWindows';
|
|
170
|
+
export type { ObservedContextWindowOptions, ObservedContextWindowStore, } from './observedContextWindows';
|
|
169
171
|
export { extractTokenUsage } from './extractors/tokenUsage';
|
|
170
172
|
export { extractUrls, extractFilePaths, extractCommands } from './extractors/sessionAssets';
|
|
171
173
|
export type { AssetAgent, ExtractedAsset, ExtractedAssetProvenance, ExtractedAssetType, ExtractedAssets, SourceAssets, } from './extractors/sessionAssets';
|
package/dist/index.js
CHANGED
|
@@ -8,8 +8,8 @@ exports.createSessionContextProjector = exports.calculateSessionContextPressure
|
|
|
8
8
|
exports.scoreSessionQuality = exports.calculateQualityTrend = exports.PatternExtractor = exports.HeatmapTracker = exports.FrequencyTracker = exports.readErrorHistory = exports.getTopFailingTools = exports.appendErrorHistory = exports.MAX_ERROR_HISTORY_SESSIONS = exports.ERROR_HISTORY_SCHEMA_VERSION = exports.getSnapshotPath = exports.isSnapshotValid = exports.deleteSnapshot = exports.loadSnapshot = exports.saveSnapshot = exports.SessionMonitor = exports.parseTodoDependencies = exports.EventAggregator = exports.getRandomPhrase = exports.PHRASE_CATEGORIES = exports.ALL_PHRASES = exports.HIGHLIGHT_CSS = exports.clearHighlightCache = exports.highlightEvent = exports.formatSessionJson = exports.formatSessionMarkdown = exports.formatSessionText = exports.classifyNoise = exports.shouldMergeWithPrevious = exports.classifyFollowEvent = exports.classifyMessage = exports.getSoftNoiseReason = exports.isHardNoiseFollowEvent = exports.isHardNoise = exports.formatToolSummary = exports.formatTokenCount = exports.formatDurationMs = exports.createJsonlTail = exports.toFollowEvents = exports.createWatcher = exports.parseChangelog = exports.extractProposedPlanShared = exports.parsePlanMarkdownShared = exports.PlanExtractor = exports.segmentAssistantTurn = exports.reasoningSummary = exports.isAssistantTurnSubagentTool = exports.extractTurnSubagents = exports.assistantTurnEventsFromSessionEvents = exports.readSessionContextSnapshot = void 0;
|
|
9
9
|
exports.getCodexProfileHome = exports.getCodexProfilesDir = exports.readClaudeProfileIdentity = exports.isClaudeProfileAuthenticated = exports.claudeKeychainService = exports.claudeKeychainSuffix = exports.getClaudeProfileHome = exports.getClaudeProfilesDir = exports.getActiveAccountStatus = exports.removeSavedAccountProfile = exports.replaceSavedAccountProfiles = exports.setActiveSavedAccount = exports.upsertSavedAccountProfile = exports.getActiveSavedAccount = exports.listSavedAccountProfiles = exports.writeSavedAccountRegistry = exports.readSavedAccountRegistry = exports.getAccountsDir = exports.resolveClaudeLoginCommand = exports.listAllAccounts = exports.switchAccount = exports.spawnAccountLogin = exports.finalizeAccountLogin = exports.getAccountLoginStatus = exports.beginAccountLogin = exports.isMultiAccountEnabled = exports.resolveActiveClaudeAccount = exports.getActiveAccount = exports.listAccounts = exports.removeAccount = exports.reconcileClaudeAuthState = exports.applyActiveClaudeToLiveHome = exports.resolveActiveClaudeHome = exports.switchToAccount = exports.addCurrentAccount = exports.readActiveClaudeAccount = exports.writeAccountRegistry = exports.readAccountRegistry = exports.ensureDefaultAccounts = exports.readClaudeMaxAccessTokenSync = exports.readClaudeMaxCredentials = exports.writeActiveCredentials = exports.readActiveCredentials = exports.openInBrowser = exports.parseTranscriptFromEvents = exports.parseTranscript = exports.generateHtmlReport = exports.formatCompactionLedger = exports.calculateCompactionLedger = exports.calculateCodeImpact = void 0;
|
|
10
10
|
exports.selectStatuslineAccount = exports.formatStatusline = exports.estimateTimeToQuota = exports.BurnRateCalculator = exports.MultiProviderQuotaService = exports.resolveZaiQuota = exports.readZaiCredentials = exports.quotaStateFromZaiQuotaLimitPayload = exports.fetchZaiQuotaFromApi = exports.CodexQuotaWatcher = exports.resolveCodexQuotaFromLocalSources = exports.resolveCodexQuota = exports.readLatestCodexQuotaFromRollouts = exports.quotaFromCodexRateLimits = exports.fetchCodexQuotaFromApi = exports.fetchCodexResetCreditsFromApi = exports.getWorkspaceIdFromPath = exports.pruneQuotaHistory = exports.readQuotaHistoryDailyBuckets = exports.readQuotaHistoryRange = exports.appendQuotaHistorySample = exports.writeQuotaSnapshot = exports.readQuotaSnapshot = exports.QuotaPoller = exports.describeQuotaFailure = exports.withQuotaProjections = exports.projectQuotaWindow = exports.fetchQuota = exports.SEVEN_DAY_WINDOW_MS = exports.FIVE_HOUR_WINDOW_MS = exports.AutoSwitchController = exports.decideAutoSwitch = exports.DEFAULT_AUTO_SWITCH_CONFIG = exports.removeLauncher = exports.writeLauncher = exports.isShellHookInstalled = exports.uninstallShellHook = exports.installShellHook = exports.setTerminalActiveProfile = exports.removeCodexAccount = exports.switchToCodexAccount = exports.finalizeCodexAccount = exports.prepareCodexAccount = exports.getCodexExecutionEnv = exports.resolveSidekickCodexHome = exports.resolveActiveCodexAccount = exports.getActiveCodexAccount = exports.listCodexAccounts = exports.getSystemCodexHome = exports.getCodexMonitoringHomes = void 0;
|
|
11
|
-
exports.
|
|
12
|
-
exports.scopePeakHoursToSessionProvider = exports.isClaudeCodeSessionProvider = exports.fetchPeakHoursStatus = exports.createPeakHoursNotApplicableState = exports.runDoctor = exports.getSessionDiagnostics = exports.formatHealthReport = exports.fetchOpenAIStatus = exports.fetchProviderStatus = exports.assistantTurnToolRefSchema = exports.assistantTurnToolGroupStepSchema = exports.assistantTurnTimelineItemSchema = exports.assistantTurnSubagentStatusSchema = exports.assistantTurnSubagentSchema = exports.assistantTurnReasoningTimelineItemSchema = exports.assistantTurnProjectionSchema = exports.assistantTurnProcessStepSchema = exports.assistantTurnProcessSchema = exports.assistantTurnNarrationStepSchema = exports.assistantTurnEventTypeSchema = exports.assistantTurnEventSchema = exports.listAllAccountsResultSchema = exports.savedAccountProfileSchema = exports.accountEntrySchema = exports.accountLoginStatusSchema = exports.beginAccountLoginResultSchema = exports.accountManagerResultSchema = exports.accountProviderIdSchema = exports.activeAccountStatusSchema = exports.activeProviderAccountStatusSchema = exports.quotaHistoryDailyBucketSchema = exports.quotaHistorySampleSchema = exports.quotaHistoryRuntimeProviderSchema = exports.providerQuotaMapSchema = void 0;
|
|
11
|
+
exports.runtimeQuotaProviderSchema = exports.quotaFailureDescriptorSchema = exports.peakHoursStateSchema = exports.codexResetCreditsSnapshotSchema = exports.codexResetCreditSchema = exports.quotaSourceSchema = exports.quotaProviderIdSchema = exports.quotaFailureKindSchema = exports.quotaStateSchema = exports.quotaWindowSchema = exports.extractSessionEvents = exports.permissionModeSchema = exports.sessionEventSchema = exports.sessionMessageSchema = exports.messageUsageSchema = exports.ToolCallTracker = exports.extractToolCalls = exports.extractToolCall = exports.extractErrorMessage = exports.categorizeError = exports.gatherAssetsForCwd = exports.codexSessions = exports.readCodexAssets = exports.claudeSessions = exports.readClaudeAssets = exports.extractCommands = exports.extractFilePaths = exports.extractUrls = exports.extractTokenUsage = exports.getObservedContextWindowPath = exports.recordObservedContextWindow = exports.loadObservedContextWindows = exports.LITELLM_CATALOG_URL = exports.normalizeLiteLlmContextWindows = exports.normalizeLiteLlmCatalog = exports.hydratePricingCatalog = exports.formatCost = exports.sortModelIds = exports.compareModelIds = exports.getModelDisplayInfo = exports.shortModelName = exports.mergeCostSources = exports.calculateCostWithProvenance = exports.calculateCostWithPricing = exports.calculateCost = exports.getModelInfo = exports.getModelPricing = exports.parseModelId = exports.DEFAULT_CONTEXT_WINDOW = exports.getModelContextWindowSize = void 0;
|
|
12
|
+
exports.scopePeakHoursToSessionProvider = exports.isClaudeCodeSessionProvider = exports.fetchPeakHoursStatus = exports.createPeakHoursNotApplicableState = exports.runDoctor = exports.getSessionDiagnostics = exports.formatHealthReport = exports.fetchOpenAIStatus = exports.fetchProviderStatus = exports.assistantTurnToolRefSchema = exports.assistantTurnToolGroupStepSchema = exports.assistantTurnTimelineItemSchema = exports.assistantTurnSubagentStatusSchema = exports.assistantTurnSubagentSchema = exports.assistantTurnReasoningTimelineItemSchema = exports.assistantTurnProjectionSchema = exports.assistantTurnProcessStepSchema = exports.assistantTurnProcessSchema = exports.assistantTurnNarrationStepSchema = exports.assistantTurnEventTypeSchema = exports.assistantTurnEventSchema = exports.listAllAccountsResultSchema = exports.savedAccountProfileSchema = exports.accountEntrySchema = exports.accountLoginStatusSchema = exports.beginAccountLoginResultSchema = exports.accountManagerResultSchema = exports.accountProviderIdSchema = exports.activeAccountStatusSchema = exports.activeProviderAccountStatusSchema = exports.quotaHistoryDailyBucketSchema = exports.quotaHistorySampleSchema = exports.quotaHistoryRuntimeProviderSchema = exports.providerQuotaMapSchema = exports.zaiProviderQuotaStateSchema = exports.codexProviderQuotaStateSchema = exports.claudeProviderQuotaStateSchema = exports.providerQuotaStateSchema = void 0;
|
|
13
13
|
var usageNormalization_1 = require("./usageNormalization");
|
|
14
14
|
Object.defineProperty(exports, "normalizeProviderUsage", { enumerable: true, get: function () { return usageNormalization_1.normalizeProviderUsage; } });
|
|
15
15
|
Object.defineProperty(exports, "extractNormalizedUsage", { enumerable: true, get: function () { return usageNormalization_1.extractNormalizedUsage; } });
|
|
@@ -397,7 +397,13 @@ Object.defineProperty(exports, "formatCost", { enumerable: true, get: function (
|
|
|
397
397
|
var pricingCatalog_1 = require("./pricingCatalog");
|
|
398
398
|
Object.defineProperty(exports, "hydratePricingCatalog", { enumerable: true, get: function () { return pricingCatalog_1.hydratePricingCatalog; } });
|
|
399
399
|
Object.defineProperty(exports, "normalizeLiteLlmCatalog", { enumerable: true, get: function () { return pricingCatalog_1.normalizeLiteLlmCatalog; } });
|
|
400
|
+
Object.defineProperty(exports, "normalizeLiteLlmContextWindows", { enumerable: true, get: function () { return pricingCatalog_1.normalizeLiteLlmContextWindows; } });
|
|
400
401
|
Object.defineProperty(exports, "LITELLM_CATALOG_URL", { enumerable: true, get: function () { return pricingCatalog_1.LITELLM_CATALOG_URL; } });
|
|
402
|
+
// Observed context windows (provider-reported, persisted) — Node-only.
|
|
403
|
+
var observedContextWindows_1 = require("./observedContextWindows");
|
|
404
|
+
Object.defineProperty(exports, "loadObservedContextWindows", { enumerable: true, get: function () { return observedContextWindows_1.loadObservedContextWindows; } });
|
|
405
|
+
Object.defineProperty(exports, "recordObservedContextWindow", { enumerable: true, get: function () { return observedContextWindows_1.recordObservedContextWindow; } });
|
|
406
|
+
Object.defineProperty(exports, "getObservedContextWindowPath", { enumerable: true, get: function () { return observedContextWindows_1.getObservedContextWindowPath; } });
|
|
401
407
|
// Extractors — per-event token usage and tool call extraction
|
|
402
408
|
var tokenUsage_1 = require("./extractors/tokenUsage");
|
|
403
409
|
Object.defineProperty(exports, "extractTokenUsage", { enumerable: true, get: function () { return tokenUsage_1.extractTokenUsage; } });
|
package/dist/modelContext.d.ts
CHANGED
|
@@ -1,19 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralized model ID → context window size lookup.
|
|
3
3
|
* Single source of truth used by all providers and the CLI dashboard.
|
|
4
|
+
*
|
|
5
|
+
* Resolution is layered, most-trusted first:
|
|
6
|
+
* 1. Runtime-reported — providers that self-report their window (Codex) apply
|
|
7
|
+
* it in `getContextWindowLimit`, ahead of this module.
|
|
8
|
+
* 2. Observed overrides — the last window a provider actually reported for a
|
|
9
|
+
* model, persisted across runs by `observedContextWindows.ts`. Reflects the
|
|
10
|
+
* effective window for this account/tier, which can be well below the
|
|
11
|
+
* model's published maximum.
|
|
12
|
+
* 3. Catalog overrides — `max_input_tokens` from the LiteLLM catalog, hydrated
|
|
13
|
+
* by `pricingCatalog.ts`. Keeps new models correct without a code change.
|
|
14
|
+
* 4. The static table below — offline baseline of last resort.
|
|
15
|
+
*
|
|
16
|
+
* Within that order, match quality comes first: every layer is tried for an
|
|
17
|
+
* exact hit before any layer is tried for a prefix hit. See
|
|
18
|
+
* `getModelContextWindowSize`.
|
|
19
|
+
*
|
|
20
|
+
* Layers 2 and 3 are pushed in by Node-only modules through the `_set*` hooks,
|
|
21
|
+
* so this file stays browser-safe (no node:fs, no fetch) and can be bundled for
|
|
22
|
+
* webviews. Same pattern as `_setPricingOverrides` in `modelInfo.ts`.
|
|
4
23
|
*/
|
|
5
24
|
/** Default context window size when model is unknown. */
|
|
6
25
|
export declare const DEFAULT_CONTEXT_WINDOW = 200000;
|
|
26
|
+
/**
|
|
27
|
+
* Internal: replace the observed-context-window map. Called by
|
|
28
|
+
* `observedContextWindows.ts` on load and on each newly observed value.
|
|
29
|
+
* Not part of the stable public API.
|
|
30
|
+
*/
|
|
31
|
+
export declare function _setObservedContextWindows(overrides: Record<string, number>): void;
|
|
32
|
+
/** Internal: snapshot observed overrides (test + diagnostics). */
|
|
33
|
+
export declare function _getObservedContextWindows(): Record<string, number>;
|
|
34
|
+
/** Internal: clear observed overrides (used by tests). */
|
|
35
|
+
export declare function _clearObservedContextWindows(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Internal: replace the catalog-context-window map. Called by
|
|
38
|
+
* `pricingCatalog.ts` after a successful LiteLLM hydration.
|
|
39
|
+
* Not part of the stable public API.
|
|
40
|
+
*/
|
|
41
|
+
export declare function _setCatalogContextWindows(overrides: Record<string, number>): void;
|
|
42
|
+
/** Internal: snapshot catalog overrides (test + diagnostics). */
|
|
43
|
+
export declare function _getCatalogContextWindows(): Record<string, number>;
|
|
44
|
+
/** Internal: clear catalog overrides (used by tests and when disabling hydration). */
|
|
45
|
+
export declare function _clearCatalogContextWindows(): void;
|
|
7
46
|
/**
|
|
8
47
|
* Returns the context window size for a model ID.
|
|
9
48
|
*
|
|
10
49
|
* Input is trimmed and lowercased before lookup, so padded or mixed-case
|
|
11
|
-
* IDs (e.g. "Claude-Opus-
|
|
50
|
+
* IDs (e.g. "Claude-Opus-5 ") resolve without caller-side normalization.
|
|
12
51
|
*
|
|
13
52
|
* Lookup order:
|
|
14
53
|
* 1. Explicit "[1m]" suffix (Claude Code's 1M-variant marker) → 1_000_000
|
|
15
|
-
* 2. Exact match
|
|
16
|
-
* 3. Longest-prefix match
|
|
54
|
+
* 2. Exact match, most-trusted layer first: observed → catalog → static
|
|
55
|
+
* 3. Longest-prefix match, same layer order
|
|
56
|
+
* (e.g. "claude-opus-4-6-20250414" → "claude-opus-4-6")
|
|
17
57
|
* 4. DEFAULT_CONTEXT_WINDOW
|
|
58
|
+
*
|
|
59
|
+
* Match quality outranks layer trust: an exact hit in *any* layer beats a
|
|
60
|
+
* prefix guess in *every* layer. Resolving each layer fully before moving on
|
|
61
|
+
* would let a catalog key that merely happens to be a prefix shadow a curated
|
|
62
|
+
* static entry — e.g. the catalog has no "claude-sonnet-4-7", and prefix-first
|
|
63
|
+
* would hand it "claude-sonnet-4" (128K, a GitHub Copilot deployment) instead
|
|
64
|
+
* of the static table's exact 1M.
|
|
18
65
|
*/
|
|
19
66
|
export declare function getModelContextWindowSize(modelId?: string): number;
|
package/dist/modelContext.js
CHANGED
|
@@ -2,17 +2,44 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Centralized model ID → context window size lookup.
|
|
4
4
|
* Single source of truth used by all providers and the CLI dashboard.
|
|
5
|
+
*
|
|
6
|
+
* Resolution is layered, most-trusted first:
|
|
7
|
+
* 1. Runtime-reported — providers that self-report their window (Codex) apply
|
|
8
|
+
* it in `getContextWindowLimit`, ahead of this module.
|
|
9
|
+
* 2. Observed overrides — the last window a provider actually reported for a
|
|
10
|
+
* model, persisted across runs by `observedContextWindows.ts`. Reflects the
|
|
11
|
+
* effective window for this account/tier, which can be well below the
|
|
12
|
+
* model's published maximum.
|
|
13
|
+
* 3. Catalog overrides — `max_input_tokens` from the LiteLLM catalog, hydrated
|
|
14
|
+
* by `pricingCatalog.ts`. Keeps new models correct without a code change.
|
|
15
|
+
* 4. The static table below — offline baseline of last resort.
|
|
16
|
+
*
|
|
17
|
+
* Within that order, match quality comes first: every layer is tried for an
|
|
18
|
+
* exact hit before any layer is tried for a prefix hit. See
|
|
19
|
+
* `getModelContextWindowSize`.
|
|
20
|
+
*
|
|
21
|
+
* Layers 2 and 3 are pushed in by Node-only modules through the `_set*` hooks,
|
|
22
|
+
* so this file stays browser-safe (no node:fs, no fetch) and can be bundled for
|
|
23
|
+
* webviews. Same pattern as `_setPricingOverrides` in `modelInfo.ts`.
|
|
5
24
|
*/
|
|
6
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
26
|
exports.DEFAULT_CONTEXT_WINDOW = void 0;
|
|
27
|
+
exports._setObservedContextWindows = _setObservedContextWindows;
|
|
28
|
+
exports._getObservedContextWindows = _getObservedContextWindows;
|
|
29
|
+
exports._clearObservedContextWindows = _clearObservedContextWindows;
|
|
30
|
+
exports._setCatalogContextWindows = _setCatalogContextWindows;
|
|
31
|
+
exports._getCatalogContextWindows = _getCatalogContextWindows;
|
|
32
|
+
exports._clearCatalogContextWindows = _clearCatalogContextWindows;
|
|
8
33
|
exports.getModelContextWindowSize = getModelContextWindowSize;
|
|
9
34
|
/** Known model context window sizes (in tokens). */
|
|
10
35
|
const MODEL_CONTEXT_SIZES = {
|
|
11
36
|
// Claude — native 1M context (Fable 5, Opus 4.6+, Sonnet 4.6+)
|
|
12
37
|
'claude-fable-5': 1_000_000,
|
|
38
|
+
'claude-opus-5': 1_000_000,
|
|
13
39
|
'claude-opus-4-8': 1_000_000,
|
|
14
40
|
'claude-opus-4-7': 1_000_000,
|
|
15
41
|
'claude-opus-4-6': 1_000_000,
|
|
42
|
+
'claude-sonnet-5': 1_000_000,
|
|
16
43
|
'claude-sonnet-4-7': 1_000_000,
|
|
17
44
|
'claude-sonnet-4-6': 1_000_000,
|
|
18
45
|
// Claude 4 family
|
|
@@ -30,7 +57,14 @@ const MODEL_CONTEXT_SIZES = {
|
|
|
30
57
|
// OpenAI GPT-4.1 series (1M context)
|
|
31
58
|
'gpt-4.1': 1_048_576,
|
|
32
59
|
// OpenAI GPT-5 series (keys sorted longest-first below; explicit entries
|
|
33
|
-
// for every variant so prefix matching can't misclassify a new one)
|
|
60
|
+
// for every variant so prefix matching can't misclassify a new one).
|
|
61
|
+
// These are published model maxima. Codex reports a smaller *effective*
|
|
62
|
+
// window per account tier via token_count; that value wins through the
|
|
63
|
+
// observed-override layer.
|
|
64
|
+
'gpt-5.6-sol': 1_050_000,
|
|
65
|
+
'gpt-5.6-terra': 1_050_000,
|
|
66
|
+
'gpt-5.6': 1_050_000,
|
|
67
|
+
'gpt-5.5': 1_050_000,
|
|
34
68
|
'gpt-5.4': 1_050_000,
|
|
35
69
|
'gpt-5.3-codex-spark': 128_000,
|
|
36
70
|
'gpt-5.3-codex': 400_000,
|
|
@@ -49,20 +83,100 @@ const MODEL_CONTEXT_SIZES = {
|
|
|
49
83
|
deepseek: 128_000,
|
|
50
84
|
};
|
|
51
85
|
/** Prefix keys sorted longest-first for correct prefix matching. */
|
|
52
|
-
const SORTED_KEYS =
|
|
86
|
+
const SORTED_KEYS = sortKeysLongestFirst(MODEL_CONTEXT_SIZES);
|
|
53
87
|
/** Default context window size when model is unknown. */
|
|
54
88
|
exports.DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
89
|
+
// ── Override Tables (populated at runtime by Node-only modules) ──
|
|
90
|
+
let observedTable = {};
|
|
91
|
+
let observedSortedKeys = [];
|
|
92
|
+
let catalogTable = {};
|
|
93
|
+
let catalogSortedKeys = [];
|
|
94
|
+
function sortKeysLongestFirst(table) {
|
|
95
|
+
return Object.keys(table).sort((a, b) => b.length - a.length);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Lowercase keys so lookups match regardless of how the source spelled them.
|
|
99
|
+
* The LiteLLM catalog publishes some IDs mixed-case; the static table and the
|
|
100
|
+
* lookup path are both lowercase. Drops non-positive and non-finite values.
|
|
101
|
+
*/
|
|
102
|
+
function normalizeOverrides(overrides) {
|
|
103
|
+
const out = {};
|
|
104
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
105
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
106
|
+
continue;
|
|
107
|
+
out[key.trim().toLowerCase()] = value;
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Internal: replace the observed-context-window map. Called by
|
|
113
|
+
* `observedContextWindows.ts` on load and on each newly observed value.
|
|
114
|
+
* Not part of the stable public API.
|
|
115
|
+
*/
|
|
116
|
+
function _setObservedContextWindows(overrides) {
|
|
117
|
+
observedTable = normalizeOverrides(overrides);
|
|
118
|
+
observedSortedKeys = sortKeysLongestFirst(observedTable);
|
|
119
|
+
}
|
|
120
|
+
/** Internal: snapshot observed overrides (test + diagnostics). */
|
|
121
|
+
function _getObservedContextWindows() {
|
|
122
|
+
return { ...observedTable };
|
|
123
|
+
}
|
|
124
|
+
/** Internal: clear observed overrides (used by tests). */
|
|
125
|
+
function _clearObservedContextWindows() {
|
|
126
|
+
observedTable = {};
|
|
127
|
+
observedSortedKeys = [];
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Internal: replace the catalog-context-window map. Called by
|
|
131
|
+
* `pricingCatalog.ts` after a successful LiteLLM hydration.
|
|
132
|
+
* Not part of the stable public API.
|
|
133
|
+
*/
|
|
134
|
+
function _setCatalogContextWindows(overrides) {
|
|
135
|
+
catalogTable = normalizeOverrides(overrides);
|
|
136
|
+
catalogSortedKeys = sortKeysLongestFirst(catalogTable);
|
|
137
|
+
}
|
|
138
|
+
/** Internal: snapshot catalog overrides (test + diagnostics). */
|
|
139
|
+
function _getCatalogContextWindows() {
|
|
140
|
+
return { ...catalogTable };
|
|
141
|
+
}
|
|
142
|
+
/** Internal: clear catalog overrides (used by tests and when disabling hydration). */
|
|
143
|
+
function _clearCatalogContextWindows() {
|
|
144
|
+
catalogTable = {};
|
|
145
|
+
catalogSortedKeys = [];
|
|
146
|
+
}
|
|
147
|
+
// ── Lookup ──
|
|
148
|
+
/** Exact match against one table. */
|
|
149
|
+
function exactWindow(table, modelId) {
|
|
150
|
+
const hit = table[modelId];
|
|
151
|
+
return hit === undefined ? null : hit;
|
|
152
|
+
}
|
|
153
|
+
/** Longest-prefix match against one table. */
|
|
154
|
+
function prefixWindow(table, sortedKeys, modelId) {
|
|
155
|
+
for (const key of sortedKeys) {
|
|
156
|
+
if (modelId.startsWith(key))
|
|
157
|
+
return table[key];
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
55
161
|
/**
|
|
56
162
|
* Returns the context window size for a model ID.
|
|
57
163
|
*
|
|
58
164
|
* Input is trimmed and lowercased before lookup, so padded or mixed-case
|
|
59
|
-
* IDs (e.g. "Claude-Opus-
|
|
165
|
+
* IDs (e.g. "Claude-Opus-5 ") resolve without caller-side normalization.
|
|
60
166
|
*
|
|
61
167
|
* Lookup order:
|
|
62
168
|
* 1. Explicit "[1m]" suffix (Claude Code's 1M-variant marker) → 1_000_000
|
|
63
|
-
* 2. Exact match
|
|
64
|
-
* 3. Longest-prefix match
|
|
169
|
+
* 2. Exact match, most-trusted layer first: observed → catalog → static
|
|
170
|
+
* 3. Longest-prefix match, same layer order
|
|
171
|
+
* (e.g. "claude-opus-4-6-20250414" → "claude-opus-4-6")
|
|
65
172
|
* 4. DEFAULT_CONTEXT_WINDOW
|
|
173
|
+
*
|
|
174
|
+
* Match quality outranks layer trust: an exact hit in *any* layer beats a
|
|
175
|
+
* prefix guess in *every* layer. Resolving each layer fully before moving on
|
|
176
|
+
* would let a catalog key that merely happens to be a prefix shadow a curated
|
|
177
|
+
* static entry — e.g. the catalog has no "claude-sonnet-4-7", and prefix-first
|
|
178
|
+
* would hand it "claude-sonnet-4" (128K, a GitHub Copilot deployment) instead
|
|
179
|
+
* of the static table's exact 1M.
|
|
66
180
|
*/
|
|
67
181
|
function getModelContextWindowSize(modelId) {
|
|
68
182
|
if (!modelId)
|
|
@@ -72,21 +186,24 @@ function getModelContextWindowSize(modelId) {
|
|
|
72
186
|
if (/\[1m\]/i.test(modelId))
|
|
73
187
|
return 1_000_000;
|
|
74
188
|
// Strip the suffix if present, so the normal lookup still succeeds when
|
|
75
|
-
// a caller passes e.g. "claude-opus-
|
|
189
|
+
// a caller passes e.g. "claude-opus-5[1m]" and we've already handled it.
|
|
76
190
|
// Trim/lowercase so padded or mixed-case IDs match the lowercase table keys.
|
|
77
191
|
const normalized = modelId
|
|
78
192
|
.replace(/\[1m\]/gi, '')
|
|
79
193
|
.trim()
|
|
80
194
|
.toLowerCase();
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
195
|
+
// Pass 1 — exact matches, most-trusted layer first.
|
|
196
|
+
const exact = exactWindow(observedTable, normalized) ??
|
|
197
|
+
exactWindow(catalogTable, normalized) ??
|
|
198
|
+
exactWindow(MODEL_CONTEXT_SIZES, normalized);
|
|
199
|
+
if (exact !== null)
|
|
200
|
+
return exact;
|
|
201
|
+
// Pass 2 — longest-prefix matches, same layer order. Resolves versioned IDs
|
|
202
|
+
// that no layer spells out, e.g. "claude-opus-4-6-20250414".
|
|
203
|
+
const prefix = prefixWindow(observedTable, observedSortedKeys, normalized) ??
|
|
204
|
+
prefixWindow(catalogTable, catalogSortedKeys, normalized) ??
|
|
205
|
+
prefixWindow(MODEL_CONTEXT_SIZES, SORTED_KEYS, normalized);
|
|
206
|
+
if (prefix !== null)
|
|
207
|
+
return prefix;
|
|
91
208
|
return exports.DEFAULT_CONTEXT_WINDOW;
|
|
92
209
|
}
|
package/dist/modelInfo.js
CHANGED
|
@@ -76,6 +76,14 @@ const PRICING_TABLE = {
|
|
|
76
76
|
cacheWriteCostPerMillion: 1.0,
|
|
77
77
|
cacheReadCostPerMillion: 0.08,
|
|
78
78
|
},
|
|
79
|
+
// Sonnet 5 carries the standard rate here. The introductory $2/$10 (through
|
|
80
|
+
// 2026-08-31) arrives via catalog hydration, which supersedes this baseline.
|
|
81
|
+
'claude-sonnet-5': {
|
|
82
|
+
inputCostPerMillion: 3.0,
|
|
83
|
+
outputCostPerMillion: 15.0,
|
|
84
|
+
cacheWriteCostPerMillion: 3.75,
|
|
85
|
+
cacheReadCostPerMillion: 0.3,
|
|
86
|
+
},
|
|
79
87
|
'claude-sonnet-4-6': {
|
|
80
88
|
inputCostPerMillion: 3.0,
|
|
81
89
|
outputCostPerMillion: 15.0,
|
|
@@ -106,6 +114,12 @@ const PRICING_TABLE = {
|
|
|
106
114
|
cacheWriteCostPerMillion: 3.75,
|
|
107
115
|
cacheReadCostPerMillion: 0.3,
|
|
108
116
|
},
|
|
117
|
+
'claude-opus-5': {
|
|
118
|
+
inputCostPerMillion: 5.0,
|
|
119
|
+
outputCostPerMillion: 25.0,
|
|
120
|
+
cacheWriteCostPerMillion: 6.25,
|
|
121
|
+
cacheReadCostPerMillion: 0.5,
|
|
122
|
+
},
|
|
109
123
|
'claude-opus-4-8': {
|
|
110
124
|
inputCostPerMillion: 5.0,
|
|
111
125
|
outputCostPerMillion: 25.0,
|
|
@@ -186,14 +200,45 @@ const PRICING_TABLE = {
|
|
|
186
200
|
cacheWriteCostPerMillion: 0,
|
|
187
201
|
cacheReadCostPerMillion: 0,
|
|
188
202
|
},
|
|
189
|
-
// ── OpenAI: GPT-5 family
|
|
190
|
-
//
|
|
191
|
-
//
|
|
203
|
+
// ── OpenAI: GPT-5 family ──
|
|
204
|
+
// Published rates, verified against the LiteLLM catalog. Runtime hydration
|
|
205
|
+
// still supersedes these; they are the offline baseline. Entries below
|
|
206
|
+
// `gpt-5.4` predate the published-rate pass and remain rough estimates.
|
|
207
|
+
'gpt-5.6-sol': {
|
|
208
|
+
inputCostPerMillion: 5.0,
|
|
209
|
+
outputCostPerMillion: 30.0,
|
|
210
|
+
cacheWriteCostPerMillion: 6.25,
|
|
211
|
+
cacheReadCostPerMillion: 0.5,
|
|
212
|
+
},
|
|
213
|
+
'gpt-5.6-terra': {
|
|
214
|
+
inputCostPerMillion: 2.5,
|
|
215
|
+
outputCostPerMillion: 15.0,
|
|
216
|
+
cacheWriteCostPerMillion: 3.13,
|
|
217
|
+
cacheReadCostPerMillion: 0.25,
|
|
218
|
+
},
|
|
219
|
+
'gpt-5.6': {
|
|
220
|
+
inputCostPerMillion: 5.0,
|
|
221
|
+
outputCostPerMillion: 30.0,
|
|
222
|
+
cacheWriteCostPerMillion: 6.25,
|
|
223
|
+
cacheReadCostPerMillion: 0.5,
|
|
224
|
+
},
|
|
225
|
+
'gpt-5.5': {
|
|
226
|
+
inputCostPerMillion: 5.0,
|
|
227
|
+
outputCostPerMillion: 30.0,
|
|
228
|
+
cacheWriteCostPerMillion: 0,
|
|
229
|
+
cacheReadCostPerMillion: 0.5,
|
|
230
|
+
},
|
|
231
|
+
'gpt-5.4-mini': {
|
|
232
|
+
inputCostPerMillion: 0.75,
|
|
233
|
+
outputCostPerMillion: 4.5,
|
|
234
|
+
cacheWriteCostPerMillion: 0,
|
|
235
|
+
cacheReadCostPerMillion: 0.07,
|
|
236
|
+
},
|
|
192
237
|
'gpt-5.4': {
|
|
193
|
-
inputCostPerMillion:
|
|
194
|
-
outputCostPerMillion:
|
|
238
|
+
inputCostPerMillion: 2.5,
|
|
239
|
+
outputCostPerMillion: 15.0,
|
|
195
240
|
cacheWriteCostPerMillion: 0,
|
|
196
|
-
cacheReadCostPerMillion: 0.
|
|
241
|
+
cacheReadCostPerMillion: 0.25,
|
|
197
242
|
},
|
|
198
243
|
'gpt-5.3-codex': {
|
|
199
244
|
inputCostPerMillion: 1.25,
|
package/dist/node.d.ts
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* and `node:path`; safe for extension-host and CLI consumers. Not safe
|
|
6
6
|
* for browser bundles — use `sidekick-shared/browser` there.
|
|
7
7
|
*/
|
|
8
|
-
export { hydratePricingCatalog, normalizeLiteLlmCatalog, LITELLM_CATALOG_URL, } from './pricingCatalog';
|
|
8
|
+
export { hydratePricingCatalog, normalizeLiteLlmCatalog, normalizeLiteLlmContextWindows, LITELLM_CATALOG_URL, } from './pricingCatalog';
|
|
9
9
|
export type { HydrateOptions, HydrateResult } from './pricingCatalog';
|
|
10
|
+
export { loadObservedContextWindows, recordObservedContextWindow, getObservedContextWindowPath, } from './observedContextWindows';
|
|
11
|
+
export type { ObservedContextWindowOptions, ObservedContextWindowStore, } from './observedContextWindows';
|
|
10
12
|
export { listRecentSessions, readSessionTranscript } from './sessionTranscripts';
|
|
11
13
|
export type { ProviderSessionIndex, ListRecentSessionsOptions } from './sessionTranscripts';
|
|
12
14
|
export { ObservedSessionCollector, observedSessionSourceFromProvider, fileFingerprint, } from './observedSessionCollector';
|
package/dist/node.js
CHANGED
|
@@ -7,11 +7,16 @@
|
|
|
7
7
|
* for browser bundles — use `sidekick-shared/browser` there.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.fileFingerprint = exports.observedSessionSourceFromProvider = exports.ObservedSessionCollector = exports.readSessionTranscript = exports.listRecentSessions = exports.LITELLM_CATALOG_URL = exports.normalizeLiteLlmCatalog = exports.hydratePricingCatalog = void 0;
|
|
10
|
+
exports.fileFingerprint = exports.observedSessionSourceFromProvider = exports.ObservedSessionCollector = exports.readSessionTranscript = exports.listRecentSessions = exports.getObservedContextWindowPath = exports.recordObservedContextWindow = exports.loadObservedContextWindows = exports.LITELLM_CATALOG_URL = exports.normalizeLiteLlmContextWindows = exports.normalizeLiteLlmCatalog = exports.hydratePricingCatalog = void 0;
|
|
11
11
|
var pricingCatalog_1 = require("./pricingCatalog");
|
|
12
12
|
Object.defineProperty(exports, "hydratePricingCatalog", { enumerable: true, get: function () { return pricingCatalog_1.hydratePricingCatalog; } });
|
|
13
13
|
Object.defineProperty(exports, "normalizeLiteLlmCatalog", { enumerable: true, get: function () { return pricingCatalog_1.normalizeLiteLlmCatalog; } });
|
|
14
|
+
Object.defineProperty(exports, "normalizeLiteLlmContextWindows", { enumerable: true, get: function () { return pricingCatalog_1.normalizeLiteLlmContextWindows; } });
|
|
14
15
|
Object.defineProperty(exports, "LITELLM_CATALOG_URL", { enumerable: true, get: function () { return pricingCatalog_1.LITELLM_CATALOG_URL; } });
|
|
16
|
+
var observedContextWindows_1 = require("./observedContextWindows");
|
|
17
|
+
Object.defineProperty(exports, "loadObservedContextWindows", { enumerable: true, get: function () { return observedContextWindows_1.loadObservedContextWindows; } });
|
|
18
|
+
Object.defineProperty(exports, "recordObservedContextWindow", { enumerable: true, get: function () { return observedContextWindows_1.recordObservedContextWindow; } });
|
|
19
|
+
Object.defineProperty(exports, "getObservedContextWindowPath", { enumerable: true, get: function () { return observedContextWindows_1.getObservedContextWindowPath; } });
|
|
15
20
|
var sessionTranscripts_1 = require("./sessionTranscripts");
|
|
16
21
|
Object.defineProperty(exports, "listRecentSessions", { enumerable: true, get: function () { return sessionTranscripts_1.listRecentSessions; } });
|
|
17
22
|
Object.defineProperty(exports, "readSessionTranscript", { enumerable: true, get: function () { return sessionTranscripts_1.readSessionTranscript; } });
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persisted record of context windows providers actually reported (Node-only).
|
|
3
|
+
*
|
|
4
|
+
* Some agents report the real context window for the running session — Codex
|
|
5
|
+
* emits `model_context_window` on every `token_count` event. That number is the
|
|
6
|
+
* *effective* window for the account and tier, which can sit well below the
|
|
7
|
+
* model's published maximum (e.g. 258,400 observed for `gpt-5.6-sol` against a
|
|
8
|
+
* catalog maximum of 1,050,000).
|
|
9
|
+
*
|
|
10
|
+
* A live session uses that value directly. This module keeps it across runs, so
|
|
11
|
+
* historical and static views don't fall back to a published maximum that would
|
|
12
|
+
* make the context gauge read far too empty.
|
|
13
|
+
*
|
|
14
|
+
* Precedence, most-trusted first: runtime → observed (here) → LiteLLM catalog →
|
|
15
|
+
* static table. See `modelContext.ts`.
|
|
16
|
+
*
|
|
17
|
+
* @module observedContextWindows
|
|
18
|
+
*/
|
|
19
|
+
export interface ObservedContextWindowOptions {
|
|
20
|
+
/** Directory for the store (defaults to the Sidekick config dir). */
|
|
21
|
+
cacheDir?: string;
|
|
22
|
+
/** Optional logger (useful for diagnostics without pulling a logger dep). */
|
|
23
|
+
logger?: (msg: string) => void;
|
|
24
|
+
}
|
|
25
|
+
export interface ObservedContextWindowEntry {
|
|
26
|
+
contextWindow: number;
|
|
27
|
+
/** ISO timestamp of the most recent observation. */
|
|
28
|
+
observedAt: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ObservedContextWindowStore {
|
|
31
|
+
version: 1;
|
|
32
|
+
models: Record<string, ObservedContextWindowEntry>;
|
|
33
|
+
}
|
|
34
|
+
/** Resolve the on-disk store path. */
|
|
35
|
+
export declare function getObservedContextWindowPath(cacheDir?: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Load persisted observations and apply them as context-window overrides.
|
|
38
|
+
*
|
|
39
|
+
* Always resolves — never throws. A missing, unreadable, or malformed store
|
|
40
|
+
* leaves the overrides empty and lookups fall through to catalog/static.
|
|
41
|
+
*/
|
|
42
|
+
export declare function loadObservedContextWindows(options?: ObservedContextWindowOptions): Promise<Record<string, number>>;
|
|
43
|
+
/**
|
|
44
|
+
* Record a provider-reported context window for a model.
|
|
45
|
+
*
|
|
46
|
+
* Applies in memory immediately, then persists — but only when the value
|
|
47
|
+
* actually changed. Codex reports a window on every `token_count` event, so an
|
|
48
|
+
* unconditional write would mean disk I/O many times per session for a value
|
|
49
|
+
* that almost never moves.
|
|
50
|
+
*
|
|
51
|
+
* Always resolves — never throws. Persistence failures are logged and dropped;
|
|
52
|
+
* the in-memory override still stands for this process.
|
|
53
|
+
*/
|
|
54
|
+
export declare function recordObservedContextWindow(modelId: string, contextWindow: number, options?: ObservedContextWindowOptions): Promise<void>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Persisted record of context windows providers actually reported (Node-only).
|
|
4
|
+
*
|
|
5
|
+
* Some agents report the real context window for the running session — Codex
|
|
6
|
+
* emits `model_context_window` on every `token_count` event. That number is the
|
|
7
|
+
* *effective* window for the account and tier, which can sit well below the
|
|
8
|
+
* model's published maximum (e.g. 258,400 observed for `gpt-5.6-sol` against a
|
|
9
|
+
* catalog maximum of 1,050,000).
|
|
10
|
+
*
|
|
11
|
+
* A live session uses that value directly. This module keeps it across runs, so
|
|
12
|
+
* historical and static views don't fall back to a published maximum that would
|
|
13
|
+
* make the context gauge read far too empty.
|
|
14
|
+
*
|
|
15
|
+
* Precedence, most-trusted first: runtime → observed (here) → LiteLLM catalog →
|
|
16
|
+
* static table. See `modelContext.ts`.
|
|
17
|
+
*
|
|
18
|
+
* @module observedContextWindows
|
|
19
|
+
*/
|
|
20
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
23
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
24
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
25
|
+
}
|
|
26
|
+
Object.defineProperty(o, k2, desc);
|
|
27
|
+
}) : (function(o, m, k, k2) {
|
|
28
|
+
if (k2 === undefined) k2 = k;
|
|
29
|
+
o[k2] = m[k];
|
|
30
|
+
}));
|
|
31
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
32
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
33
|
+
}) : function(o, v) {
|
|
34
|
+
o["default"] = v;
|
|
35
|
+
});
|
|
36
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
37
|
+
var ownKeys = function(o) {
|
|
38
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
39
|
+
var ar = [];
|
|
40
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
41
|
+
return ar;
|
|
42
|
+
};
|
|
43
|
+
return ownKeys(o);
|
|
44
|
+
};
|
|
45
|
+
return function (mod) {
|
|
46
|
+
if (mod && mod.__esModule) return mod;
|
|
47
|
+
var result = {};
|
|
48
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
49
|
+
__setModuleDefault(result, mod);
|
|
50
|
+
return result;
|
|
51
|
+
};
|
|
52
|
+
})();
|
|
53
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
+
exports.getObservedContextWindowPath = getObservedContextWindowPath;
|
|
55
|
+
exports.loadObservedContextWindows = loadObservedContextWindows;
|
|
56
|
+
exports.recordObservedContextWindow = recordObservedContextWindow;
|
|
57
|
+
const node_fs_1 = require("node:fs");
|
|
58
|
+
const path = __importStar(require("node:path"));
|
|
59
|
+
const modelContext_1 = require("./modelContext");
|
|
60
|
+
const paths_1 = require("./paths");
|
|
61
|
+
const atomic_1 = require("./writers/atomic");
|
|
62
|
+
// ── Constants ──
|
|
63
|
+
const STORE_FILE_NAME = 'observed-context-windows.json';
|
|
64
|
+
const STORE_VERSION = 1;
|
|
65
|
+
/** Resolve the on-disk store path. */
|
|
66
|
+
function getObservedContextWindowPath(cacheDir) {
|
|
67
|
+
return path.join(cacheDir ?? (0, paths_1.getConfigDir)(), STORE_FILE_NAME);
|
|
68
|
+
}
|
|
69
|
+
function emptyStore() {
|
|
70
|
+
return { version: STORE_VERSION, models: {} };
|
|
71
|
+
}
|
|
72
|
+
/** Keep only well-formed entries; a hand-edited or partly-corrupt file shouldn't poison lookups. */
|
|
73
|
+
function toOverrideMap(store) {
|
|
74
|
+
const out = {};
|
|
75
|
+
for (const [modelId, entry] of Object.entries(store.models ?? {})) {
|
|
76
|
+
const window = entry?.contextWindow;
|
|
77
|
+
if (typeof window !== 'number' || !Number.isFinite(window) || window <= 0)
|
|
78
|
+
continue;
|
|
79
|
+
out[modelId] = window;
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
// ── Public API ──
|
|
84
|
+
/**
|
|
85
|
+
* Load persisted observations and apply them as context-window overrides.
|
|
86
|
+
*
|
|
87
|
+
* Always resolves — never throws. A missing, unreadable, or malformed store
|
|
88
|
+
* leaves the overrides empty and lookups fall through to catalog/static.
|
|
89
|
+
*/
|
|
90
|
+
async function loadObservedContextWindows(options = {}) {
|
|
91
|
+
const storePath = getObservedContextWindowPath(options.cacheDir);
|
|
92
|
+
try {
|
|
93
|
+
const raw = await node_fs_1.promises.readFile(storePath, 'utf8');
|
|
94
|
+
const parsed = JSON.parse(raw);
|
|
95
|
+
if (!parsed || typeof parsed !== 'object' || !parsed.models)
|
|
96
|
+
return {};
|
|
97
|
+
const overrides = toOverrideMap(parsed);
|
|
98
|
+
(0, modelContext_1._setObservedContextWindows)(overrides);
|
|
99
|
+
return overrides;
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
const code = err.code;
|
|
103
|
+
if (code !== 'ENOENT') {
|
|
104
|
+
options.logger?.(`observedContextWindows: failed to load ${storePath}: ${String(err)}`);
|
|
105
|
+
}
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Record a provider-reported context window for a model.
|
|
111
|
+
*
|
|
112
|
+
* Applies in memory immediately, then persists — but only when the value
|
|
113
|
+
* actually changed. Codex reports a window on every `token_count` event, so an
|
|
114
|
+
* unconditional write would mean disk I/O many times per session for a value
|
|
115
|
+
* that almost never moves.
|
|
116
|
+
*
|
|
117
|
+
* Always resolves — never throws. Persistence failures are logged and dropped;
|
|
118
|
+
* the in-memory override still stands for this process.
|
|
119
|
+
*/
|
|
120
|
+
async function recordObservedContextWindow(modelId, contextWindow, options = {}) {
|
|
121
|
+
const normalizedId = modelId?.trim().toLowerCase();
|
|
122
|
+
if (!normalizedId)
|
|
123
|
+
return;
|
|
124
|
+
if (!Number.isFinite(contextWindow) || contextWindow <= 0)
|
|
125
|
+
return;
|
|
126
|
+
const current = (0, modelContext_1._getObservedContextWindows)();
|
|
127
|
+
if (current[normalizedId] === contextWindow)
|
|
128
|
+
return;
|
|
129
|
+
(0, modelContext_1._setObservedContextWindows)({ ...current, [normalizedId]: contextWindow });
|
|
130
|
+
const storePath = getObservedContextWindowPath(options.cacheDir);
|
|
131
|
+
try {
|
|
132
|
+
// Lock-guarded read-modify-write: the CLI and the extension host can both
|
|
133
|
+
// be observing sessions at once, and each should merge rather than clobber.
|
|
134
|
+
await (0, atomic_1.updateJsonStoreAtomic)(storePath, emptyStore, (latest) => {
|
|
135
|
+
const models = latest?.models ?? {};
|
|
136
|
+
if (models[normalizedId]?.contextWindow === contextWindow)
|
|
137
|
+
return latest;
|
|
138
|
+
return {
|
|
139
|
+
version: STORE_VERSION,
|
|
140
|
+
models: {
|
|
141
|
+
...models,
|
|
142
|
+
[normalizedId]: { contextWindow, observedAt: new Date().toISOString() },
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
options.logger?.(`observedContextWindows: failed to persist ${storePath}: ${String(err)}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
package/dist/pricingCatalog.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ export interface HydrateResult {
|
|
|
36
36
|
source: 'cache' | 'network' | 'offline';
|
|
37
37
|
/** Number of pricing entries applied to the override map. */
|
|
38
38
|
entries: number;
|
|
39
|
+
/** Number of context-window entries applied to the override map. */
|
|
40
|
+
contextWindowEntries: number;
|
|
39
41
|
/** ISO timestamp of the fetch that produced the cache. */
|
|
40
42
|
fetchedAt: string;
|
|
41
43
|
}
|
|
@@ -56,3 +58,13 @@ export declare function hydratePricingCatalog(options: HydrateOptions): Promise<
|
|
|
56
58
|
* Exported for tests; rarely called directly.
|
|
57
59
|
*/
|
|
58
60
|
export declare function normalizeLiteLlmCatalog(raw: unknown): Record<string, ModelPricing>;
|
|
61
|
+
/**
|
|
62
|
+
* Convert a LiteLLM catalog payload to a model → context-window override map.
|
|
63
|
+
*
|
|
64
|
+
* Kept separate from `normalizeLiteLlmCatalog` rather than folded into its
|
|
65
|
+
* return type: that function is public API on a published package, and pricing
|
|
66
|
+
* and context coverage differ (an entry can have one without the other).
|
|
67
|
+
*
|
|
68
|
+
* Exported for tests; rarely called directly.
|
|
69
|
+
*/
|
|
70
|
+
export declare function normalizeLiteLlmContextWindows(raw: unknown): Record<string, number>;
|
package/dist/pricingCatalog.js
CHANGED
|
@@ -54,8 +54,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
54
54
|
exports.LITELLM_CATALOG_URL = void 0;
|
|
55
55
|
exports.hydratePricingCatalog = hydratePricingCatalog;
|
|
56
56
|
exports.normalizeLiteLlmCatalog = normalizeLiteLlmCatalog;
|
|
57
|
+
exports.normalizeLiteLlmContextWindows = normalizeLiteLlmContextWindows;
|
|
57
58
|
const node_fs_1 = require("node:fs");
|
|
58
59
|
const path = __importStar(require("node:path"));
|
|
60
|
+
const modelContext_1 = require("./modelContext");
|
|
59
61
|
const modelInfo_1 = require("./modelInfo");
|
|
60
62
|
// ── Constants ──
|
|
61
63
|
exports.LITELLM_CATALOG_URL = 'https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json';
|
|
@@ -80,43 +82,51 @@ async function hydratePricingCatalog(options) {
|
|
|
80
82
|
// 1. Fresh cache?
|
|
81
83
|
const cached = await readCache(cachePath);
|
|
82
84
|
if (cached && now - Date.parse(cached.fetchedAt) < ttlMs) {
|
|
83
|
-
(
|
|
84
|
-
return {
|
|
85
|
-
source: 'cache',
|
|
86
|
-
entries: Object.keys(cached.overrides).length,
|
|
87
|
-
fetchedAt: cached.fetchedAt,
|
|
88
|
-
};
|
|
85
|
+
return applyCacheFile(cached, 'cache');
|
|
89
86
|
}
|
|
90
87
|
// 2. Try network.
|
|
91
88
|
if (fetchImpl) {
|
|
92
89
|
const fetched = await fetchCatalog(url, fetchImpl, timeoutMs, logger);
|
|
93
90
|
if (fetched) {
|
|
94
|
-
const overrides = normalizeLiteLlmCatalog(fetched);
|
|
95
91
|
const payload = {
|
|
96
92
|
fetchedAt: new Date(now).toISOString(),
|
|
97
93
|
url,
|
|
98
|
-
overrides,
|
|
94
|
+
overrides: normalizeLiteLlmCatalog(fetched),
|
|
95
|
+
contextWindows: normalizeLiteLlmContextWindows(fetched),
|
|
99
96
|
};
|
|
100
97
|
await writeCache(cachePath, payload, logger);
|
|
101
|
-
(
|
|
102
|
-
return {
|
|
103
|
-
source: 'network',
|
|
104
|
-
entries: Object.keys(overrides).length,
|
|
105
|
-
fetchedAt: payload.fetchedAt,
|
|
106
|
-
};
|
|
98
|
+
return applyCacheFile(payload, 'network');
|
|
107
99
|
}
|
|
108
100
|
}
|
|
109
101
|
// 3. Fall back to stale cache if present.
|
|
110
102
|
if (cached) {
|
|
111
|
-
(
|
|
112
|
-
return {
|
|
113
|
-
source: 'cache',
|
|
114
|
-
entries: Object.keys(cached.overrides).length,
|
|
115
|
-
fetchedAt: cached.fetchedAt,
|
|
116
|
-
};
|
|
103
|
+
return applyCacheFile(cached, 'cache');
|
|
117
104
|
}
|
|
118
|
-
// 4. Nothing we can do — stay on static
|
|
119
|
-
return {
|
|
105
|
+
// 4. Nothing we can do — stay on static tables.
|
|
106
|
+
return {
|
|
107
|
+
source: 'offline',
|
|
108
|
+
entries: 0,
|
|
109
|
+
contextWindowEntries: 0,
|
|
110
|
+
fetchedAt: new Date(now).toISOString(),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Push a cache payload into both override maps and describe what was applied.
|
|
115
|
+
* `contextWindows` is absent on caches written before context hydration; those
|
|
116
|
+
* contribute no overrides rather than clearing whatever is already loaded.
|
|
117
|
+
*/
|
|
118
|
+
function applyCacheFile(cache, source) {
|
|
119
|
+
const contextWindows = cache.contextWindows ?? {};
|
|
120
|
+
(0, modelInfo_1._setPricingOverrides)(cache.overrides);
|
|
121
|
+
if (Object.keys(contextWindows).length > 0) {
|
|
122
|
+
(0, modelContext_1._setCatalogContextWindows)(contextWindows);
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
source,
|
|
126
|
+
entries: Object.keys(cache.overrides).length,
|
|
127
|
+
contextWindowEntries: Object.keys(contextWindows).length,
|
|
128
|
+
fetchedAt: cache.fetchedAt,
|
|
129
|
+
};
|
|
120
130
|
}
|
|
121
131
|
/**
|
|
122
132
|
* Convert a LiteLLM catalog payload to our `ModelPricing` override map.
|
|
@@ -145,6 +155,57 @@ function normalizeLiteLlmCatalog(raw) {
|
|
|
145
155
|
}
|
|
146
156
|
return out;
|
|
147
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Convert a LiteLLM catalog payload to a model → context-window override map.
|
|
160
|
+
*
|
|
161
|
+
* Kept separate from `normalizeLiteLlmCatalog` rather than folded into its
|
|
162
|
+
* return type: that function is public API on a published package, and pricing
|
|
163
|
+
* and context coverage differ (an entry can have one without the other).
|
|
164
|
+
*
|
|
165
|
+
* Exported for tests; rarely called directly.
|
|
166
|
+
*/
|
|
167
|
+
function normalizeLiteLlmContextWindows(raw) {
|
|
168
|
+
const out = {};
|
|
169
|
+
if (!raw || typeof raw !== 'object')
|
|
170
|
+
return out;
|
|
171
|
+
// Bare-model aliases are gathered separately rather than written as we go. A
|
|
172
|
+
// `provider/model` entry describes that *provider's deployment* window, which
|
|
173
|
+
// is often a truncation of the model's own — the live catalog carries five
|
|
174
|
+
// different windows for `claude-sonnet-4`, from GitHub Copilot's 128K to
|
|
175
|
+
// Vertex's 1M. Taking whichever appeared first in the JSON is a coin flip, so
|
|
176
|
+
// an alias is only promoted when every provider mentioning it agrees.
|
|
177
|
+
const aliasWindows = new Map();
|
|
178
|
+
for (const [key, entry] of Object.entries(raw)) {
|
|
179
|
+
// LiteLLM ships a `sample_spec` header entry. Skip it.
|
|
180
|
+
if (key === 'sample_spec')
|
|
181
|
+
continue;
|
|
182
|
+
if (!entry || typeof entry !== 'object')
|
|
183
|
+
continue;
|
|
184
|
+
const window = entry.max_input_tokens;
|
|
185
|
+
if (typeof window !== 'number' || !Number.isFinite(window) || window <= 0)
|
|
186
|
+
continue;
|
|
187
|
+
out[key] = window;
|
|
188
|
+
const bare = stripProviderPrefix(key);
|
|
189
|
+
if (bare && bare !== key) {
|
|
190
|
+
const seen = aliasWindows.get(bare) ?? new Set();
|
|
191
|
+
seen.add(window);
|
|
192
|
+
aliasWindows.set(bare, seen);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
for (const [bare, windows] of aliasWindows) {
|
|
196
|
+
// A top-level entry for the same ID is authoritative — never shadow it.
|
|
197
|
+
if (out[bare] !== undefined)
|
|
198
|
+
continue;
|
|
199
|
+
// Providers disagree, so none of their numbers can be attributed to the
|
|
200
|
+
// bare model. Record nothing and let a more trusted layer answer.
|
|
201
|
+
if (windows.size !== 1)
|
|
202
|
+
continue;
|
|
203
|
+
const [only] = windows;
|
|
204
|
+
if (typeof only === 'number')
|
|
205
|
+
out[bare] = only;
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
148
209
|
// ── Internals ──
|
|
149
210
|
function liteLlmEntryToPricing(entry) {
|
|
150
211
|
const input = entry.input_cost_per_token;
|
package/dist/providers/codex.js
CHANGED
|
@@ -53,6 +53,7 @@ const codexParser_1 = require("../parsers/codexParser");
|
|
|
53
53
|
const codexDatabase_1 = require("./codexDatabase");
|
|
54
54
|
const codexProfiles_1 = require("../codexProfiles");
|
|
55
55
|
const modelContext_1 = require("../modelContext");
|
|
56
|
+
const observedContextWindows_1 = require("../observedContextWindows");
|
|
56
57
|
const usageNormalization_1 = require("../usageNormalization");
|
|
57
58
|
// ---------------------------------------------------------------------------
|
|
58
59
|
// Helper functions
|
|
@@ -510,10 +511,12 @@ class CodexReader {
|
|
|
510
511
|
// Skip malformed JSON lines
|
|
511
512
|
}
|
|
512
513
|
}
|
|
513
|
-
// Propagate model_context_window from token_count events to the provider
|
|
514
|
+
// Propagate model_context_window from token_count events to the provider.
|
|
515
|
+
// The model comes along so the value can be persisted per model — the
|
|
516
|
+
// window Codex reports is tier-specific, not the published maximum.
|
|
514
517
|
const mcw = this.parser.getModelContextWindow();
|
|
515
518
|
if (mcw && this.onContextWindowLimit) {
|
|
516
|
-
this.onContextWindowLimit(mcw);
|
|
519
|
+
this.onContextWindowLimit(mcw, this.parser.getCurrentModel() ?? undefined);
|
|
517
520
|
}
|
|
518
521
|
// Propagate rate_limits from token_count events to the provider
|
|
519
522
|
const rateLimits = this.parser.getLastRateLimits();
|
|
@@ -791,8 +794,15 @@ class CodexProvider {
|
|
|
791
794
|
}
|
|
792
795
|
// --- Data reading ---
|
|
793
796
|
createReader(sessionPath) {
|
|
794
|
-
return new CodexReader(sessionPath, (limit) => {
|
|
797
|
+
return new CodexReader(sessionPath, (limit, modelId) => {
|
|
795
798
|
this.dynamicContextWindowLimit = limit;
|
|
799
|
+
if (modelId) {
|
|
800
|
+
// Fire-and-forget: persist so historical/static views keep this
|
|
801
|
+
// tier-specific window instead of falling back to the catalog maximum.
|
|
802
|
+
void (0, observedContextWindows_1.recordObservedContextWindow)(modelId, limit).catch(() => {
|
|
803
|
+
/* non-fatal; the in-memory limit above still applies */
|
|
804
|
+
});
|
|
805
|
+
}
|
|
796
806
|
}, (limits) => {
|
|
797
807
|
this.lastRateLimitsData = limits;
|
|
798
808
|
}, (usage) => {
|
package/package.json
CHANGED