sidekick-shared 0.24.5 → 0.25.0

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.
Files changed (76) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +159 -52
  3. package/dist/accountChangeSignal.d.ts +3 -0
  4. package/dist/accountChangeSignal.js +19 -0
  5. package/dist/accountChanges.d.ts +21 -0
  6. package/dist/accountChanges.js +151 -0
  7. package/dist/accountRegistry.d.ts +3 -1
  8. package/dist/accountRegistry.js +5 -0
  9. package/dist/browser.d.ts +8 -3
  10. package/dist/browser.js +15 -1
  11. package/dist/codexQuotaWatcher.d.ts +6 -0
  12. package/dist/codexQuotaWatcher.js +101 -47
  13. package/dist/index.d.ts +23 -13
  14. package/dist/index.js +34 -10
  15. package/dist/modelAliases.d.ts +13 -0
  16. package/dist/modelAliases.js +58 -0
  17. package/dist/modelCatalog.d.ts +32 -0
  18. package/dist/modelCatalog.js +145 -0
  19. package/dist/modelContext.d.ts +36 -3
  20. package/dist/modelContext.js +109 -34
  21. package/dist/modelInfo.d.ts +30 -1
  22. package/dist/modelInfo.js +116 -26
  23. package/dist/multiProviderQuotaService.d.ts +8 -0
  24. package/dist/multiProviderQuotaService.js +64 -3
  25. package/dist/node.d.ts +4 -4
  26. package/dist/node.js +4 -1
  27. package/dist/observedSessionCollector.d.ts +75 -4
  28. package/dist/observedSessionCollector.js +452 -42
  29. package/dist/parsers/codexHistory.d.ts +4 -2
  30. package/dist/parsers/codexHistory.js +9 -2
  31. package/dist/parsers/sessionActivityDetector.d.ts +6 -0
  32. package/dist/parsers/sessionActivityDetector.js +17 -0
  33. package/dist/providerIds.d.ts +11 -0
  34. package/dist/providerIds.js +9 -0
  35. package/dist/providerQuota.d.ts +3 -1
  36. package/dist/providerQuota.js +3 -0
  37. package/dist/providers/claudeCode.d.ts +10 -1
  38. package/dist/providers/claudeCode.js +204 -39
  39. package/dist/providers/codex.d.ts +18 -1
  40. package/dist/providers/codex.js +269 -40
  41. package/dist/providers/codexDatabase.d.ts +9 -0
  42. package/dist/providers/codexDatabase.js +107 -2
  43. package/dist/providers/diagnostics.d.ts +13 -0
  44. package/dist/providers/diagnostics.js +88 -0
  45. package/dist/providers/factory.d.ts +14 -0
  46. package/dist/providers/factory.js +57 -0
  47. package/dist/providers/openCode.d.ts +20 -1
  48. package/dist/providers/openCode.js +361 -11
  49. package/dist/providers/openCodeDatabase.d.ts +10 -0
  50. package/dist/providers/openCodeDatabase.js +69 -0
  51. package/dist/providers/types.d.ts +52 -2
  52. package/dist/providers/types.js +4 -0
  53. package/dist/quota.d.ts +4 -1
  54. package/dist/quota.js +3 -7
  55. package/dist/quotaHistory.d.ts +2 -1
  56. package/dist/quotaPoller.d.ts +13 -1
  57. package/dist/quotaPoller.js +65 -3
  58. package/dist/schemas/accountManager.d.ts +2 -2
  59. package/dist/schemas/accountManager.js +2 -4
  60. package/dist/schemas/accountStatus.d.ts +3 -0
  61. package/dist/schemas/accountStatus.js +1 -0
  62. package/dist/schemas/index.d.ts +1 -0
  63. package/dist/schemas/index.js +7 -2
  64. package/dist/schemas/observedSessionV1.d.ts +27 -26
  65. package/dist/schemas/observedSessionV1.js +6 -4
  66. package/dist/schemas/quota.d.ts +2 -2
  67. package/dist/schemas/quota.js +3 -6
  68. package/dist/schemas/quotaHistory.d.ts +2 -2
  69. package/dist/schemas/tokenEstimation.d.ts +1 -1
  70. package/dist/sessionMonitor.d.ts +15 -0
  71. package/dist/sessionMonitor.js +142 -0
  72. package/dist/sessionPreviews.d.ts +26 -1
  73. package/dist/sessionPreviews.js +319 -4
  74. package/dist/types/observedSessionV1.d.ts +6 -2
  75. package/dist/types/observedSessionV1.js +14 -8
  76. package/package.json +22 -1
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.onAccountsChanged = onAccountsChanged;
37
+ exports._notifyAccountsChanged = _notifyAccountsChanged;
38
+ const fs = __importStar(require("fs"));
39
+ const os = __importStar(require("os"));
40
+ const path = __importStar(require("path"));
41
+ const accountChangeSignal_1 = require("./accountChangeSignal");
42
+ const accountStatus_1 = require("./accountStatus");
43
+ const accountRegistry_1 = require("./accountRegistry");
44
+ const codexProfiles_1 = require("./codexProfiles");
45
+ const listeners = new Set();
46
+ let watchers = [];
47
+ let pollTimer;
48
+ let stopRawListener;
49
+ let lastFingerprint;
50
+ let debounceTimer;
51
+ let activePollIntervalMs = 30_000;
52
+ /**
53
+ * Observe login, logout, and active-account switches. Local Sidekick mutations
54
+ * signal immediately; filesystem watchers cover external CLIs; a low-frequency
55
+ * catch-up poll protects network mounts and watcher-hostile filesystems.
56
+ */
57
+ function onAccountsChanged(listener, options = {}) {
58
+ listeners.add(listener);
59
+ activePollIntervalMs = Math.max(1_000, options.pollIntervalMs ?? activePollIntervalMs);
60
+ ensureMonitoring();
61
+ if (options.emitCurrent)
62
+ emitIfChanged('poll', true, listener);
63
+ return {
64
+ dispose: () => {
65
+ listeners.delete(listener);
66
+ if (listeners.size === 0)
67
+ stopMonitoring();
68
+ },
69
+ };
70
+ }
71
+ function ensureMonitoring() {
72
+ if (stopRawListener)
73
+ return;
74
+ lastFingerprint = fingerprint((0, accountStatus_1.getActiveAccountStatus)());
75
+ stopRawListener = (0, accountChangeSignal_1._onRawAccountsChanged)((reason) => scheduleCheck(reason));
76
+ for (const target of watchTargets()) {
77
+ try {
78
+ const watcher = fs.watch(target, { persistent: false }, () => {
79
+ scheduleCheck('filesystem');
80
+ });
81
+ watcher.on('error', () => {
82
+ watchers = watchers.filter((candidate) => candidate !== watcher);
83
+ try {
84
+ watcher.close();
85
+ }
86
+ catch {
87
+ // The catch-up poll remains active.
88
+ }
89
+ });
90
+ watchers.push(watcher);
91
+ }
92
+ catch {
93
+ // Missing/unwatchable directories are covered by the catch-up poll.
94
+ }
95
+ }
96
+ pollTimer = setInterval(() => emitIfChanged('poll'), activePollIntervalMs);
97
+ pollTimer.unref?.();
98
+ }
99
+ function stopMonitoring() {
100
+ stopRawListener?.();
101
+ stopRawListener = undefined;
102
+ for (const watcher of watchers)
103
+ watcher.close();
104
+ watchers = [];
105
+ if (pollTimer)
106
+ clearInterval(pollTimer);
107
+ pollTimer = undefined;
108
+ if (debounceTimer)
109
+ clearTimeout(debounceTimer);
110
+ debounceTimer = undefined;
111
+ lastFingerprint = undefined;
112
+ }
113
+ function watchTargets() {
114
+ return Array.from(new Set([(0, accountRegistry_1.getAccountsDir)(), path.join(os.homedir(), '.claude'), (0, codexProfiles_1.getSystemCodexHome)()]));
115
+ }
116
+ function scheduleCheck(reason) {
117
+ if (debounceTimer)
118
+ clearTimeout(debounceTimer);
119
+ debounceTimer = setTimeout(() => {
120
+ debounceTimer = undefined;
121
+ // Never force: watch targets include ~/.claude and the Codex home, whose
122
+ // history files churn on every prompt. Only a real status change may wake
123
+ // subscribers, or dormant quota pollers would poll on unrelated writes.
124
+ emitIfChanged(reason);
125
+ }, 25);
126
+ debounceTimer.unref?.();
127
+ }
128
+ function emitIfChanged(reason, force = false, singleListener) {
129
+ const status = (0, accountStatus_1.getActiveAccountStatus)();
130
+ const nextFingerprint = fingerprint(status);
131
+ if (!force && nextFingerprint === lastFingerprint)
132
+ return;
133
+ lastFingerprint = nextFingerprint;
134
+ const event = { reason, status };
135
+ const targets = singleListener ? [singleListener] : [...listeners];
136
+ for (const listener of targets) {
137
+ try {
138
+ listener(event);
139
+ }
140
+ catch {
141
+ // A host callback cannot stop monitoring.
142
+ }
143
+ }
144
+ }
145
+ function fingerprint(status) {
146
+ return JSON.stringify(status);
147
+ }
148
+ /** Internal bridge for account mutations outside the registry module. */
149
+ function _notifyAccountsChanged() {
150
+ (0, accountChangeSignal_1._emitAccountsChanged)('local');
151
+ }
@@ -1,4 +1,6 @@
1
- export type AccountProviderId = 'claude-code' | 'codex';
1
+ import type { AccountProviderId } from './providerIds';
2
+ export { ACCOUNT_PROVIDER_IDS } from './providerIds';
3
+ export type { AccountProviderId } from './providerIds';
2
4
  export interface AccountIdentityMetadata {
3
5
  email?: string;
4
6
  workspaceId?: string;
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ACCOUNT_PROVIDER_IDS = void 0;
36
37
  exports.getAccountsDir = getAccountsDir;
37
38
  exports.readSavedAccountRegistry = readSavedAccountRegistry;
38
39
  exports.writeSavedAccountRegistry = writeSavedAccountRegistry;
@@ -46,6 +47,9 @@ const fs = __importStar(require("fs"));
46
47
  const path = __importStar(require("path"));
47
48
  const paths_1 = require("./paths");
48
49
  const atomic_1 = require("./writers/atomic");
50
+ const accountChangeSignal_1 = require("./accountChangeSignal");
51
+ var providerIds_1 = require("./providerIds");
52
+ Object.defineProperty(exports, "ACCOUNT_PROVIDER_IDS", { enumerable: true, get: function () { return providerIds_1.ACCOUNT_PROVIDER_IDS; } });
49
53
  function getAccountsDir() {
50
54
  return path.join((0, paths_1.getConfigDir)(), 'accounts');
51
55
  }
@@ -60,6 +64,7 @@ function ensureAccountsDir() {
60
64
  }
61
65
  function writeRegistryUnlocked(registry) {
62
66
  (0, atomic_1.atomicWriteJsonSync)(getRegistryPath(), normalizeRegistry(registry));
67
+ (0, accountChangeSignal_1._emitAccountsChanged)('local');
63
68
  }
64
69
  /**
65
70
  * Serializes registry read-modify-write cycles across processes. Without the
package/dist/browser.d.ts CHANGED
@@ -7,7 +7,8 @@
7
7
  *
8
8
  * For pricing hydration (LiteLLM catalog refresh), use `sidekick-shared/node`.
9
9
  */
10
- export { getModelContextWindowSize, DEFAULT_CONTEXT_WINDOW } from './modelContext';
10
+ export { getModelContextWindowSize, resolveModelContextWindow, DEFAULT_CONTEXT_WINDOW, } from './modelContext';
11
+ export type { ModelCatalogMatch, ModelContextWindowProvenance, ModelContextWindowSource, ResolvedModelContextWindow, } from './modelContext';
11
12
  export { normalizeProviderUsage, extractNormalizedUsage, calculateNormalizedUsageCost, } from './usageNormalization';
12
13
  export type { UsageProvider, UsageSemantics, UsageNormalizationProvenance, ProviderUsageInput, AnthropicUsageInput, OpenAIUsageInput, SidekickUsageInput, NormalizedUsage, PricingProvenance, NormalizedUsageCostInput, NormalizedUsageCost, } from './usageNormalization';
13
14
  export { estimateTextTokens, estimateSerializedTokens } from './tokenEstimation';
@@ -19,8 +20,12 @@ export type { FormatDurationMsOptions, FormatTokenCountOptions } from './formatt
19
20
  export { parseMcpToolName } from './parsers/mcpToolName';
20
21
  export type { McpToolNameParts } from './parsers/mcpToolName';
21
22
  export { buildSessionContextSnapshot, calculateSessionContextPressure, createSessionContextProjector, } from './context/sessionContext';
23
+ export { ACCOUNT_PROVIDER_IDS, MODEL_PROVIDER_IDS, QUOTA_PROVIDER_IDS, RUNTIME_QUOTA_PROVIDER_IDS, SESSION_PROVIDER_IDS, } from './providerIds';
24
+ export type { AccountProviderId, ModelProviderId, QuotaProviderId, RuntimeQuotaProviderId, SessionProviderId, } from './providerIds';
22
25
  export type { BuildSessionContextSnapshotOptions, SessionContextCapabilities, SessionContextLayerBreakdown, SessionContextPressure, SessionContextProjector, SessionContextSnapshot, SessionContextSource, SessionContextSourceType, } from './context/sessionContext';
23
26
  export { assistantTurnEventsFromSessionEvents, extractTurnSubagents, isAssistantTurnSubagentTool, reasoningSummary, segmentAssistantTurn, } from './turns/assistantTurn';
24
27
  export type { AssistantTurnEvent, AssistantTurnEventType, AssistantTurnProcess, AssistantTurnProcessStep, AssistantTurnProjection, AssistantTurnSubagent, AssistantTurnSubagentStatus, AssistantTurnTimelineItem, AssistantTurnToolRef, ExtractTurnSubagentsOptions, ReasoningSummary, SegmentAssistantTurnOptions, } from './turns/assistantTurn';
25
- export { parseModelId, getModelPricing, getModelInfo, calculateCost, calculateCostWithPricing, calculateCostWithProvenance, mergeCostSources, shortModelName, getModelDisplayInfo, compareModelIds, sortModelIds, formatCost, } from './modelInfo';
26
- export type { ModelPricing, CostTokenUsage, CostSource, CostProvenanceInput, CostWithProvenance, ModelProvider, ParsedModelId, ModelInfo, ModelDisplayInfo, } from './modelInfo';
28
+ export { parseModelId, getModelPricing, resolveModelPricing, getModelInfo, calculateCost, calculateCostWithPricing, calculateCostWithProvenance, mergeCostSources, shortModelName, getModelDisplayInfo, compareModelIds, sortModelIds, formatCost, } from './modelInfo';
29
+ export type { ModelPricing, CostTokenUsage, CostSource, CostProvenanceInput, CostWithProvenance, ModelProvider, ParsedModelId, ModelInfo, ModelDisplayInfo, ModelPricingMatch, ModelPricingProvenance, ModelPricingSource, ResolvedModelPricing, } from './modelInfo';
30
+ export { exportResolvedModelCatalog, importResolvedModelCatalog, registerModelAlias, resolveModelAlias, RESOLVED_MODEL_CATALOG_SCHEMA_VERSION, } from './modelCatalog';
31
+ export type { ImportResolvedModelCatalogResult, ResolvedModelCatalogEntry, ResolvedModelCatalogSnapshot, } from './modelCatalog';
package/dist/browser.js CHANGED
@@ -9,9 +9,10 @@
9
9
  * For pricing hydration (LiteLLM catalog refresh), use `sidekick-shared/node`.
10
10
  */
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- 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.segmentAssistantTurn = exports.reasoningSummary = exports.isAssistantTurnSubagentTool = exports.extractTurnSubagents = exports.assistantTurnEventsFromSessionEvents = exports.createSessionContextProjector = exports.calculateSessionContextPressure = exports.buildSessionContextSnapshot = exports.parseMcpToolName = exports.formatTokenCount = exports.formatDurationMs = exports.projectSessionTranscript = exports.estimateSerializedTokens = exports.estimateTextTokens = exports.calculateNormalizedUsageCost = exports.extractNormalizedUsage = exports.normalizeProviderUsage = exports.DEFAULT_CONTEXT_WINDOW = exports.getModelContextWindowSize = void 0;
12
+ exports.RESOLVED_MODEL_CATALOG_SCHEMA_VERSION = exports.resolveModelAlias = exports.registerModelAlias = exports.importResolvedModelCatalog = exports.exportResolvedModelCatalog = exports.formatCost = exports.sortModelIds = exports.compareModelIds = exports.getModelDisplayInfo = exports.shortModelName = exports.mergeCostSources = exports.calculateCostWithProvenance = exports.calculateCostWithPricing = exports.calculateCost = exports.getModelInfo = exports.resolveModelPricing = exports.getModelPricing = exports.parseModelId = exports.segmentAssistantTurn = exports.reasoningSummary = exports.isAssistantTurnSubagentTool = exports.extractTurnSubagents = exports.assistantTurnEventsFromSessionEvents = exports.SESSION_PROVIDER_IDS = exports.RUNTIME_QUOTA_PROVIDER_IDS = exports.QUOTA_PROVIDER_IDS = exports.MODEL_PROVIDER_IDS = exports.ACCOUNT_PROVIDER_IDS = exports.createSessionContextProjector = exports.calculateSessionContextPressure = exports.buildSessionContextSnapshot = exports.parseMcpToolName = exports.formatTokenCount = exports.formatDurationMs = exports.projectSessionTranscript = exports.estimateSerializedTokens = exports.estimateTextTokens = exports.calculateNormalizedUsageCost = exports.extractNormalizedUsage = exports.normalizeProviderUsage = exports.DEFAULT_CONTEXT_WINDOW = exports.resolveModelContextWindow = exports.getModelContextWindowSize = void 0;
13
13
  var modelContext_1 = require("./modelContext");
14
14
  Object.defineProperty(exports, "getModelContextWindowSize", { enumerable: true, get: function () { return modelContext_1.getModelContextWindowSize; } });
15
+ Object.defineProperty(exports, "resolveModelContextWindow", { enumerable: true, get: function () { return modelContext_1.resolveModelContextWindow; } });
15
16
  Object.defineProperty(exports, "DEFAULT_CONTEXT_WINDOW", { enumerable: true, get: function () { return modelContext_1.DEFAULT_CONTEXT_WINDOW; } });
16
17
  var usageNormalization_1 = require("./usageNormalization");
17
18
  Object.defineProperty(exports, "normalizeProviderUsage", { enumerable: true, get: function () { return usageNormalization_1.normalizeProviderUsage; } });
@@ -31,6 +32,12 @@ var sessionContext_1 = require("./context/sessionContext");
31
32
  Object.defineProperty(exports, "buildSessionContextSnapshot", { enumerable: true, get: function () { return sessionContext_1.buildSessionContextSnapshot; } });
32
33
  Object.defineProperty(exports, "calculateSessionContextPressure", { enumerable: true, get: function () { return sessionContext_1.calculateSessionContextPressure; } });
33
34
  Object.defineProperty(exports, "createSessionContextProjector", { enumerable: true, get: function () { return sessionContext_1.createSessionContextProjector; } });
35
+ var providerIds_1 = require("./providerIds");
36
+ Object.defineProperty(exports, "ACCOUNT_PROVIDER_IDS", { enumerable: true, get: function () { return providerIds_1.ACCOUNT_PROVIDER_IDS; } });
37
+ Object.defineProperty(exports, "MODEL_PROVIDER_IDS", { enumerable: true, get: function () { return providerIds_1.MODEL_PROVIDER_IDS; } });
38
+ Object.defineProperty(exports, "QUOTA_PROVIDER_IDS", { enumerable: true, get: function () { return providerIds_1.QUOTA_PROVIDER_IDS; } });
39
+ Object.defineProperty(exports, "RUNTIME_QUOTA_PROVIDER_IDS", { enumerable: true, get: function () { return providerIds_1.RUNTIME_QUOTA_PROVIDER_IDS; } });
40
+ Object.defineProperty(exports, "SESSION_PROVIDER_IDS", { enumerable: true, get: function () { return providerIds_1.SESSION_PROVIDER_IDS; } });
34
41
  var assistantTurn_1 = require("./turns/assistantTurn");
35
42
  Object.defineProperty(exports, "assistantTurnEventsFromSessionEvents", { enumerable: true, get: function () { return assistantTurn_1.assistantTurnEventsFromSessionEvents; } });
36
43
  Object.defineProperty(exports, "extractTurnSubagents", { enumerable: true, get: function () { return assistantTurn_1.extractTurnSubagents; } });
@@ -40,6 +47,7 @@ Object.defineProperty(exports, "segmentAssistantTurn", { enumerable: true, get:
40
47
  var modelInfo_1 = require("./modelInfo");
41
48
  Object.defineProperty(exports, "parseModelId", { enumerable: true, get: function () { return modelInfo_1.parseModelId; } });
42
49
  Object.defineProperty(exports, "getModelPricing", { enumerable: true, get: function () { return modelInfo_1.getModelPricing; } });
50
+ Object.defineProperty(exports, "resolveModelPricing", { enumerable: true, get: function () { return modelInfo_1.resolveModelPricing; } });
43
51
  Object.defineProperty(exports, "getModelInfo", { enumerable: true, get: function () { return modelInfo_1.getModelInfo; } });
44
52
  Object.defineProperty(exports, "calculateCost", { enumerable: true, get: function () { return modelInfo_1.calculateCost; } });
45
53
  Object.defineProperty(exports, "calculateCostWithPricing", { enumerable: true, get: function () { return modelInfo_1.calculateCostWithPricing; } });
@@ -50,3 +58,9 @@ Object.defineProperty(exports, "getModelDisplayInfo", { enumerable: true, get: f
50
58
  Object.defineProperty(exports, "compareModelIds", { enumerable: true, get: function () { return modelInfo_1.compareModelIds; } });
51
59
  Object.defineProperty(exports, "sortModelIds", { enumerable: true, get: function () { return modelInfo_1.sortModelIds; } });
52
60
  Object.defineProperty(exports, "formatCost", { enumerable: true, get: function () { return modelInfo_1.formatCost; } });
61
+ var modelCatalog_1 = require("./modelCatalog");
62
+ Object.defineProperty(exports, "exportResolvedModelCatalog", { enumerable: true, get: function () { return modelCatalog_1.exportResolvedModelCatalog; } });
63
+ Object.defineProperty(exports, "importResolvedModelCatalog", { enumerable: true, get: function () { return modelCatalog_1.importResolvedModelCatalog; } });
64
+ Object.defineProperty(exports, "registerModelAlias", { enumerable: true, get: function () { return modelCatalog_1.registerModelAlias; } });
65
+ Object.defineProperty(exports, "resolveModelAlias", { enumerable: true, get: function () { return modelCatalog_1.resolveModelAlias; } });
66
+ Object.defineProperty(exports, "RESOLVED_MODEL_CATALOG_SCHEMA_VERSION", { enumerable: true, get: function () { return modelCatalog_1.RESOLVED_MODEL_CATALOG_SCHEMA_VERSION; } });
@@ -6,6 +6,7 @@ import type { SavedAccountProfile } from './accountRegistry';
6
6
  import type { Disposable } from './quotaPoller';
7
7
  import type { QuotaState } from './quota';
8
8
  import type { ProviderQuotaState } from './providerQuota';
9
+ import { type AccountsChangedEvent } from './accountChanges';
9
10
  type CodexAccountReader = () => SavedAccountProfile | null;
10
11
  type SnapshotReader = (providerId: 'codex', accountId: string) => QuotaState | null;
11
12
  type SnapshotWriter = (providerId: 'codex', accountId: string, quota: QuotaState) => void;
@@ -27,6 +28,7 @@ export interface CodexQuotaWatcherOptions {
27
28
  workspaceId?: string;
28
29
  /** Override the history append function (used by tests). Default: `appendQuotaHistorySample`. */
29
30
  appendHistorySample?: HistoryAppender;
31
+ subscribeAccountsChanged?: (listener: (event: AccountsChangedEvent) => void) => Disposable;
30
32
  }
31
33
  /**
32
34
  * Watches the active Codex rollout for quota snapshots and falls back to the
@@ -46,6 +48,7 @@ export declare class CodexQuotaWatcher implements Disposable {
46
48
  private readonly now;
47
49
  private readonly workspaceId;
48
50
  private readonly appendHistorySample;
51
+ private readonly subscribeAccountsChanged;
49
52
  private readonly listeners;
50
53
  private discoveryTimer;
51
54
  private provider;
@@ -56,6 +59,7 @@ export declare class CodexQuotaWatcher implements Disposable {
56
59
  private lastLocalScanAt;
57
60
  private lastLocalScanState;
58
61
  private running;
62
+ private accountSubscription;
59
63
  constructor(workspacePath: string, options?: CodexQuotaWatcherOptions);
60
64
  start(): void;
61
65
  stop(): void;
@@ -63,12 +67,14 @@ export declare class CodexQuotaWatcher implements Disposable {
63
67
  onUpdate(cb: (state: ProviderQuotaState<'codex'>) => void): Disposable;
64
68
  onQuotaUpdate(cb: (state: ProviderQuotaState<'codex'>) => void): Disposable;
65
69
  refresh(): void;
70
+ private handleAccountsChanged;
66
71
  private refreshActiveSession;
67
72
  private attachToSession;
68
73
  private ingestSessionUpdate;
69
74
  private emitCachedOrUnavailable;
70
75
  private emitState;
71
76
  private getProvider;
77
+ private safeGetActiveAccount;
72
78
  private teardownActiveSession;
73
79
  }
74
80
  export {};
@@ -40,6 +40,7 @@ const codexQuota_1 = require("./codexQuota");
40
40
  const quotaSnapshots_1 = require("./quotaSnapshots");
41
41
  const quotaHistory_1 = require("./quotaHistory");
42
42
  const codex_1 = require("./providers/codex");
43
+ const accountChanges_1 = require("./accountChanges");
43
44
  const DEFAULT_DISCOVERY_POLL_INTERVAL_MS = 30_000;
44
45
  const DEFAULT_LOCAL_SCAN_CACHE_MS = 5 * 60_000;
45
46
  function accountEmail(account) {
@@ -86,6 +87,7 @@ class CodexQuotaWatcher {
86
87
  now;
87
88
  workspaceId;
88
89
  appendHistorySample;
90
+ subscribeAccountsChanged;
89
91
  listeners = [];
90
92
  discoveryTimer;
91
93
  provider = null;
@@ -96,6 +98,7 @@ class CodexQuotaWatcher {
96
98
  lastLocalScanAt = Number.NEGATIVE_INFINITY;
97
99
  lastLocalScanState = null;
98
100
  running = false;
101
+ accountSubscription = null;
99
102
  constructor(workspacePath, options = {}) {
100
103
  this.workspacePath = workspacePath;
101
104
  this.discoveryPollIntervalMs =
@@ -119,15 +122,14 @@ class CodexQuotaWatcher {
119
122
  this.now = options.now ?? Date.now;
120
123
  this.workspaceId = options.workspaceId;
121
124
  this.appendHistorySample = options.appendHistorySample ?? quotaHistory_1.appendQuotaHistorySample;
125
+ this.subscribeAccountsChanged = options.subscribeAccountsChanged ?? accountChanges_1.onAccountsChanged;
122
126
  }
123
127
  start() {
124
128
  if (this.running)
125
129
  return;
126
130
  this.running = true;
127
- this.refreshActiveSession();
128
- this.discoveryTimer = setInterval(() => {
129
- this.refreshActiveSession();
130
- }, this.discoveryPollIntervalMs);
131
+ this.accountSubscription ??= this.subscribeAccountsChanged(() => this.handleAccountsChanged());
132
+ this.handleAccountsChanged();
131
133
  }
132
134
  stop() {
133
135
  if (!this.running)
@@ -137,6 +139,8 @@ class CodexQuotaWatcher {
137
139
  clearInterval(this.discoveryTimer);
138
140
  this.discoveryTimer = undefined;
139
141
  }
142
+ this.accountSubscription?.dispose();
143
+ this.accountSubscription = null;
140
144
  this.teardownActiveSession();
141
145
  }
142
146
  dispose() {
@@ -157,21 +161,46 @@ class CodexQuotaWatcher {
157
161
  return this.onUpdate(cb);
158
162
  }
159
163
  refresh() {
160
- this.refreshActiveSession();
164
+ this.handleAccountsChanged();
161
165
  }
162
- refreshActiveSession() {
166
+ handleAccountsChanged() {
167
+ if (!this.running)
168
+ return;
169
+ const account = this.safeGetActiveAccount();
170
+ if (!account) {
171
+ if (this.discoveryTimer) {
172
+ clearInterval(this.discoveryTimer);
173
+ this.discoveryTimer = undefined;
174
+ }
175
+ this.teardownActiveSession();
176
+ this.emitState(makeUnavailableState(null, 'No Codex account configured'));
177
+ return;
178
+ }
179
+ if (!this.discoveryTimer) {
180
+ this.discoveryTimer = setInterval(() => {
181
+ this.refreshActiveSession();
182
+ }, this.discoveryPollIntervalMs);
183
+ this.discoveryTimer.unref?.();
184
+ }
185
+ this.refreshActiveSession(account);
186
+ }
187
+ refreshActiveSession(account = this.safeGetActiveAccount()) {
188
+ if (!account) {
189
+ this.handleAccountsChanged();
190
+ return;
191
+ }
163
192
  const provider = this.getProvider();
164
193
  const nextSessionPath = provider.findActiveSession(this.workspacePath);
165
194
  if (!nextSessionPath) {
166
195
  this.teardownActiveSession();
167
- this.emitCachedOrUnavailable();
196
+ this.emitCachedOrUnavailable(account);
168
197
  return;
169
198
  }
170
199
  if (nextSessionPath !== this.sessionPath || this.reader == null) {
171
200
  this.attachToSession(nextSessionPath);
172
201
  return;
173
202
  }
174
- this.ingestSessionUpdate('readNew');
203
+ this.ingestSessionUpdate('readNew', account);
175
204
  }
176
205
  attachToSession(nextSessionPath) {
177
206
  this.teardownActiveSession();
@@ -189,14 +218,30 @@ class CodexQuotaWatcher {
189
218
  }
190
219
  this.refreshActiveSession();
191
220
  });
221
+ if (typeof this.fileWatcher.on === 'function') {
222
+ this.fileWatcher.on('error', () => {
223
+ try {
224
+ this.fileWatcher?.close();
225
+ }
226
+ catch {
227
+ // Discovery polling remains active.
228
+ }
229
+ this.fileWatcher = null;
230
+ });
231
+ }
192
232
  }
193
233
  catch {
194
234
  this.emitCachedOrUnavailable();
195
235
  }
196
236
  }
197
- ingestSessionUpdate(mode) {
237
+ ingestSessionUpdate(mode, knownAccount) {
238
+ const account = knownAccount ?? this.safeGetActiveAccount();
239
+ if (!account) {
240
+ this.handleAccountsChanged();
241
+ return;
242
+ }
198
243
  if (!this.provider || !this.reader) {
199
- this.emitCachedOrUnavailable();
244
+ this.emitCachedOrUnavailable(account);
200
245
  return;
201
246
  }
202
247
  if (!this.reader.exists()) {
@@ -211,48 +256,45 @@ class CodexQuotaWatcher {
211
256
  }
212
257
  const liveQuota = (0, codexQuota_1.quotaFromCodexRateLimits)(this.provider.getLastRateLimits(), 'session');
213
258
  if (!liveQuota) {
214
- this.emitCachedOrUnavailable();
259
+ this.emitCachedOrUnavailable(account);
215
260
  return;
216
261
  }
217
- const account = this.getActiveAccount();
218
- const cached = account ? this.readSnapshot('codex', account.id) : null;
262
+ const cached = this.readSnapshot('codex', account.id);
219
263
  const liveQuotaWithResetCredits = {
220
264
  ...liveQuota,
221
265
  resetCredits: liveQuota.resetCredits ?? cached?.resetCredits,
222
266
  };
223
- if (account) {
224
- this.writeSnapshot('codex', account.id, liveQuotaWithResetCredits);
225
- if (this.workspaceId) {
226
- const sample = {
227
- timestamp: liveQuotaWithResetCredits.capturedAt ?? new Date().toISOString(),
228
- runtimeProvider: 'codex',
229
- providerId: account.id,
230
- workspaceId: this.workspaceId,
231
- fiveHour: {
232
- utilization: liveQuotaWithResetCredits.fiveHour.utilization,
233
- resetsAt: liveQuotaWithResetCredits.fiveHour.resetsAt,
234
- },
235
- sevenDay: {
236
- utilization: liveQuotaWithResetCredits.sevenDay.utilization,
237
- resetsAt: liveQuotaWithResetCredits.sevenDay.resetsAt,
238
- },
239
- available: liveQuotaWithResetCredits.available,
240
- error: liveQuotaWithResetCredits.error,
241
- source: liveQuotaWithResetCredits.source,
242
- stale: liveQuotaWithResetCredits.stale,
243
- };
244
- try {
245
- const result = this.appendHistorySample(sample);
246
- if (result && typeof result.catch === 'function') {
247
- result.catch(() => {
248
- // History append must never break the live emission path.
249
- });
250
- }
251
- }
252
- catch {
253
- // Synchronous errors swallowed for the same reason.
267
+ this.writeSnapshot('codex', account.id, liveQuotaWithResetCredits);
268
+ if (this.workspaceId) {
269
+ const sample = {
270
+ timestamp: liveQuotaWithResetCredits.capturedAt ?? new Date().toISOString(),
271
+ runtimeProvider: 'codex',
272
+ providerId: account.id,
273
+ workspaceId: this.workspaceId,
274
+ fiveHour: {
275
+ utilization: liveQuotaWithResetCredits.fiveHour.utilization,
276
+ resetsAt: liveQuotaWithResetCredits.fiveHour.resetsAt,
277
+ },
278
+ sevenDay: {
279
+ utilization: liveQuotaWithResetCredits.sevenDay.utilization,
280
+ resetsAt: liveQuotaWithResetCredits.sevenDay.resetsAt,
281
+ },
282
+ available: liveQuotaWithResetCredits.available,
283
+ error: liveQuotaWithResetCredits.error,
284
+ source: liveQuotaWithResetCredits.source,
285
+ stale: liveQuotaWithResetCredits.stale,
286
+ };
287
+ try {
288
+ const result = this.appendHistorySample(sample);
289
+ if (result && typeof result.catch === 'function') {
290
+ result.catch(() => {
291
+ // History append must never break the live emission path.
292
+ });
254
293
  }
255
294
  }
295
+ catch {
296
+ // Synchronous errors swallowed for the same reason.
297
+ }
256
298
  }
257
299
  this.emitState(enrichQuotaState({
258
300
  ...liveQuotaWithResetCredits,
@@ -260,8 +302,12 @@ class CodexQuotaWatcher {
260
302
  providerId: 'codex',
261
303
  }, account));
262
304
  }
263
- emitCachedOrUnavailable() {
264
- const account = this.getActiveAccount();
305
+ emitCachedOrUnavailable(knownAccount) {
306
+ const account = knownAccount ?? this.safeGetActiveAccount();
307
+ if (!account) {
308
+ this.emitState(makeUnavailableState(null, 'No Codex account configured'));
309
+ return;
310
+ }
265
311
  const scanNow = this.now();
266
312
  if (scanNow - this.lastLocalScanAt < this.localScanCacheMs) {
267
313
  if (this.lastLocalScanState) {
@@ -296,7 +342,7 @@ class CodexQuotaWatcher {
296
342
  localProvider?.dispose();
297
343
  }
298
344
  }
299
- const cached = account ? this.readSnapshot('codex', account.id) : null;
345
+ const cached = this.readSnapshot('codex', account.id);
300
346
  if (cached) {
301
347
  this.emitState(enrichQuotaState({
302
348
  ...cached,
@@ -331,6 +377,14 @@ class CodexQuotaWatcher {
331
377
  }
332
378
  return this.provider;
333
379
  }
380
+ safeGetActiveAccount() {
381
+ try {
382
+ return this.getActiveAccount();
383
+ }
384
+ catch {
385
+ return null;
386
+ }
387
+ }
334
388
  teardownActiveSession() {
335
389
  if (this.fileWatcher) {
336
390
  this.fileWatcher.close();