sidekick-shared 0.21.0 → 0.21.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/README.md CHANGED
@@ -40,13 +40,13 @@ npm install sidekick-shared
40
40
  | **Assistant Turns** | Browser-safe timeline/process/answer projection for provider-normalized assistant turns (`segmentAssistantTurn()`, `assistantTurnEventsFromSessionEvents()`), including interleaved reasoning, compact tool groups, and Claude `Task` subagent refs without prompt leakage |
41
41
  | **Report** | Self-contained HTML session report generation |
42
42
  | **Credentials** | Claude Max OAuth credential reading from `~/.claude/.credentials.json` |
43
- | **Quota** | Claude Max subscription quota fetching (5-hour and 7-day windows) and Codex rate-limit extraction from event streams |
43
+ | **Quota** | Claude Max subscription quota fetching (5-hour and 7-day windows), Codex rate-limit extraction from event streams, and **estimated** z.ai Coding Plan quota derivation from observed OpenCode traffic (`accumulateZaiUsage()`, `inferZaiQuotaState()`, `parseZaiQuotaError()`, `resolveZaiTier()`, `ZAI_TIER_BUDGETS`) — z.ai exposes no usage API, so this is a best-effort estimate, not authoritative |
44
44
  | **Provider Status** | API health checking via status.claude.com and status.openai.com (indicator, components, incidents) |
