sidekick-shared 0.26.0 → 0.26.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/CHANGELOG.md +8 -0
- package/README.md +6 -5
- package/dist/modelContext.js +8 -3
- package/dist/modelInfo.js +45 -27
- package/dist/quotaPresentation.js +17 -5
- package/dist/quotaResolver.d.ts +6 -4
- package/dist/quotaResolver.js +36 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ All notable changes to sidekick-shared will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.26.1] - 2026-09-05
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Static model metadata verified on 2026-09-05 adds exact Fable 5.1 pricing/context entries in dashed and dotted forms, with its $0.25/M cache-read rate, and GPT-6 Astra pricing with a 1.05M context window. GPT-5.6 Sol/Terra/Luna rates and the Sol alias are current; GPT-5.4 mini cache reads cost $0.075/M and mini/nano context windows are 400K. Sonnet 5's permanent $2/$10 rate is documented. Catalog overrides, observed limits, and historical models are preserved.
|
|
13
|
+
- `resolveQuota({ preferFresh: false })` tries the API before local samples when API access is allowed. Failed live queries select the newer session or cached sample by capture time, prefer the cache on ties, and retain the API failure. Only a selected session sample is persisted.
|
|
14
|
+
- Codex quota failures name Codex instead of Claude or Anthropic. Default cache preferences and API-disabled resolution are unchanged.
|
|
15
|
+
|
|
8
16
|
## [0.26.0] - 2026-09-05
|
|
9
17
|
|
|
10
18
|
### Added
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Types, parsers, providers, readers, formatters, aggregation, search, reporting,
|
|
|
9
9
|
|
|
10
10
|
## Recent additions
|
|
11
11
|
|
|
12
|
-
- **One token vocabulary and cost provenance (0.26.0)** — `summarizeTokens()` gives every surface the same `total` (input + output + cache writes + cache reads) and `context` (input + cache) figures; `AggregatedTokens.costUsd` carries `costProvenance` so reports can say whether a cost was provider-reported or estimated; `parseClaudeStatuslinePayload()` / `quotaFromStatuslinePayload()` turn Claude Code's status-line JSON into an official `statusline` quota source; `readQuotaSnapshot()` reports `ageMs` / `freshness` and keeps the sample's origin as `capturedSource`; `resolveQuota()`
|
|
12
|
+
- **One token vocabulary and cost provenance (0.26.0)** — `summarizeTokens()` gives every surface the same `total` (input + output + cache writes + cache reads) and `context` (input + cache) figures; `AggregatedTokens.costUsd` carries `costProvenance` so reports can say whether a cost was provider-reported or estimated; `parseClaudeStatuslinePayload()` / `quotaFromStatuslinePayload()` turn Claude Code's status-line JSON into an official `statusline` quota source; `readQuotaSnapshot()` reports `ageMs` / `freshness` and keeps the sample's origin as `capturedSource`; `resolveQuota()` shares quota resolution across CLI, MCP, and dashboard startup, with default local-sample preferences and an API-first option used by one-shot Codex queries; `readSessionFileStats()` / `computeSessionFileStats()` give every provider one `readSessionStats()` over the shared aggregator, with `availability`, `costProvenance`, and `toolFailures` on `SessionFileStats`; `collectUsageEvents()` reads priced usage events from session logs with a per-session cache, `computeBillingBlocks()` groups them into five-hour billing blocks, and `bucketUsage()` turns them into day, week, month, or session rows; `applySessionSummary()` and `importSessionHistory()` let any host fold finished sessions into `historical-data.json` the way the extension does; observed sessions are read once per discovery, re-read only when their fingerprint changes, and classified from events by `classifySessionActivity()`; `readSessionReportInputs()` derives report metrics and transcript from one read; `writeStateFile()` / `readStateFile()` (also on the `statusline` subpath) maintain the public, versioned `state.json` for external tools, validated by `sidekickStateFileSchema`; `getScheduledPeakHoursState()` and `evaluateQuotaThresholds()` are shared by the CLI and extension dashboards.
|
|
13
13
|
- **Host-safe APIs (0.25.0)** — `listSessionPreviewsAsync()` / `readSessionPreviewAsync()` read previews with bounded concurrency and cooperative yielding; `ObservedSessionCollector.subscribe()` and `SessionMonitor.subscribe()` replace host polling loops with debounced change batches; `createSessionProviders({ onDiagnostic })` constructs every usable provider with structured diagnostics instead of throwing; `findSessionById()` resolves one session without scanning; `exportResolvedModelCatalog()` / `importResolvedModelCatalog()` and `registerModelAlias()` transfer context/pricing resolutions across realms; `onAccountsChanged()` reports login/logout/switch events; provider constructors perform no environment I/O, and quota pollers stay dormant without an account.
|
|
14
14
|
- **Native session-file helpers** — `encodeClaudeWorkspacePath()` / `getClaudeSessionDirectory()` locate Claude Code's `~/.claude/projects/` directories with the real on-disk encoding, `findCodexRolloutFile()` resolves a Codex session id to its rollout path, `readCodexHistory()` tail-reads `~/.codex/history.jsonl`, `listSessionPreviews()` / `readSessionPreview()` build a cheap stat-first recent-sessions index across providers, and `parseMcpToolName()` splits `mcp__<server>__<tool>` identifiers (browser-safe).
|
|
15
15
|
- **Non-blocking account APIs & locked sync writers** — every login/switch entry point gains an async variant (`switchAccountAsync()`, `getAccountLoginStatusAsync()`, `finalizeAccountLoginAsync()`, `prepareCodexAccountAsync()`, `finalizeCodexAccountAsync()`, `switchToCodexAccountAsync()`) that keeps `codex` CLI probes off the event loop, and synchronous callers get the same cross-process store lock via `atomicWriteFileSync()` / `updateJsonStoreAtomicSync()` / `withFileLockSync()`, with progress-based timeouts and abandoned-lock reclaim.
|
|
@@ -475,24 +475,25 @@ if (!peak.unavailable && peak.isPeak) {
|
|
|
475
475
|
}
|
|
476
476
|
```
|
|
477
477
|
|
|
478
|
-
### Resolve quota with
|
|
478
|
+
### Resolve quota with shared source policies
|
|
479
479
|
|
|
480
480
|
```typescript
|
|
481
481
|
import { resolveQuota } from 'sidekick-shared';
|
|
482
482
|
|
|
483
|
-
//
|
|
483
|
+
// Default: fresh persisted sample (status line, session, or API; under five minutes) →
|
|
484
484
|
// session logs (Codex rollouts) → provider API → older sample labelled by age.
|
|
485
485
|
const quota = await resolveQuota({ providerId: 'claude-code' });
|
|
486
486
|
console.log(quota.resolution); // 'snapshot-fresh' | 'session' | 'api' | 'snapshot-aging' | 'snapshot-stale' | 'unavailable'
|
|
487
487
|
console.log(quota.source, quota.capturedSource, quota.freshness, quota.ageMs);
|
|
488
488
|
|
|
489
|
-
//
|
|
489
|
+
// Ask the API before any local sample: used by every one-shot Codex query
|
|
490
|
+
// and by --refresh for other providers. API failures retain local fallbacks.
|
|
490
491
|
await resolveQuota({ providerId: 'codex', workspacePath: process.cwd(), preferFresh: false });
|
|
491
492
|
// Paint from local samples only (a poller will fetch live data next).
|
|
492
493
|
await resolveQuota({ providerId: 'claude-code', allowApi: false });
|
|
493
494
|
```
|
|
494
495
|
|
|
495
|
-
`resolveQuota()` is
|
|
496
|
+
`resolveQuota()` is shared by `sidekick quota`, `quota --all`, the MCP `get_quota_status` tool, and dashboard startup. CLI and MCP Codex queries pass `preferFresh: false` to fetch live utilization and reset credits every time; dashboard schedules and default cache preferences are unchanged. After a failed live query, the newer session or persisted sample wins by `capturedAt`, with ties going to the persisted sample and missing timestamps treated as oldest. Only a selected session sample is persisted. Fallback results keep their source and age plus the API `failure` descriptor. `allowApi: false` always prevents network access, including when `preferFresh` is false. Every side effect is injectable (`readSnapshot`, `writeSnapshot`, `fetchImpl`, `getClaudeAccessToken`, the account resolvers) for tests.
|
|
496
497
|
|
|
497
498
|
### Fetch subscription quota
|
|
498
499
|
|
package/dist/modelContext.js
CHANGED
|
@@ -42,9 +42,11 @@ exports._getModelContextIds = _getModelContextIds;
|
|
|
42
42
|
exports.resolveModelContextWindow = resolveModelContextWindow;
|
|
43
43
|
exports.getModelContextWindowSize = getModelContextWindowSize;
|
|
44
44
|
const modelAliases_1 = require("./modelAliases");
|
|
45
|
-
/** Known model context window sizes (in tokens). */
|
|
45
|
+
/** Known model context window sizes (in tokens); current models verified 2026-09-05. */
|
|
46
46
|
const MODEL_CONTEXT_SIZES = {
|
|
47
|
-
// Claude — native 1M context (Fable 5
|
|
47
|
+
// Claude — native 1M context (Fable 5+, Opus 4.6+, Sonnet 4.6+)
|
|
48
|
+
'claude-fable-5-1': 1_000_000,
|
|
49
|
+
'claude-fable-5.1': 1_000_000,
|
|
48
50
|
'claude-fable-5': 1_000_000,
|
|
49
51
|
'claude-opus-5': 1_000_000,
|
|
50
52
|
'claude-opus-4-8': 1_000_000,
|
|
@@ -67,16 +69,19 @@ const MODEL_CONTEXT_SIZES = {
|
|
|
67
69
|
'claude-3-haiku': 200_000,
|
|
68
70
|
// OpenAI GPT-4.1 series (1M context)
|
|
69
71
|
'gpt-4.1': 1_048_576,
|
|
70
|
-
// OpenAI GPT-5 series (keys sorted longest-first below; explicit entries
|
|
72
|
+
// OpenAI GPT-5/6 series (keys sorted longest-first below; explicit entries
|
|
71
73
|
// for every variant so prefix matching can't misclassify a new one).
|
|
72
74
|
// These are published model maxima. Codex reports a smaller *effective*
|
|
73
75
|
// window per account tier via token_count; that value wins through the
|
|
74
76
|
// observed-override layer.
|
|
77
|
+
'gpt-6-astra': 1_050_000,
|
|
75
78
|
'gpt-5.6-sol': 1_050_000,
|
|
76
79
|
'gpt-5.6-terra': 1_050_000,
|
|
77
80
|
'gpt-5.6-luna': 1_050_000,
|
|
78
81
|
'gpt-5.6': 1_050_000,
|
|
79
82
|
'gpt-5.5': 1_050_000,
|
|
83
|
+
'gpt-5.4-mini': 400_000,
|
|
84
|
+
'gpt-5.4-nano': 400_000,
|
|
80
85
|
'gpt-5.4': 1_050_000,
|
|
81
86
|
'gpt-5.3-codex-spark': 128_000,
|
|
82
87
|
'gpt-5.3-codex': 400_000,
|
package/dist/modelInfo.js
CHANGED
|
@@ -50,9 +50,10 @@ Object.defineProperty(exports, "MODEL_PROVIDER_IDS", { enumerable: true, get: fu
|
|
|
50
50
|
* against `claude-sonnet-4.5`.
|
|
51
51
|
*
|
|
52
52
|
* Sources:
|
|
53
|
-
* - Anthropic: https://
|
|
54
|
-
* - OpenAI: https://openai.com/api/
|
|
55
|
-
*
|
|
53
|
+
* - Anthropic: https://platform.claude.com/docs/en/about-claude/pricing
|
|
54
|
+
* - OpenAI: https://developers.openai.com/api/docs/models
|
|
55
|
+
* Current Anthropic and GPT-5.6/6 rates verified: 2026-09-05.
|
|
56
|
+
* Runtime LiteLLM hydration refreshes this offline baseline.
|
|
56
57
|
*
|
|
57
58
|
* Anthropic keys appear in both dashed (`claude-opus-4-8`, the real model-ID
|
|
58
59
|
* form) and dotted (`claude-opus-4.8`, the LiteLLM catalog form) spellings —
|
|
@@ -60,6 +61,19 @@ Object.defineProperty(exports, "MODEL_PROVIDER_IDS", { enumerable: true, get: fu
|
|
|
60
61
|
*/
|
|
61
62
|
const PRICING_TABLE = {
|
|
62
63
|
// ── Anthropic: Claude ──
|
|
64
|
+
// Fable 5.1 cache reads cost 0.025x input; Fable 5 retains its 0.1x rate.
|
|
65
|
+
'claude-fable-5-1': {
|
|
66
|
+
inputCostPerMillion: 10.0,
|
|
67
|
+
outputCostPerMillion: 50.0,
|
|
68
|
+
cacheWriteCostPerMillion: 12.5,
|
|
69
|
+
cacheReadCostPerMillion: 0.25,
|
|
70
|
+
},
|
|
71
|
+
'claude-fable-5.1': {
|
|
72
|
+
inputCostPerMillion: 10.0,
|
|
73
|
+
outputCostPerMillion: 50.0,
|
|
74
|
+
cacheWriteCostPerMillion: 12.5,
|
|
75
|
+
cacheReadCostPerMillion: 0.25,
|
|
76
|
+
},
|
|
63
77
|
'claude-fable-5': {
|
|
64
78
|
inputCostPerMillion: 10.0,
|
|
65
79
|
outputCostPerMillion: 50.0,
|
|
@@ -84,11 +98,8 @@ const PRICING_TABLE = {
|
|
|
84
98
|
cacheWriteCostPerMillion: 1.0,
|
|
85
99
|
cacheReadCostPerMillion: 0.08,
|
|
86
100
|
},
|
|
87
|
-
// Sonnet 5
|
|
88
|
-
//
|
|
89
|
-
// Sonnet 5 is $2/$10, not the $3/$15 every earlier Sonnet charged, and it
|
|
90
|
-
// carries no >200K surcharge the way Sonnet 4.5 does. The catalog agrees
|
|
91
|
-
// across all eleven of its Sonnet 5 entries.
|
|
101
|
+
// Sonnet 5's $2/$10 introductory rate is now permanent. Standard pricing
|
|
102
|
+
// applies across its full 1M context window.
|
|
92
103
|
'claude-sonnet-5': {
|
|
93
104
|
inputCostPerMillion: 2.0,
|
|
94
105
|
outputCostPerMillion: 10.0,
|
|
@@ -234,37 +245,44 @@ const PRICING_TABLE = {
|
|
|
234
245
|
cacheWriteCostPerMillion: 0,
|
|
235
246
|
cacheReadCostPerMillion: 0,
|
|
236
247
|
},
|
|
237
|
-
// ── OpenAI: GPT-5
|
|
238
|
-
// Published rates,
|
|
248
|
+
// ── OpenAI: GPT-5/6 families ──
|
|
249
|
+
// Published rates, cross-checked with the LiteLLM catalog. Runtime hydration
|
|
239
250
|
// still supersedes these; they are the offline baseline.
|
|
240
251
|
//
|
|
241
252
|
// Every tier that ships at a rate of its own needs an entry of its own. Keys
|
|
242
253
|
// are matched longest-first, so a `-mini`/`-nano`/`-pro` variant with no entry
|
|
243
254
|
// silently inherits its base model's rate and reports a wrong number rather
|
|
244
255
|
// than none — `gpt-5-nano` read 25x its true cost this way.
|
|
256
|
+
'gpt-6-astra': {
|
|
257
|
+
inputCostPerMillion: 10.0,
|
|
258
|
+
outputCostPerMillion: 50.0,
|
|
259
|
+
cacheWriteCostPerMillion: 12.5,
|
|
260
|
+
cacheReadCostPerMillion: 1.0,
|
|
261
|
+
},
|
|
245
262
|
'gpt-5.6-sol': {
|
|
246
|
-
inputCostPerMillion:
|
|
247
|
-
outputCostPerMillion:
|
|
248
|
-
cacheWriteCostPerMillion:
|
|
249
|
-
cacheReadCostPerMillion: 0.
|
|
263
|
+
inputCostPerMillion: 4.0,
|
|
264
|
+
outputCostPerMillion: 20.0,
|
|
265
|
+
cacheWriteCostPerMillion: 5.0,
|
|
266
|
+
cacheReadCostPerMillion: 0.4,
|
|
250
267
|
},
|
|
251
268
|
'gpt-5.6-terra': {
|
|
252
|
-
inputCostPerMillion: 2.
|
|
253
|
-
outputCostPerMillion:
|
|
254
|
-
cacheWriteCostPerMillion:
|
|
255
|
-
cacheReadCostPerMillion: 0.
|
|
269
|
+
inputCostPerMillion: 2.0,
|
|
270
|
+
outputCostPerMillion: 12.0,
|
|
271
|
+
cacheWriteCostPerMillion: 2.5,
|
|
272
|
+
cacheReadCostPerMillion: 0.2,
|
|
256
273
|
},
|
|
257
274
|
'gpt-5.6-luna': {
|
|
258
|
-
inputCostPerMillion:
|
|
259
|
-
outputCostPerMillion:
|
|
260
|
-
cacheWriteCostPerMillion:
|
|
261
|
-
cacheReadCostPerMillion: 0.
|
|
275
|
+
inputCostPerMillion: 0.2,
|
|
276
|
+
outputCostPerMillion: 1.2,
|
|
277
|
+
cacheWriteCostPerMillion: 0.25,
|
|
278
|
+
cacheReadCostPerMillion: 0.02,
|
|
262
279
|
},
|
|
280
|
+
// The GPT-5.6 alias routes to Sol.
|
|
263
281
|
'gpt-5.6': {
|
|
264
|
-
inputCostPerMillion:
|
|
265
|
-
outputCostPerMillion:
|
|
266
|
-
cacheWriteCostPerMillion:
|
|
267
|
-
cacheReadCostPerMillion: 0.
|
|
282
|
+
inputCostPerMillion: 4.0,
|
|
283
|
+
outputCostPerMillion: 20.0,
|
|
284
|
+
cacheWriteCostPerMillion: 5.0,
|
|
285
|
+
cacheReadCostPerMillion: 0.4,
|
|
268
286
|
},
|
|
269
287
|
'gpt-5.5-pro': {
|
|
270
288
|
inputCostPerMillion: 30.0,
|
|
@@ -282,7 +300,7 @@ const PRICING_TABLE = {
|
|
|
282
300
|
inputCostPerMillion: 0.75,
|
|
283
301
|
outputCostPerMillion: 4.5,
|
|
284
302
|
cacheWriteCostPerMillion: 0,
|
|
285
|
-
cacheReadCostPerMillion: 0.
|
|
303
|
+
cacheReadCostPerMillion: 0.075,
|
|
286
304
|
},
|
|
287
305
|
'gpt-5.4-nano': {
|
|
288
306
|
inputCostPerMillion: 0.2,
|
|
@@ -23,8 +23,20 @@ function formatRetryAfter(ms) {
|
|
|
23
23
|
function describeQuotaFailure(quota) {
|
|
24
24
|
if (!quota || quota.available || !quota.failureKind)
|
|
25
25
|
return null;
|
|
26
|
+
const isCodex = quota.providerId === 'codex';
|
|
27
|
+
const apiName = isCodex ? 'Codex' : 'Anthropic';
|
|
26
28
|
switch (quota.failureKind) {
|
|
27
29
|
case 'auth':
|
|
30
|
+
if (isCodex) {
|
|
31
|
+
return {
|
|
32
|
+
severity: 'error',
|
|
33
|
+
title: quota.httpStatus ? 'Codex sign-in expired' : 'Codex sign-in required',
|
|
34
|
+
message: quota.error ?? 'The current Codex login could not be used.',
|
|
35
|
+
detail: 'Run `codex login` to sign in, then retry quota refresh.',
|
|
36
|
+
alertKey: `auth:${quota.httpStatus ?? 'no-credentials'}`,
|
|
37
|
+
isRetryable: false,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
28
40
|
if (quota.error === 'No OAuth token available') {
|
|
29
41
|
return {
|
|
30
42
|
severity: 'error',
|
|
@@ -47,7 +59,7 @@ function describeQuotaFailure(quota) {
|
|
|
47
59
|
return {
|
|
48
60
|
severity: 'warning',
|
|
49
61
|
title: 'Quota API unreachable',
|
|
50
|
-
message:
|
|
62
|
+
message: `Could not reach ${apiName} from the current environment.`,
|
|
51
63
|
detail: 'Check connectivity, proxy, or firewall settings, then retry.',
|
|
52
64
|
alertKey: 'network',
|
|
53
65
|
isRetryable: true,
|
|
@@ -59,7 +71,7 @@ function describeQuotaFailure(quota) {
|
|
|
59
71
|
message: quota.retryAfterMs != null
|
|
60
72
|
? `Retry in ${formatRetryAfter(quota.retryAfterMs)}.`
|
|
61
73
|
: 'Retry shortly.',
|
|
62
|
-
detail: quota.httpStatus != null ?
|
|
74
|
+
detail: quota.httpStatus != null ? `${apiName} returned HTTP ${quota.httpStatus}.` : undefined,
|
|
63
75
|
alertKey: `rate_limit:${quota.httpStatus ?? 429}`,
|
|
64
76
|
isRetryable: true,
|
|
65
77
|
};
|
|
@@ -68,8 +80,8 @@ function describeQuotaFailure(quota) {
|
|
|
68
80
|
severity: 'warning',
|
|
69
81
|
title: 'Quota API unavailable',
|
|
70
82
|
message: quota.httpStatus != null
|
|
71
|
-
?
|
|
72
|
-
:
|
|
83
|
+
? `${apiName} returned HTTP ${quota.httpStatus}. Try again shortly.`
|
|
84
|
+
: `${apiName} quota data is temporarily unavailable.`,
|
|
73
85
|
alertKey: `server:${quota.httpStatus ?? 'unknown'}`,
|
|
74
86
|
isRetryable: true,
|
|
75
87
|
};
|
|
@@ -78,7 +90,7 @@ function describeQuotaFailure(quota) {
|
|
|
78
90
|
severity: 'error',
|
|
79
91
|
title: 'Unexpected quota response',
|
|
80
92
|
message: quota.httpStatus != null
|
|
81
|
-
?
|
|
93
|
+
? `${apiName} returned HTTP ${quota.httpStatus}.`
|
|
82
94
|
: (quota.error ?? 'Quota data could not be retrieved.'),
|
|
83
95
|
detail: 'This failure is not classified as retryable.',
|
|
84
96
|
alertKey: `unknown:${quota.httpStatus ?? 'none'}`,
|
package/dist/quotaResolver.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `sidekick quota`, `quota --all`, the MCP facts server, and both dashboards
|
|
5
5
|
* used to pick between the live API, session logs, and the persisted snapshot
|
|
6
|
-
* in four different orders. `resolveQuota()`
|
|
6
|
+
* in four different orders. `resolveQuota()` defaults to this precedence:
|
|
7
7
|
*
|
|
8
8
|
* 1. a fresh persisted snapshot (younger than `QUOTA_FRESH_MAX_AGE_MS`, whatever
|
|
9
9
|
* its origin: the official status-line sample, a session log, or an earlier
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
* 4. an aging or stale snapshot, labelled as such, with the API failure attached;
|
|
15
15
|
* 5. an unavailable state describing why nothing could be resolved.
|
|
16
16
|
*
|
|
17
|
-
* `preferFresh: false`
|
|
17
|
+
* `preferFresh: false` asks the API first when allowed (the `--refresh` flag
|
|
18
|
+
* and one-shot Codex queries). On failure, the newer session or persisted
|
|
19
|
+
* sample wins, with ties going to the persisted sample. Every result carries
|
|
18
20
|
* `source`, `capturedSource`, `freshness`, `ageMs`, and a `resolution` tag.
|
|
19
21
|
*/
|
|
20
22
|
import type { ResolvedActiveAccount, SavedAccountProfile } from './accountRegistry';
|
|
@@ -38,9 +40,9 @@ export interface ResolveQuotaOptions<T extends QuotaResolveProviderId = QuotaRes
|
|
|
38
40
|
accountId?: string;
|
|
39
41
|
/** Workspace whose Codex rollouts are searched first for session-derived rate limits. */
|
|
40
42
|
workspacePath?: string;
|
|
41
|
-
/**
|
|
43
|
+
/** Prefer local samples (default true). False asks the API before any local sample when allowed. */
|
|
42
44
|
preferFresh?: boolean;
|
|
43
|
-
/** Allow
|
|
45
|
+
/** Allow provider API calls (default true), including when preferFresh is false. */
|
|
44
46
|
allowApi?: boolean;
|
|
45
47
|
/** Re-point a stale saved-account pointer while resolving the account (default true). */
|
|
46
48
|
selfHeal?: boolean;
|
package/dist/quotaResolver.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* `sidekick quota`, `quota --all`, the MCP facts server, and both dashboards
|
|
6
6
|
* used to pick between the live API, session logs, and the persisted snapshot
|
|
7
|
-
* in four different orders. `resolveQuota()`
|
|
7
|
+
* in four different orders. `resolveQuota()` defaults to this precedence:
|
|
8
8
|
*
|
|
9
9
|
* 1. a fresh persisted snapshot (younger than `QUOTA_FRESH_MAX_AGE_MS`, whatever
|
|
10
10
|
* its origin: the official status-line sample, a session log, or an earlier
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
* 4. an aging or stale snapshot, labelled as such, with the API failure attached;
|
|
16
16
|
* 5. an unavailable state describing why nothing could be resolved.
|
|
17
17
|
*
|
|
18
|
-
* `preferFresh: false`
|
|
18
|
+
* `preferFresh: false` asks the API first when allowed (the `--refresh` flag
|
|
19
|
+
* and one-shot Codex queries). On failure, the newer session or persisted
|
|
20
|
+
* sample wins, with ties going to the persisted sample. Every result carries
|
|
19
21
|
* `source`, `capturedSource`, `freshness`, `ageMs`, and a `resolution` tag.
|
|
20
22
|
*/
|
|
21
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -146,6 +148,10 @@ function snapshotResolution(state) {
|
|
|
146
148
|
return 'snapshot-aging';
|
|
147
149
|
return 'snapshot-stale';
|
|
148
150
|
}
|
|
151
|
+
function captureTimeMs(state) {
|
|
152
|
+
const capturedMs = state.capturedAt ? Date.parse(state.capturedAt) : NaN;
|
|
153
|
+
return Number.isFinite(capturedMs) ? capturedMs : Number.NEGATIVE_INFINITY;
|
|
154
|
+
}
|
|
149
155
|
/**
|
|
150
156
|
* Resolve the current quota for one provider with the shared precedence
|
|
151
157
|
* described at the top of this file.
|
|
@@ -160,6 +166,7 @@ async function resolveQuota(options) {
|
|
|
160
166
|
const capturedAt = now.toISOString();
|
|
161
167
|
const preferFresh = options.preferFresh ?? true;
|
|
162
168
|
const allowApi = options.allowApi ?? true;
|
|
169
|
+
const apiFirst = !preferFresh && allowApi;
|
|
163
170
|
const readSnapshot = options.readSnapshot ?? quotaSnapshots_1.readQuotaSnapshot;
|
|
164
171
|
const writeSnapshot = options.writeSnapshot ?? quotaSnapshots_1.writeQuotaSnapshot;
|
|
165
172
|
const identity = resolveIdentity(options);
|
|
@@ -178,19 +185,27 @@ async function resolveQuota(options) {
|
|
|
178
185
|
if (preferFresh && snapshot && snapshot.freshness === 'fresh') {
|
|
179
186
|
return finish(snapshot, 'snapshot-fresh');
|
|
180
187
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|
|
188
|
+
const readLocalCodex = () => (0, codexQuota_1.resolveCodexQuotaFromLocalSources)({
|
|
189
|
+
workspacePath: options.workspacePath,
|
|
190
|
+
provider: options.codexProvider,
|
|
191
|
+
activeAccount: identity.codexAccount,
|
|
192
|
+
codexHome: options.codexHome,
|
|
193
|
+
// Only rollout hits count as "session-derived"; the snapshot fallback is
|
|
194
|
+
// handled below so it can be labelled by age.
|
|
195
|
+
readSnapshot: () => null,
|
|
196
|
+
// Inspect candidates without overwriting the persisted fallback. Only
|
|
197
|
+
// the selected session sample is written, using the resolved account key.
|
|
198
|
+
writeSnapshot: () => { },
|
|
199
|
+
});
|
|
200
|
+
const finishLocal = (local) => {
|
|
201
|
+
if (identity.accountId)
|
|
202
|
+
writeSnapshot('codex', identity.accountId, local);
|
|
203
|
+
return finish(local, 'session');
|
|
204
|
+
};
|
|
205
|
+
if (providerId === 'codex' && !apiFirst) {
|
|
206
|
+
const local = readLocalCodex();
|
|
207
|
+
if (local?.available)
|
|
208
|
+
return finishLocal(local);
|
|
194
209
|
}
|
|
195
210
|
let apiState = null;
|
|
196
211
|
if (allowApi) {
|
|
@@ -202,6 +217,12 @@ async function resolveQuota(options) {
|
|
|
202
217
|
return finish(apiState, 'api');
|
|
203
218
|
}
|
|
204
219
|
}
|
|
220
|
+
if (providerId === 'codex' && apiFirst) {
|
|
221
|
+
const local = readLocalCodex();
|
|
222
|
+
if (local?.available && (!snapshot || captureTimeMs(local) > captureTimeMs(snapshot))) {
|
|
223
|
+
return { ...finishLocal(local), failure: (0, quotaPresentation_1.describeQuotaFailure)(apiState) };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
205
226
|
if (snapshot) {
|
|
206
227
|
const fallback = finish(snapshot, snapshotResolution(snapshot));
|
|
207
228
|
return apiState ? { ...fallback, failure: (0, quotaPresentation_1.describeQuotaFailure)(apiState) } : fallback;
|