45
45
  | **Schemas** | Zod schemas for runtime validation of data crossing process/IPC boundaries — JSONL session events (`sessionEventSchema`, `messageUsageSchema`, `sessionMessageSchema`), assistant turns, quota, account status, and quota history — plus `extractSessionEvents()` to unwrap `progress`-wrapped events. Also published fs-free via the [`sidekick-shared/schemas`](#supported-import-paths) subpath |
46
46
  | **Extractors** | Pure functions for single-event processing: `extractTokenUsage()`, `extractToolCall()` (top-level `tool_use`), `extractToolCalls()` (assistant content blocks), plus Node-only session asset extraction (`gatherAssetsForCwd()`, `readClaudeAssets()`, `readCodexAssets()`) for URLs, file paths, commands, and plans |
47
47
  | **Model Info & Pricing** | Model family parsing (Anthropic / OpenAI / Google, including legacy `claude-3-opus-…` and `claude-3-5-sonnet-…` IDs), context-window lookup (including Fable 5 / Opus 4.8 / Opus 4.7 / Sonnet 4.7 1M and GPT-5.x variants), pricing tables with optional LiteLLM hydration, null-aware cost (`calculateCost()`), provenance-preserving cost (`calculateCostWithProvenance()`, `mergeCostSources()`), and display helpers (`shortModelName()`, `getModelDisplayInfo()`, `compareModelIds()`, `sortModelIds()`, `formatCost()`) |
48
48
  | **Quota Polling** | `QuotaPoller` class with exponential backoff, active/idle intervals, and cached fallback |
49
- | **Multi-Provider Quota** | `MultiProviderQuotaService` orchestrates Claude polling + peak-hours + account labels + Codex quota updates behind one typed `{ claude?, codex? }` event stream. `CodexQuotaWatcher` watches the active Codex rollout for live rate limits with snapshot fallback |
49
+ | **Multi-Provider Quota** | `MultiProviderQuotaService` orchestrates Claude polling + peak-hours + account labels + Codex/z.ai quota updates behind one typed event stream. `CodexQuotaWatcher` watches the active Codex rollout for live rate limits with snapshot fallback; `ZaiQuotaWatcher` derives an estimated z.ai Coding Plan quota from observed OpenCode turns (event-driven, snapshot + history) |
50
50
  | **Accounts** | Multi-provider account registry (v2) with per-provider active account, save/switch/remove, v1 migration, `ensureDefaultAccounts()` for first-run bootstrap of the active system Claude/Codex credentials as a "Default" saved account, and `getActiveAccountStatus()` for a single-pass active-account read across providers |
51
51
  | **Account Management 2.0** | Provider-neutral acquisition + switching: `beginAccountLogin()` / `getAccountLoginStatus()` / `finalizeAccountLogin()` / `spawnAccountLogin()` drive a TTY-less, profile-isolated login that doesn't disturb the active account until finalization; `listAllAccounts()` and `switchAccount()` expose a shared switcher across Claude and Codex. Claude accounts get canonical profile homes with account-scoped macOS keychain suffixes and startup migration from legacy flat backups |
52
52
  | **Terminal sync & auto-switch** | Opt-in terminal profile pointers, shell-hook/launcher helpers, and a default-off `AutoSwitchController` (`decideAutoSwitch()`) that moves to a healthier saved account when quota crosses a configured threshold |
package/dist/index.d.ts CHANGED
@@ -28,7 +28,7 @@ export type { ReadPlansOptions, PlanAnalytics } from './readers/plans';
28
28
  export type { ProviderId, SessionProvider, SessionProviderBase, SessionFileStats, SessionFileInfo, SearchHit, ProjectFolderInfo, SessionReader, ProviderRuntimeStatus } from './providers/types';
29
29
  export { detectProvider, getAllDetectedProviders } from './providers/detect';
30
30
  export { ClaudeCodeProvider } from './providers/claudeCode';
31
- export { OpenCodeProvider } from './providers/openCode';
31
+ export { OpenCodeProvider, getOpenCodeDataDir } from './providers/openCode';
32
32
  export { CodexProvider } from './providers/codex';
33
33
  export { JsonlParser, TRUNCATION_PATTERNS } from './parsers/jsonl';
34
34
  export type { RawSessionEvent, JsonlParserCallbacks, JsonlParserOptions } from './parsers/jsonl';
@@ -121,9 +121,14 @@ export { fetchCodexQuotaFromApi, quotaFromCodexRateLimits, readLatestCodexQuotaF
121
121
  export type { CodexQuotaApiOptions, CodexQuotaCreditsSnapshot, CodexQuotaResolveOptions, CodexQuotaResolveSource, } from './codexQuota';
122
122
  export { CodexQuotaWatcher } from './codexQuotaWatcher';
123
123
  export type { CodexQuotaWatcherOptions } from './codexQuotaWatcher';
124
+ export { accumulateZaiUsage, extractNextFlushTime, inferZaiQuotaState, isZaiProviderId, makeUnavailableZaiQuotaState, parseZaiQuotaError, resolveZaiTier, rowsToZaiTurnsAndErrors, turnTokenWeight, ZAI_PROMPT_INVOCATIONS, ZAI_PROVIDER_IDS, ZAI_TIER_BUDGETS, } from './zaiQuota';
125
+ export type { ZaiAccumulatedUsage, ZaiAssistantTurn, ZaiOpenCodeRow, ZaiProviderId, ZaiQuotaError, ZaiQuotaErrorKind, ZaiTier, InferZaiQuotaStateOptions, } from './zaiQuota';
126
+ export { ZaiQuotaWatcher } from './zaiQuotaWatcher';
127
+ export type { ZaiQuotaWatcherOptions } from './zaiQuotaWatcher';
124
128
  export { MultiProviderQuotaService } from './multiProviderQuotaService';
125
129
  export type { MultiProviderQuotaServiceOptions } from './multiProviderQuotaService';
126
130
  export type { ProviderQuotaMap, ProviderQuotaState, RuntimeQuotaProvider } from './providerQuota';
131
+ export type { QuotaSnapshotProviderId } from './quotaSnapshots';
127
132
  export { getModelContextWindowSize, DEFAULT_CONTEXT_WINDOW } from './modelContext';
128
133
  export { parseModelId, getModelPricing, getModelInfo, calculateCost, calculateCostWithPricing, calculateCostWithProvenance, mergeCostSources, shortModelName, getModelDisplayInfo, compareModelIds, sortModelIds, formatCost, } from './modelInfo';
129
134
  export type { ModelPricing, CostTokenUsage, CostSource, CostProvenanceInput, CostWithProvenance, ModelInfo, ModelProvider, ParsedModelId, ModelDisplayInfo, } from './modelInfo';
@@ -138,7 +143,7 @@ export { readCodexAssets, codexSessions } from './extractors/sources/codexAssets
138
143
  export { gatherAssetsForCwd } from './extractors/gatherAssets';
139
144
  export type { GatherAssetsOptions, GatherAssetsResult } from './extractors/gatherAssets';
140
145
  export { messageUsageSchema, sessionMessageSchema, sessionEventSchema, permissionModeSchema, extractSessionEvents, } from './schemas/sessionEvent';
141
- export { quotaWindowSchema, quotaStateSchema, quotaFailureKindSchema, quotaProviderIdSchema, quotaSourceSchema, peakHoursStateSchema, quotaFailureDescriptorSchema, runtimeQuotaProviderSchema, providerQuotaStateSchema, claudeProviderQuotaStateSchema, codexProviderQuotaStateSchema, providerQuotaMapSchema, } from './schemas/quota';
146
+ export { quotaWindowSchema, quotaStateSchema, quotaFailureKindSchema, quotaProviderIdSchema, quotaSourceSchema, peakHoursStateSchema, quotaFailureDescriptorSchema, runtimeQuotaProviderSchema, providerQuotaStateSchema, claudeProviderQuotaStateSchema, codexProviderQuotaStateSchema, zaiProviderQuotaStateSchema, providerQuotaMapSchema, } from './schemas/quota';
142
147
  export { quotaHistoryRuntimeProviderSchema, quotaHistorySampleSchema, quotaHistoryDailyBucketSchema, } from './schemas/quotaHistory';
143
148
  export { activeProviderAccountStatusSchema, activeAccountStatusSchema, } from './schemas/accountStatus';
144
149
  export { accountProviderIdSchema, accountManagerResultSchema, beginAccountLoginResultSchema, accountLoginStatusSchema, accountEntrySchema, savedAccountProfileSchema, listAllAccountsResultSchema, } from './schemas/accountManager';
package/dist/index.js CHANGED
@@ -3,12 +3,12 @@
3
3
  * Public API for sidekick-shared.
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.findActiveClaudeSession = exports.discoverSessionDirectory = exports.getClaudeSessionDirectory = exports.encodeClaudeWorkspacePath = exports.detectSessionActivity = exports.extractTaskInfo = exports.scanSubagentDir = exports.normalizeCodexToolInput = exports.normalizeCodexToolName = exports.extractPatchFilePaths = exports.CodexRolloutParser = exports.parseDbPartData = exports.parseDbMessageData = exports.convertOpenCodeMessage = exports.detectPlanModeFromText = exports.normalizeToolInput = exports.normalizeToolName = exports.TRUNCATION_PATTERNS = exports.JsonlParser = exports.CodexProvider = exports.OpenCodeProvider = exports.ClaudeCodeProvider = exports.getAllDetectedProviders = exports.detectProvider = exports.readClaudeCodePlanFiles = exports.getPlanAnalytics = exports.writePlans = exports.getLatestPlan = exports.readPlans = exports.readLatestHandoff = exports.readHistory = exports.readNotes = exports.readDecisions = exports.readTasks = exports.getProjectSlugRaw = exports.getProjectSlug = exports.encodeWorkspacePath = exports.getGlobalDataPath = exports.getProjectDataPath = exports.getConfigDir = exports.MAX_PLANS_PER_PROJECT = exports.PLAN_SCHEMA_VERSION = exports.createEmptyTokenTotals = exports.HISTORICAL_DATA_SCHEMA_VERSION = exports.STALENESS_THRESHOLDS = exports.IMPORTANCE_DECAY_FACTORS = exports.KNOWLEDGE_NOTE_SCHEMA_VERSION = exports.DECISION_LOG_SCHEMA_VERSION = exports.normalizeTaskStatus = exports.TASK_PERSISTENCE_SCHEMA_VERSION = void 0;
7
- 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 = exports.createSessionContextProjector = exports.calculateSessionContextPressure = exports.buildSessionContextSnapshot = exports.composeContext = exports.FilterEngine = exports.searchSessions = exports.CodexDatabase = exports.OpenCodeDatabase = exports.discoverDebugLogs = exports.collapseDuplicates = exports.filterByLevel = exports.parseDebugLog = exports.scanSubagentTraces = exports.findAllSessionsWithWorktrees = exports.discoverWorktreeSiblings = exports.resolveWorktreeMainRepo = exports.getAllClaudeProjectFolders = exports.decodeEncodedPath = exports.getMostRecentlyActiveSessionDir = exports.findSubdirectorySessionDirs = exports.findSessionsInDirectory = exports.findAllClaudeSessions = void 0;
8
- 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.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.PatternExtractor = exports.HeatmapTracker = exports.FrequencyTracker = exports.getSnapshotPath = exports.isSnapshotValid = exports.deleteSnapshot = exports.loadSnapshot = exports.saveSnapshot = exports.parseTodoDependencies = exports.EventAggregator = exports.getRandomPhrase = exports.PHRASE_CATEGORIES = exports.ALL_PHRASES = exports.HIGHLIGHT_CSS = void 0;
9
- exports.getModelPricing = exports.parseModelId = exports.DEFAULT_CONTEXT_WINDOW = exports.getModelContextWindowSize = exports.MultiProviderQuotaService = exports.CodexQuotaWatcher = exports.resolveCodexQuotaFromLocalSources = exports.resolveCodexQuota = exports.readLatestCodexQuotaFromRollouts = exports.quotaFromCodexRateLimits = exports.fetchCodexQuotaFromApi = exports.getWorkspaceIdFromPath = exports.pruneQuotaHistory = exports.readQuotaHistoryDailyBuckets = exports.readQuotaHistoryRange = exports.appendQuotaHistorySample = exports.writeQuotaSnapshot = exports.readQuotaSnapshot = exports.QuotaPoller = exports.describeQuotaFailure = exports.fetchQuota = 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.getActiveCodexAccount = exports.listCodexAccounts = exports.getSystemCodexHome = exports.getCodexMonitoringHomes = exports.getCodexProfileHome = exports.getCodexProfilesDir = exports.readClaudeProfileIdentity = exports.isClaudeProfileAuthenticated = exports.claudeKeychainService = exports.claudeKeychainSuffix = exports.getClaudeProfileHome = exports.getClaudeProfilesDir = exports.getActiveAccountStatus = exports.removeSavedAccountProfile = void 0;
10
- exports.accountLoginStatusSchema = exports.beginAccountLoginResultSchema = exports.accountManagerResultSchema = exports.accountProviderIdSchema = exports.activeAccountStatusSchema = exports.activeProviderAccountStatusSchema = exports.quotaHistoryDailyBucketSchema = exports.quotaHistorySampleSchema = exports.quotaHistoryRuntimeProviderSchema = exports.providerQuotaMapSchema = exports.codexProviderQuotaStateSchema = exports.claudeProviderQuotaStateSchema = exports.providerQuotaStateSchema = exports.runtimeQuotaProviderSchema = exports.quotaFailureDescriptorSchema = exports.peakHoursStateSchema = exports.quotaSourceSchema = exports.quotaProviderIdSchema = exports.quotaFailureKindSchema = exports.quotaStateSchema = exports.quotaWindowSchema = exports.extractSessionEvents = exports.permissionModeSchema = exports.sessionEventSchema = exports.sessionMessageSchema = exports.messageUsageSchema = exports.gatherAssetsForCwd = exports.codexSessions = exports.readCodexAssets = exports.claudeSessions = exports.readClaudeAssets = exports.extractCommands = exports.extractFilePaths = exports.extractUrls = exports.extractToolCalls = exports.extractToolCall = exports.extractTokenUsage = exports.LITELLM_CATALOG_URL = exports.normalizeLiteLlmCatalog = exports.hydratePricingCatalog = exports.formatCost = exports.sortModelIds = exports.compareModelIds = exports.getModelDisplayInfo = exports.shortModelName = exports.mergeCostSources = exports.calculateCostWithProvenance = exports.calculateCostWithPricing = exports.calculateCost = exports.getModelInfo = void 0;
11
- exports.scopePeakHoursToSessionProvider = exports.isClaudeCodeSessionProvider = exports.fetchPeakHoursStatus = exports.createPeakHoursNotApplicableState = 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 = void 0;
6
+ exports.discoverSessionDirectory = exports.getClaudeSessionDirectory = exports.encodeClaudeWorkspacePath = exports.detectSessionActivity = exports.extractTaskInfo = exports.scanSubagentDir = exports.normalizeCodexToolInput = exports.normalizeCodexToolName = exports.extractPatchFilePaths = exports.CodexRolloutParser = exports.parseDbPartData = exports.parseDbMessageData = exports.convertOpenCodeMessage = exports.detectPlanModeFromText = exports.normalizeToolInput = exports.normalizeToolName = exports.TRUNCATION_PATTERNS = exports.JsonlParser = exports.CodexProvider = exports.getOpenCodeDataDir = exports.OpenCodeProvider = exports.ClaudeCodeProvider = exports.getAllDetectedProviders = exports.detectProvider = exports.readClaudeCodePlanFiles = exports.getPlanAnalytics = exports.writePlans = exports.getLatestPlan = exports.readPlans = exports.readLatestHandoff = exports.readHistory = exports.readNotes = exports.readDecisions = exports.readTasks = exports.getProjectSlugRaw = exports.getProjectSlug = exports.encodeWorkspacePath = exports.getGlobalDataPath = exports.getProjectDataPath = exports.getConfigDir = exports.MAX_PLANS_PER_PROJECT = exports.PLAN_SCHEMA_VERSION = exports.createEmptyTokenTotals = exports.HISTORICAL_DATA_SCHEMA_VERSION = exports.STALENESS_THRESHOLDS = exports.IMPORTANCE_DECAY_FACTORS = exports.KNOWLEDGE_NOTE_SCHEMA_VERSION = exports.DECISION_LOG_SCHEMA_VERSION = exports.normalizeTaskStatus = exports.TASK_PERSISTENCE_SCHEMA_VERSION = void 0;
7
+ 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 = exports.createSessionContextProjector = exports.calculateSessionContextPressure = exports.buildSessionContextSnapshot = exports.composeContext = exports.FilterEngine = exports.searchSessions = exports.CodexDatabase = exports.OpenCodeDatabase = exports.discoverDebugLogs = exports.collapseDuplicates = exports.filterByLevel = exports.parseDebugLog = exports.scanSubagentTraces = exports.findAllSessionsWithWorktrees = exports.discoverWorktreeSiblings = exports.resolveWorktreeMainRepo = exports.getAllClaudeProjectFolders = exports.decodeEncodedPath = exports.getMostRecentlyActiveSessionDir = exports.findSubdirectorySessionDirs = exports.findSessionsInDirectory = exports.findAllClaudeSessions = exports.findActiveClaudeSession = void 0;
8
+ 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.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.PatternExtractor = exports.HeatmapTracker = exports.FrequencyTracker = exports.getSnapshotPath = exports.isSnapshotValid = exports.deleteSnapshot = exports.loadSnapshot = exports.saveSnapshot = exports.parseTodoDependencies = exports.EventAggregator = exports.getRandomPhrase = exports.PHRASE_CATEGORIES = exports.ALL_PHRASES = exports.HIGHLIGHT_CSS = exports.clearHighlightCache = void 0;
9
+ exports.isZaiProviderId = exports.inferZaiQuotaState = exports.extractNextFlushTime = exports.accumulateZaiUsage = exports.CodexQuotaWatcher = exports.resolveCodexQuotaFromLocalSources = exports.resolveCodexQuota = exports.readLatestCodexQuotaFromRollouts = exports.quotaFromCodexRateLimits = exports.fetchCodexQuotaFromApi = exports.getWorkspaceIdFromPath = exports.pruneQuotaHistory = exports.readQuotaHistoryDailyBuckets = exports.readQuotaHistoryRange = exports.appendQuotaHistorySample = exports.writeQuotaSnapshot = exports.readQuotaSnapshot = exports.QuotaPoller = exports.describeQuotaFailure = exports.fetchQuota = 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.getActiveCodexAccount = exports.listCodexAccounts = exports.getSystemCodexHome = exports.getCodexMonitoringHomes = exports.getCodexProfileHome = exports.getCodexProfilesDir = exports.readClaudeProfileIdentity = exports.isClaudeProfileAuthenticated = exports.claudeKeychainService = exports.claudeKeychainSuffix = exports.getClaudeProfileHome = exports.getClaudeProfilesDir = exports.getActiveAccountStatus = exports.removeSavedAccountProfile = exports.replaceSavedAccountProfiles = void 0;
10
+ exports.quotaFailureDescriptorSchema = exports.peakHoursStateSchema = exports.quotaSourceSchema = exports.quotaProviderIdSchema = exports.quotaFailureKindSchema = exports.quotaStateSchema = exports.quotaWindowSchema = exports.extractSessionEvents = exports.permissionModeSchema = exports.sessionEventSchema = exports.sessionMessageSchema = exports.messageUsageSchema = exports.gatherAssetsForCwd = exports.codexSessions = exports.readCodexAssets = exports.claudeSessions = exports.readClaudeAssets = exports.extractCommands = exports.extractFilePaths = exports.extractUrls = exports.extractToolCalls = exports.extractToolCall = exports.extractTokenUsage = exports.LITELLM_CATALOG_URL = 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 = exports.MultiProviderQuotaService = exports.ZaiQuotaWatcher = exports.ZAI_TIER_BUDGETS = exports.ZAI_PROVIDER_IDS = exports.ZAI_PROMPT_INVOCATIONS = exports.turnTokenWeight = exports.rowsToZaiTurnsAndErrors = exports.resolveZaiTier = exports.parseZaiQuotaError = exports.makeUnavailableZaiQuotaState = void 0;
11
+ exports.scopePeakHoursToSessionProvider = exports.isClaudeCodeSessionProvider = exports.fetchPeakHoursStatus = exports.createPeakHoursNotApplicableState = 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 = exports.runtimeQuotaProviderSchema = void 0;
12
12
  var taskPersistence_1 = require("./types/taskPersistence");
13
13
  Object.defineProperty(exports, "TASK_PERSISTENCE_SCHEMA_VERSION", { enumerable: true, get: function () { return taskPersistence_1.TASK_PERSISTENCE_SCHEMA_VERSION; } });
14
14
  Object.defineProperty(exports, "normalizeTaskStatus", { enumerable: true, get: function () { return taskPersistence_1.normalizeTaskStatus; } });
@@ -56,6 +56,7 @@ var claudeCode_1 = require("./providers/claudeCode");
56
56
  Object.defineProperty(exports, "ClaudeCodeProvider", { enumerable: true, get: function () { return claudeCode_1.ClaudeCodeProvider; } });
57
57
  var openCode_1 = require("./providers/openCode");
58
58
  Object.defineProperty(exports, "OpenCodeProvider", { enumerable: true, get: function () { return openCode_1.OpenCodeProvider; } });
59
+ Object.defineProperty(exports, "getOpenCodeDataDir", { enumerable: true, get: function () { return openCode_1.getOpenCodeDataDir; } });
59
60
  var codex_1 = require("./providers/codex");
60
61
  Object.defineProperty(exports, "CodexProvider", { enumerable: true, get: function () { return codex_1.CodexProvider; } });
61
62
  // Parsers — JSONL
@@ -295,6 +296,21 @@ Object.defineProperty(exports, "resolveCodexQuota", { enumerable: true, get: fun
295
296
  Object.defineProperty(exports, "resolveCodexQuotaFromLocalSources", { enumerable: true, get: function () { return codexQuota_1.resolveCodexQuotaFromLocalSources; } });
296
297
  var codexQuotaWatcher_1 = require("./codexQuotaWatcher");
297
298
  Object.defineProperty(exports, "CodexQuotaWatcher", { enumerable: true, get: function () { return codexQuotaWatcher_1.CodexQuotaWatcher; } });
299
+ var zaiQuota_1 = require("./zaiQuota");
300
+ Object.defineProperty(exports, "accumulateZaiUsage", { enumerable: true, get: function () { return zaiQuota_1.accumulateZaiUsage; } });
301
+ Object.defineProperty(exports, "extractNextFlushTime", { enumerable: true, get: function () { return zaiQuota_1.extractNextFlushTime; } });
302
+ Object.defineProperty(exports, "inferZaiQuotaState", { enumerable: true, get: function () { return zaiQuota_1.inferZaiQuotaState; } });
303
+ Object.defineProperty(exports, "isZaiProviderId", { enumerable: true, get: function () { return zaiQuota_1.isZaiProviderId; } });
304
+ Object.defineProperty(exports, "makeUnavailableZaiQuotaState", { enumerable: true, get: function () { return zaiQuota_1.makeUnavailableZaiQuotaState; } });
305
+ Object.defineProperty(exports, "parseZaiQuotaError", { enumerable: true, get: function () { return zaiQuota_1.parseZaiQuotaError; } });
306
+ Object.defineProperty(exports, "resolveZaiTier", { enumerable: true, get: function () { return zaiQuota_1.resolveZaiTier; } });
307
+ Object.defineProperty(exports, "rowsToZaiTurnsAndErrors", { enumerable: true, get: function () { return zaiQuota_1.rowsToZaiTurnsAndErrors; } });
308
+ Object.defineProperty(exports, "turnTokenWeight", { enumerable: true, get: function () { return zaiQuota_1.turnTokenWeight; } });
309
+ Object.defineProperty(exports, "ZAI_PROMPT_INVOCATIONS", { enumerable: true, get: function () { return zaiQuota_1.ZAI_PROMPT_INVOCATIONS; } });
310
+ Object.defineProperty(exports, "ZAI_PROVIDER_IDS", { enumerable: true, get: function () { return zaiQuota_1.ZAI_PROVIDER_IDS; } });
311
+ Object.defineProperty(exports, "ZAI_TIER_BUDGETS", { enumerable: true, get: function () { return zaiQuota_1.ZAI_TIER_BUDGETS; } });
312
+ var zaiQuotaWatcher_1 = require("./zaiQuotaWatcher");
313
+ Object.defineProperty(exports, "ZaiQuotaWatcher", { enumerable: true, get: function () { return zaiQuotaWatcher_1.ZaiQuotaWatcher; } });
298
314
  var multiProviderQuotaService_1 = require("./multiProviderQuotaService");
299
315
  Object.defineProperty(exports, "MultiProviderQuotaService", { enumerable: true, get: function () { return multiProviderQuotaService_1.MultiProviderQuotaService; } });
300
316
  // Model Context
@@ -361,6 +377,7 @@ Object.defineProperty(exports, "runtimeQuotaProviderSchema", { enumerable: true,
361
377
  Object.defineProperty(exports, "providerQuotaStateSchema", { enumerable: true, get: function () { return quota_2.providerQuotaStateSchema; } });
362
378
  Object.defineProperty(exports, "claudeProviderQuotaStateSchema", { enumerable: true, get: function () { return quota_2.claudeProviderQuotaStateSchema; } });
363
379
  Object.defineProperty(exports, "codexProviderQuotaStateSchema", { enumerable: true, get: function () { return quota_2.codexProviderQuotaStateSchema; } });
380
+ Object.defineProperty(exports, "zaiProviderQuotaStateSchema", { enumerable: true, get: function () { return quota_2.zaiProviderQuotaStateSchema; } });
364
381
  Object.defineProperty(exports, "providerQuotaMapSchema", { enumerable: true, get: function () { return quota_2.providerQuotaMapSchema; } });
365
382
  // Schemas — Zod runtime validation for quota history
366
383
  var quotaHistory_2 = require("./schemas/quotaHistory");
@@ -10,6 +10,11 @@ interface CodexWatcherLike extends Disposable {
10
10
  stop(): void;
11
11
  onUpdate(cb: (state: ProviderQuotaState<'codex'>) => void): Disposable;
12
12
  }
13
+ interface ZaiWatcherLike extends Disposable {
14
+ start(): void;
15
+ stop(): void;
16
+ onUpdate(cb: (state: ProviderQuotaState<'zai'>) => void): Disposable;
17
+ }
13
18
  export interface MultiProviderQuotaServiceOptions {
14
19
  activeIntervalMs?: number;
15
20
  idleIntervalMs?: number;
@@ -18,6 +23,7 @@ export interface MultiProviderQuotaServiceOptions {
18
23
  includePeakHours?: boolean;
19
24
  codexWorkspacePath?: string;
20
25
  codexWatcher?: CodexWatcherLike | null;
26
+ zaiWatcher?: ZaiWatcherLike | null;
21
27
  readClaudeCredentials?: () => Promise<ClaudeMaxCredentials | null>;
22
28
  readClaudeAccount?: typeof readActiveClaudeAccount;
23
29
  fetchClaudeQuota?: (accessToken: string) => Promise<QuotaState>;
@@ -39,6 +45,9 @@ export declare class MultiProviderQuotaService implements Disposable {
39
45
  private readonly codexWatcher;
40
46
  private readonly ownsCodexWatcher;
41
47
  private codexSubscription;
48
+ private readonly zaiWatcher;
49
+ private readonly ownsZaiWatcher;
50
+ private zaiSubscription;
42
51
  private timer;
43
52
  private polling;
44
53
  private quotaByProvider;
@@ -60,6 +69,7 @@ export declare class MultiProviderQuotaService implements Disposable {
60
69
  getLatest(): ProviderQuotaMap;
61
70
  updateProviderQuota(provider: 'claude', state: ProviderQuotaState<'claude'>): void;
62
71
  updateProviderQuota(provider: 'codex', state: ProviderQuotaState<'codex'>): void;
72
+ updateProviderQuota(provider: 'zai', state: ProviderQuotaState<'zai'>): void;
63
73
  dispose(): void;
64
74
  private refreshPeakHours;
65
75
  private poll;
@@ -59,6 +59,9 @@ class MultiProviderQuotaService {
59
59
  codexWatcher;
60
60
  ownsCodexWatcher;
61
61
  codexSubscription = null;
62
+ zaiWatcher;
63
+ ownsZaiWatcher;
64
+ zaiSubscription = null;
62
65
  timer;
63
66
  polling = false;
64
67
  quotaByProvider = {};
@@ -97,12 +100,24 @@ class MultiProviderQuotaService {
97
100
  this.codexSubscription = this.codexWatcher?.onUpdate((state) => {
98
101
  this.updateProviderQuota('codex', state);
99
102
  }) ?? null;
103
+ if (options.zaiWatcher !== undefined) {
104
+ this.zaiWatcher = options.zaiWatcher;
105
+ this.ownsZaiWatcher = false;
106
+ }
107
+ else {
108
+ this.zaiWatcher = null;
109
+ this.ownsZaiWatcher = false;
110
+ }
111
+ this.zaiSubscription = this.zaiWatcher?.onUpdate((state) => {
112
+ this.updateProviderQuota('zai', state);
113
+ }) ?? null;
100
114
  }
101
115
  startPolling() {
102
116
  if (this.polling)
103
117
  return;
104
118
  this.polling = true;
105
119
  this.codexWatcher?.start();
120
+ this.zaiWatcher?.start();
106
121
  void this.poll();
107
122
  this.log?.('[Quota] Polling started');
108
123
  }
@@ -115,6 +130,7 @@ class MultiProviderQuotaService {
115
130
  this.timer = undefined;
116
131
  }
117
132
  this.codexWatcher?.stop();
133
+ this.zaiWatcher?.stop();
118
134
  this.log?.('[Quota] Polling stopped');
119
135
  }
120
136
  setPollingMode(mode) {
@@ -165,6 +181,19 @@ class MultiProviderQuotaService {
165
181
  this.emit();
166
182
  return;
167
183
  }
184
+ if (provider === 'zai') {
185
+ const nextState = {
186
+ ...state,
187
+ runtimeProvider: 'zai',
188
+ providerId: 'zai',
189
+ };
190
+ this.quotaByProvider = {
191
+ ...this.quotaByProvider,
192
+ zai: nextState,
193
+ };
194
+ this.emit();
195
+ return;
196
+ }
168
197
  const nextState = {
169
198
  ...state,
170
199
  runtimeProvider: 'codex',
@@ -183,6 +212,11 @@ class MultiProviderQuotaService {
183
212
  if (this.ownsCodexWatcher) {
184
213
  this.codexWatcher?.dispose();
185
214
  }
215
+ this.zaiSubscription?.dispose();
216
+ this.zaiSubscription = null;
217
+ if (this.ownsZaiWatcher) {
218
+ this.zaiWatcher?.dispose();
219
+ }
186
220
  this.listeners.splice(0, this.listeners.length);
187
221
  }
188
222
  async refreshPeakHours() {
@@ -1,7 +1,7 @@
1
1
  import type { PeakHoursState } from './peakHours';
2
2
  import type { QuotaState } from './quota';
3
3
  import type { QuotaFailureDescriptor } from './quotaPresentation';
4
- export type RuntimeQuotaProvider = 'claude' | 'codex';
4
+ export type RuntimeQuotaProvider = 'claude' | 'codex' | 'zai';
5
5
  export interface ProviderQuotaState<TProvider extends RuntimeQuotaProvider = RuntimeQuotaProvider> extends QuotaState {
6
6
  runtimeProvider: TProvider;
7
7
  accountLabel?: string;
@@ -12,4 +12,5 @@ export interface ProviderQuotaState<TProvider extends RuntimeQuotaProvider = Run
12
12
  export interface ProviderQuotaMap {
13
13
  claude?: ProviderQuotaState<'claude'>;
14
14
  codex?: ProviderQuotaState<'codex'>;
15
+ zai?: ProviderQuotaState<'zai'>;
15
16
  }
@@ -44,17 +44,7 @@ const fs = __importStar(require("fs"));
44
44
  const os = __importStar(require("os"));
45
45
  const path = __importStar(require("path"));
46
46
  const codexProfiles_1 = require("../codexProfiles");
47
- function getOpenCodeDataDir() {
48
- const xdg = process.env.XDG_DATA_HOME;
49
- if (xdg)
50
- return path.join(xdg, 'opencode');
51
- if (process.platform === 'darwin')
52
- return path.join(os.homedir(), 'Library', 'Application Support', 'opencode');
53
- if (process.platform === 'win32') {
54
- return path.join(process.env.LOCALAPPDATA || process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'opencode');
55
- }
56
- return path.join(os.homedir(), '.local', 'share', 'opencode');
57
- }
47
+ const openCode_1 = require("./openCode");
58
48
  function getCodexHomes() {
59
49
  return (0, codexProfiles_1.getCodexMonitoringHomes)();
60
50
  }
@@ -109,7 +99,7 @@ function getMostRecentMtime(dir) {
109
99
  }
110
100
  }
111
101
  function getOpenCodeActivityMtime() {
112
- const dataDir = getOpenCodeDataDir();
102
+ const dataDir = (0, openCode_1.getOpenCodeDataDir)();
113
103
  const dbPath = path.join(dataDir, 'opencode.db');
114
104
  try {
115
105
  const dbMtime = fs.statSync(dbPath).mtime.getTime();
@@ -137,7 +127,7 @@ function getCodexActivityMtime() {
137
127
  }
138
128
  function getProviderPaths() {
139
129
  const claudeBase = path.join(os.homedir(), '.claude', 'projects');
140
- const openCodeDataDir = getOpenCodeDataDir();
130
+ const openCodeDataDir = (0, openCode_1.getOpenCodeDataDir)();
141
131
  const codexHomes = getCodexHomes();
142
132
  return {
143
133
  claudeBase,
@@ -16,8 +16,20 @@
16
16
  * @module providers/openCode
17
17
  */
18
18
  import type { ReadSessionContextSnapshotOptions, SessionContextSnapshot } from '../context/sessionContext';
19
+ import type { ZaiTier } from '../zaiQuota';
20
+ import type { QuotaState } from '../quota';
19
21
  import type { SessionProviderBase, SessionReader, ProjectFolderInfo, SearchHit, SessionFileStats, ProviderId, ProviderRuntimeStatus } from './types';
20
22
  import type { TokenUsage, SubagentStats, ContextAttribution } from '../types/sessionEvent';
23
+ /**
24
+ * Gets the OpenCode data directory.
25
+ *
26
+ * Resolves in priority order: `XDG_DATA_HOME` (if set), then whichever of
27
+ * the platform-default candidates actually contains an `opencode.db` file.
28
+ * OpenCode's CLI has historically used the Linux-style `~/.local/share/opencode`
29
+ * path even on macOS, so we probe both and pick whichever exists — falling
30
+ * back to the platform default when neither is present yet.
31
+ */
32
+ export declare function getOpenCodeDataDir(): string;
21
33
  /**
22
34
  * Session provider for OpenCode CLI.
23
35
  *
@@ -77,4 +89,23 @@ export declare class OpenCodeProvider implements SessionProviderBase {
77
89
  /** Set a runtime-reported context window limit (overrides static map). */
78
90
  setDynamicContextWindowLimit(limit: number): void;
79
91
  dispose(): void;
92
+ /**
93
+ * Derives a z.ai coding-plan `QuotaState` from OpenCode assistant turns
94
+ * tagged with `providerID ∈ {zai, zai-coding-plan}`.
95
+ *
96
+ * z.ai does not expose a quota API (verified vs. docs.z.ai/openapi.json),
97
+ * so this method accumulates observed per-turn tokens from OpenCode's DB
98
+ * into 5-hour / 7-day rolling windows and infers utilization against the
99
+ * configured tier budget. Returns null when no z.ai routing is detected.
100
+ *
101
+ * @param tier Plan tier; `'auto'` infers from observed weekly volume.
102
+ * When the caller has no opinion, the heuristic falls back to
103
+ * `'max'` (conservative — small overcount rather than large).
104
+ */
105
+ getZaiQuotaState(tier?: ZaiTier | 'auto'): QuotaState | null;
106
+ /**
107
+ * Cheap boolean: does the local OpenCode install currently route at z.ai?
108
+ * Used by callers to decide whether to render a z.ai quota surface.
109
+ */
110
+ isZaiRoutingActive(): boolean;
80
111
  }
@@ -51,6 +51,7 @@ var __importStar = (this && this.__importStar) || (function () {
51
51
  })();
52
52
  Object.defineProperty(exports, "__esModule", { value: true });
53
53
  exports.OpenCodeProvider = void 0;
54
+ exports.getOpenCodeDataDir = getOpenCodeDataDir;
54
55
  const fs = __importStar(require("fs"));
55
56
  const os = __importStar(require("os"));
56
57
  const path = __importStar(require("path"));
@@ -58,19 +59,42 @@ const child_process_1 = require("child_process");
58
59
  const sessionContext_1 = require("../context/sessionContext");
59
60
  const openCodeParser_1 = require("../parsers/openCodeParser");
60
61
  const openCodeDatabase_1 = require("./openCodeDatabase");
62
+ const zaiQuota_1 = require("../zaiQuota");
61
63
  const modelContext_1 = require("../modelContext");
62
64
  // ---------------------------------------------------------------------------
63
65
  // Helper functions
64
66
  // ---------------------------------------------------------------------------
65
67
  /**
66
68
  * Gets the OpenCode data directory.
67
- * Respects XDG_DATA_HOME if set, otherwise uses ~/.local/share/opencode/
69
+ *
70
+ * Resolves in priority order: `XDG_DATA_HOME` (if set), then whichever of
71
+ * the platform-default candidates actually contains an `opencode.db` file.
72
+ * OpenCode's CLI has historically used the Linux-style `~/.local/share/opencode`
73
+ * path even on macOS, so we probe both and pick whichever exists — falling
74
+ * back to the platform default when neither is present yet.
68
75
  */
69
76
  function getOpenCodeDataDir() {
70
77
  const xdg = process.env.XDG_DATA_HOME;
71
- if (xdg) {
78
+ if (xdg)
72
79
  return path.join(xdg, 'opencode');
80
+ const candidates = [path.join(os.homedir(), '.local', 'share', 'opencode')];
81
+ if (process.platform === 'darwin') {
82
+ candidates.push(path.join(os.homedir(), 'Library', 'Application Support', 'opencode'));
83
+ }
84
+ else if (process.platform === 'win32') {
85
+ candidates.push(path.join(process.env.LOCALAPPDATA || process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'opencode'));
86
+ }
87
+ for (const candidate of candidates) {
88
+ try {
89
+ if (fs.existsSync(path.join(candidate, 'opencode.db')))
90
+ return candidate;
91
+ }
92
+ catch {
93
+ // ignore — try the next candidate.
94
+ }
73
95
  }
96
+ // Nothing found yet — return the platform default so a fresh OpenCode
97
+ // install gets detected at the conventional location for this OS.
74
98
  if (process.platform === 'darwin') {
75
99
  return path.join(os.homedir(), 'Library', 'Application Support', 'opencode');
76
100
  }
@@ -1548,5 +1572,87 @@ class OpenCodeProvider {
1548
1572
  this.sessionMetaCache.clear();
1549
1573
  this.dynamicContextWindowLimit = null;
1550
1574
  }
1575
+ // --- z.ai (GLM Coding Plan) quota derivation ---
1576
+ /**
1577
+ * Derives a z.ai coding-plan `QuotaState` from OpenCode assistant turns
1578
+ * tagged with `providerID ∈ {zai, zai-coding-plan}`.
1579
+ *
1580
+ * z.ai does not expose a quota API (verified vs. docs.z.ai/openapi.json),
1581
+ * so this method accumulates observed per-turn tokens from OpenCode's DB
1582
+ * into 5-hour / 7-day rolling windows and infers utilization against the
1583
+ * configured tier budget. Returns null when no z.ai routing is detected.
1584
+ *
1585
+ * @param tier Plan tier; `'auto'` infers from observed weekly volume.
1586
+ * When the caller has no opinion, the heuristic falls back to
1587
+ * `'max'` (conservative — small overcount rather than large).
1588
+ */
1589
+ getZaiQuotaState(tier = 'auto') {
1590
+ const db = this.ensureDb();
1591
+ if (!db)
1592
+ return null;
1593
+ const sinceMs = Date.now() - 7 * 86_400_000;
1594
+ let rawRows = [];
1595
+ try {
1596
+ rawRows = db.getAssistantMessagesByProviderId([...zaiQuota_1.ZAI_PROVIDER_IDS], sinceMs);
1597
+ }
1598
+ catch {
1599
+ return null;
1600
+ }
1601
+ if (rawRows.length === 0)
1602
+ return null;
1603
+ const turns = rawRows.map(row => ({
1604
+ timestampMs: row.timeCreated,
1605
+ model: row.modelId,
1606
+ inputTokens: row.inputTokens || 0,
1607
+ outputTokens: row.outputTokens || 0,
1608
+ cacheReadTokens: row.cacheReadTokens || 0,
1609
+ cacheWriteTokens: row.cacheWriteTokens || 0,
1610
+ reasoningTokens: row.reasoningTokens || 0,
1611
+ }));
1612
+ const accumulated = (0, zaiQuota_1.accumulateZaiUsage)(turns, Date.now());
1613
+ // Default to 'max' on auto when usage is non-zero — overestimating the
1614
+ // tier gives a lower (safer) utilization % until calibration runs.
1615
+ const resolvedTier = tier === 'auto'
1616
+ ? (accumulated.weeklyTurns > 0 ? 'max' : 'lite')
1617
+ : tier;
1618
+ // Walk rows again to trap any captured z.ai business errors.
1619
+ let authoritativeFiveHourResetAt;
1620
+ let authoritativeWeeklyResetAt;
1621
+ for (const row of rawRows) {
1622
+ if (row.errorCode == null && !row.errorMessage)
1623
+ continue;
1624
+ const parsed = (0, zaiQuota_1.parseZaiQuotaError)({
1625
+ code: row.errorCode ?? undefined,
1626
+ message: row.errorMessage ?? undefined,
1627
+ });
1628
+ if (!parsed?.resetsAt)
1629
+ continue;
1630
+ if (parsed.kind === 'exhausted') {
1631
+ authoritativeFiveHourResetAt = parsed.resetsAt;
1632
+ if (String(parsed.code) === '1310')
1633
+ authoritativeWeeklyResetAt = parsed.resetsAt;
1634
+ }
1635
+ }
1636
+ return (0, zaiQuota_1.inferZaiQuotaState)(accumulated, resolvedTier, {
1637
+ authoritativeFiveHourResetAt,
1638
+ authoritativeWeeklyResetAt,
1639
+ });
1640
+ }
1641
+ /**
1642
+ * Cheap boolean: does the local OpenCode install currently route at z.ai?
1643
+ * Used by callers to decide whether to render a z.ai quota surface.
1644
+ */
1645
+ isZaiRoutingActive() {
1646
+ const db = this.ensureDb();
1647
+ if (!db)
1648
+ return false;
1649
+ try {
1650
+ const rows = db.getAssistantMessagesByProviderId([...zaiQuota_1.ZAI_PROVIDER_IDS], Date.now() - 7 * 86_400_000);
1651
+ return rows.length > 0;
1652
+ }
1653
+ catch {
1654
+ return false;
1655
+ }
1656
+ }
1551
1657
  }
1552
1658
  exports.OpenCodeProvider = OpenCodeProvider;
@@ -91,6 +91,25 @@ export declare class OpenCodeDatabase {
91
91
  sessionCount: number;
92
92
  maxTimeUpdated: number;
93
93
  }>;
94
+ /**
95
+ * Returns assistant messages tagged with the given providerID(s), completed
96
+ * within `sinceMs`. Used by the z.ai quota accumulator to walk OpenCode's
97
+ * per-turn token records without re-deriving the session filter.
98
+ *
99
+ * Results are ordered oldest-first by `time_created`.
100
+ */
101
+ getAssistantMessagesByProviderId(providerIds: readonly string[], sinceMs: number): Array<{
102
+ timeCreated: number;
103
+ modelId: string;
104
+ providerId: string;
105
+ inputTokens: number;
106
+ outputTokens: number;
107
+ cacheReadTokens: number;
108
+ cacheWriteTokens: number;
109
+ reasoningTokens: number;
110
+ errorMessage: string | null;
111
+ errorCode: string | null;
112
+ }>;
94
113
  /** Get the database file's mtime (ms epoch). Returns 0 if unavailable. */
95
114
  getDbMtime(): number;
96
115
  }
@@ -249,6 +249,34 @@ class OpenCodeDatabase {
249
249
  getProjectSessionStats() {
250
250
  return this.query('SELECT project_id AS projectId, COUNT(*) AS sessionCount, MAX(time_updated) AS maxTimeUpdated FROM session GROUP BY project_id');
251
251
  }
252
+ /**
253
+ * Returns assistant messages tagged with the given providerID(s), completed
254
+ * within `sinceMs`. Used by the z.ai quota accumulator to walk OpenCode's
255
+ * per-turn token records without re-deriving the session filter.
256
+ *
257
+ * Results are ordered oldest-first by `time_created`.
258
+ */
259
+ getAssistantMessagesByProviderId(providerIds, sinceMs) {
260
+ if (providerIds.length === 0)
261
+ return [];
262
+ const providerClauses = providerIds.map(() => 'json_extract(data, \'$.providerID\') = ?').join(' OR ');
263
+ return this.query(`SELECT
264
+ time_created AS timeCreated,
265
+ COALESCE(json_extract(data, '$.modelID'), 'unknown') AS modelId,
266
+ COALESCE(json_extract(data, '$.providerID'), '') AS providerId,
267
+ COALESCE(json_extract(data, '$.tokens.input'), 0) AS inputTokens,
268
+ COALESCE(json_extract(data, '$.tokens.output'), 0) AS outputTokens,
269
+ COALESCE(json_extract(data, '$.tokens.cache.read'), 0) AS cacheReadTokens,
270
+ COALESCE(json_extract(data, '$.tokens.cache.write'), 0) AS cacheWriteTokens,
271
+ COALESCE(json_extract(data, '$.tokens.reasoning'), 0) AS reasoningTokens,
272
+ json_extract(data, '$.error.message') AS errorMessage,
273
+ json_extract(data, '$.error.code') AS errorCode
274
+ FROM message
275
+ WHERE time_created >= ?
276
+ AND json_extract(data, '$.role') = 'assistant'
277
+ AND (${providerClauses})
278
+ ORDER BY time_created ASC`, [sinceMs, ...providerIds]);
279
+ }
252
280
  /** Get the database file's mtime (ms epoch). Returns 0 if unavailable. */
253
281
  getDbMtime() {
254
282
  try {
package/dist/quota.d.ts CHANGED
@@ -28,7 +28,7 @@ export interface QuotaState {
28
28
  /** Projected 7-day utilization at reset (percentage, capped at 200) */
29
29
  projectedSevenDay?: number;
30
30
  /** Provider that produced this quota sample */
31
- providerId?: 'claude-code' | 'codex';
31
+ providerId?: 'claude-code' | 'codex' | 'zai';
32
32
  /** Source of the sample */
33
33
  source?: 'api' | 'session' | 'cache';
34
34
  /** ISO timestamp when the sample was captured */
@@ -6,7 +6,7 @@
6
6
  * dashboard, the `sidekick quota history` CLI, contextful_desktop) can render heatmaps and
7
7
  * trend visualisations over a 13-week window.
8
8
  */
9
- export type QuotaHistoryRuntimeProvider = 'claude' | 'codex';
9
+ export type QuotaHistoryRuntimeProvider = 'claude' | 'codex' | 'zai';
10
10
  export interface QuotaHistorySample {
11
11
  timestamp: string;
12
12
  runtimeProvider: QuotaHistoryRuntimeProvider;
@@ -201,7 +201,7 @@ function pruneFileSync(filePath, retentionDays) {
201
201
  return { kept: keptLines.length, pruned };
202
202
  }
203
203
  function sampleToQuotaState(sample) {
204
- const providerId = sample.runtimeProvider === 'claude' ? 'claude-code' : 'codex';
204
+ const providerId = runtimeProviderToSnapshotProvider(sample.runtimeProvider);
205
205
  return {
206
206
  fiveHour: { utilization: sample.fiveHour.utilization, resetsAt: sample.fiveHour.resetsAt },
207
207
  sevenDay: { utilization: sample.sevenDay.utilization, resetsAt: sample.sevenDay.resetsAt },
@@ -213,6 +213,18 @@ function sampleToQuotaState(sample) {
213
213
  stale: sample.stale,
214
214
  };
215
215
  }
216
+ /**
217
+ * Maps a runtime provider to the snapshot storage key.
218
+ * `'zai'` is allowed at the storage layer even though it is not part of
219
+ * `AccountProviderId` (z.ai has no full account management in v1).
220
+ */
221
+ function runtimeProviderToSnapshotProvider(runtime) {
222
+ if (runtime === 'claude')
223
+ return 'claude-code';
224
+ if (runtime === 'zai')
225
+ return 'zai';
226
+ return 'codex';
227
+ }
216
228
  async function runAppend(sample, filePath, options) {
217
229
  let lastTs = lastWriteCache.get(filePath);
218
230
  if (lastTs === undefined) {
@@ -243,7 +255,7 @@ async function runAppend(sample, filePath, options) {
243
255
  // Backwards-compat: keep the latest-snapshot store hot so existing callers (DashboardViewProvider,
244
256
  // codex session provider, contextful_desktop) don't have to query history for "latest".
245
257
  try {
246
- const providerId = sample.runtimeProvider === 'claude' ? 'claude-code' : 'codex';
258
+ const providerId = runtimeProviderToSnapshotProvider(sample.runtimeProvider);
247
259
  (0, quotaSnapshots_1.writeQuotaSnapshot)(providerId, sample.providerId, sampleToQuotaState(sample));
248
260
  }
249
261
  catch {
@@ -1,4 +1,10 @@
1
1
  import type { QuotaState } from './quota';
2
2
  import type { AccountProviderId } from './accountRegistry';
3
- export declare function writeQuotaSnapshot(providerId: AccountProviderId, accountId: string, quota: QuotaState): void;
4
- export declare function readQuotaSnapshot(providerId: AccountProviderId, accountId: string): QuotaState | null;
3
+ /**
4
+ * Storage key for a quota snapshot. Extends `AccountProviderId` with `'zai'`
5
+ * because z.ai has no full account-management surface in v1, but the snapshot
6
+ * store still needs a stable key for the derived z.ai quota cache.
7
+ */
8
+ export type QuotaSnapshotProviderId = AccountProviderId | 'zai';
9
+ export declare function writeQuotaSnapshot(providerId: QuotaSnapshotProviderId, accountId: string, quota: QuotaState): void;
10
+ export declare function readQuotaSnapshot(providerId: QuotaSnapshotProviderId, accountId: string): QuotaState | null;