sidekick-shared 0.21.1 → 0.21.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -2
- package/dist/providers/openCode.d.ts +4 -4
- package/dist/providers/openCode.js +5 -5
- package/dist/quotaSnapshots.d.ts +1 -1
- package/dist/zaiQuota.d.ts +6 -6
- package/dist/zaiQuotaApi.d.ts +33 -0
- package/dist/zaiQuotaApi.js +271 -0
- package/dist/zaiQuotaWatcher.js +9 -8
- package/package.json +1 -1
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), Codex rate-limit extraction from event streams, and
|
|
43
|
+
| **Quota** | Claude Max subscription quota fetching (5-hour and 7-day windows), Codex rate-limit extraction from event streams, and authoritative z.ai Coding Plan quota fetching via `resolveZaiQuota()` / `fetchZaiQuotaFromApi()` (reads z.ai's `api/monitor/usage/quota/limit` endpoint with cached-snapshot fallback). The deprecated observed-traffic z.ai estimator helpers remain exported for backward compatibility only |
|
|
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/z.ai quota updates behind one typed 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; z.ai quota uses the API resolver with snapshot fallback |
|
|
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
|
@@ -125,6 +125,8 @@ export { accumulateZaiUsage, extractNextFlushTime, inferZaiQuotaState, isZaiProv
|
|
|
125
125
|
export type { ZaiAccumulatedUsage, ZaiAssistantTurn, ZaiOpenCodeRow, ZaiProviderId, ZaiQuotaError, ZaiQuotaErrorKind, ZaiTier, InferZaiQuotaStateOptions, } from './zaiQuota';
|
|
126
126
|
export { ZaiQuotaWatcher } from './zaiQuotaWatcher';
|
|
127
127
|
export type { ZaiQuotaWatcherOptions } from './zaiQuotaWatcher';
|
|
128
|
+
export { fetchZaiQuotaFromApi, quotaStateFromZaiQuotaLimitPayload, readZaiCredentials, resolveZaiQuota, } from './zaiQuotaApi';
|
|
129
|
+
export type { ReadZaiCredentialsOptions, ZaiCredentialSource, ZaiCredentials, ZaiPlatform, ZaiQuotaApiOptions, ZaiQuotaResolveOptions, } from './zaiQuotaApi';
|
|
128
130
|
export { MultiProviderQuotaService } from './multiProviderQuotaService';
|
|
129
131
|
export type { MultiProviderQuotaServiceOptions } from './multiProviderQuotaService';
|
|
130
132
|
export type { ProviderQuotaMap, ProviderQuotaState, RuntimeQuotaProvider } from './providerQuota';
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,8 @@ exports.discoverSessionDirectory = exports.getClaudeSessionDirectory = exports.e
|
|
|
7
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
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
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.
|
|
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;
|
|
10
|
+
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.resolveZaiQuota = exports.readZaiCredentials = exports.quotaStateFromZaiQuotaLimitPayload = exports.fetchZaiQuotaFromApi = 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 = exports.quotaFailureDescriptorSchema = exports.peakHoursStateSchema = exports.quotaSourceSchema = exports.quotaProviderIdSchema = 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; } });
|
|
@@ -311,6 +311,11 @@ Object.defineProperty(exports, "ZAI_PROVIDER_IDS", { enumerable: true, get: func
|
|
|
311
311
|
Object.defineProperty(exports, "ZAI_TIER_BUDGETS", { enumerable: true, get: function () { return zaiQuota_1.ZAI_TIER_BUDGETS; } });
|
|
312
312
|
var zaiQuotaWatcher_1 = require("./zaiQuotaWatcher");
|
|
313
313
|
Object.defineProperty(exports, "ZaiQuotaWatcher", { enumerable: true, get: function () { return zaiQuotaWatcher_1.ZaiQuotaWatcher; } });
|
|
314
|
+
var zaiQuotaApi_1 = require("./zaiQuotaApi");
|
|
315
|
+
Object.defineProperty(exports, "fetchZaiQuotaFromApi", { enumerable: true, get: function () { return zaiQuotaApi_1.fetchZaiQuotaFromApi; } });
|
|
316
|
+
Object.defineProperty(exports, "quotaStateFromZaiQuotaLimitPayload", { enumerable: true, get: function () { return zaiQuotaApi_1.quotaStateFromZaiQuotaLimitPayload; } });
|
|
317
|
+
Object.defineProperty(exports, "readZaiCredentials", { enumerable: true, get: function () { return zaiQuotaApi_1.readZaiCredentials; } });
|
|
318
|
+
Object.defineProperty(exports, "resolveZaiQuota", { enumerable: true, get: function () { return zaiQuotaApi_1.resolveZaiQuota; } });
|
|
314
319
|
var multiProviderQuotaService_1 = require("./multiProviderQuotaService");
|
|
315
320
|
Object.defineProperty(exports, "MultiProviderQuotaService", { enumerable: true, get: function () { return multiProviderQuotaService_1.MultiProviderQuotaService; } });
|
|
316
321
|
// Model Context
|
|
@@ -93,10 +93,10 @@ export declare class OpenCodeProvider implements SessionProviderBase {
|
|
|
93
93
|
* Derives a z.ai coding-plan `QuotaState` from OpenCode assistant turns
|
|
94
94
|
* tagged with `providerID ∈ {zai, zai-coding-plan}`.
|
|
95
95
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
96
|
+
* This compatibility estimator predates the authoritative z.ai quota API.
|
|
97
|
+
* New product code should use `zaiQuotaApi.ts`; this method remains for
|
|
98
|
+
* consumers that still want a local OpenCode DB-derived estimate. Returns
|
|
99
|
+
* null when no z.ai routing is detected.
|
|
100
100
|
*
|
|
101
101
|
* @param tier Plan tier; `'auto'` infers from observed weekly volume.
|
|
102
102
|
* When the caller has no opinion, the heuristic falls back to
|
|
@@ -1572,15 +1572,15 @@ class OpenCodeProvider {
|
|
|
1572
1572
|
this.sessionMetaCache.clear();
|
|
1573
1573
|
this.dynamicContextWindowLimit = null;
|
|
1574
1574
|
}
|
|
1575
|
-
// --- z.ai (GLM Coding Plan) quota derivation ---
|
|
1575
|
+
// --- z.ai (GLM Coding Plan) compatibility quota derivation ---
|
|
1576
1576
|
/**
|
|
1577
1577
|
* Derives a z.ai coding-plan `QuotaState` from OpenCode assistant turns
|
|
1578
1578
|
* tagged with `providerID ∈ {zai, zai-coding-plan}`.
|
|
1579
1579
|
*
|
|
1580
|
-
*
|
|
1581
|
-
*
|
|
1582
|
-
*
|
|
1583
|
-
*
|
|
1580
|
+
* This compatibility estimator predates the authoritative z.ai quota API.
|
|
1581
|
+
* New product code should use `zaiQuotaApi.ts`; this method remains for
|
|
1582
|
+
* consumers that still want a local OpenCode DB-derived estimate. Returns
|
|
1583
|
+
* null when no z.ai routing is detected.
|
|
1584
1584
|
*
|
|
1585
1585
|
* @param tier Plan tier; `'auto'` infers from observed weekly volume.
|
|
1586
1586
|
* When the caller has no opinion, the heuristic falls back to
|
package/dist/quotaSnapshots.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { AccountProviderId } from './accountRegistry';
|
|
|
3
3
|
/**
|
|
4
4
|
* Storage key for a quota snapshot. Extends `AccountProviderId` with `'zai'`
|
|
5
5
|
* because z.ai has no full account-management surface in v1, but the snapshot
|
|
6
|
-
* store still needs a stable key for the
|
|
6
|
+
* store still needs a stable key for the z.ai quota cache.
|
|
7
7
|
*/
|
|
8
8
|
export type QuotaSnapshotProviderId = AccountProviderId | 'zai';
|
|
9
9
|
export declare function writeQuotaSnapshot(providerId: QuotaSnapshotProviderId, accountId: string, quota: QuotaState): void;
|
package/dist/zaiQuota.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Deprecated stateless quota accumulator for z.ai coding plans.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* The
|
|
4
|
+
* This estimator is kept for compatibility with consumers that imported the
|
|
5
|
+
* old helpers directly. Product code should use `zaiQuotaApi.ts`, which calls
|
|
6
|
+
* z.ai's authoritative quota endpoint. The legacy estimator only uses:
|
|
7
7
|
*
|
|
8
8
|
* 1. Per-call token `usage` returned inline on each chat completion —
|
|
9
9
|
* which OpenCode persists into `opencode.db` message rows tagged with
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* in the human-readable message text.
|
|
14
14
|
*
|
|
15
15
|
* This module is therefore a **derivation** rather than a fetcher: callers
|
|
16
|
-
* feed it observed assistant turns (and any error events), and it computes
|
|
17
|
-
*
|
|
16
|
+
* feed it observed assistant turns (and any error events), and it computes a
|
|
17
|
+
* compatibility `QuotaState` keyed against the published per-tier budgets.
|
|
18
18
|
*
|
|
19
19
|
* Polling / eventing / persistence are the caller's responsibility —
|
|
20
20
|
* see `ZaiQuotaWatcher` for the bundled watcher.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { QuotaState } from './quota';
|
|
2
|
+
import type { ProviderQuotaState } from './providerQuota';
|
|
3
|
+
type ZaiSnapshotProvider = 'zai';
|
|
4
|
+
type SnapshotReader = (providerId: ZaiSnapshotProvider, accountId: string) => QuotaState | null;
|
|
5
|
+
type SnapshotWriter = (providerId: ZaiSnapshotProvider, accountId: string, quota: QuotaState) => void;
|
|
6
|
+
export type ZaiPlatform = 'ZAI' | 'ZHIPU';
|
|
7
|
+
export type ZaiCredentialSource = 'opencode' | 'env';
|
|
8
|
+
export interface ZaiCredentials {
|
|
9
|
+
authToken: string;
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
platform: ZaiPlatform;
|
|
12
|
+
source: ZaiCredentialSource;
|
|
13
|
+
}
|
|
14
|
+
export interface ReadZaiCredentialsOptions {
|
|
15
|
+
openCodeDataDir?: string;
|
|
16
|
+
env?: Record<string, string | undefined>;
|
|
17
|
+
}
|
|
18
|
+
export interface ZaiQuotaApiOptions extends ReadZaiCredentialsOptions {
|
|
19
|
+
credentials?: ZaiCredentials | null;
|
|
20
|
+
fetchImpl?: typeof fetch;
|
|
21
|
+
capturedAt?: string;
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface ZaiQuotaResolveOptions extends ZaiQuotaApiOptions {
|
|
25
|
+
accountId?: string;
|
|
26
|
+
readSnapshot?: SnapshotReader;
|
|
27
|
+
writeSnapshot?: SnapshotWriter;
|
|
28
|
+
}
|
|
29
|
+
export declare function readZaiCredentials(options?: ReadZaiCredentialsOptions): ZaiCredentials | null;
|
|
30
|
+
export declare function quotaStateFromZaiQuotaLimitPayload(payload: unknown, capturedAt?: string): QuotaState;
|
|
31
|
+
export declare function fetchZaiQuotaFromApi(options?: ZaiQuotaApiOptions): Promise<QuotaState>;
|
|
32
|
+
export declare function resolveZaiQuota(options?: ZaiQuotaResolveOptions): Promise<ProviderQuotaState<'zai'>>;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readZaiCredentials = readZaiCredentials;
|
|
7
|
+
exports.quotaStateFromZaiQuotaLimitPayload = quotaStateFromZaiQuotaLimitPayload;
|
|
8
|
+
exports.fetchZaiQuotaFromApi = fetchZaiQuotaFromApi;
|
|
9
|
+
exports.resolveZaiQuota = resolveZaiQuota;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const quotaSnapshots_1 = require("./quotaSnapshots");
|
|
14
|
+
const DEFAULT_ZAI_BASE_URL = 'https://api.z.ai/api/anthropic';
|
|
15
|
+
const QUOTA_PATH = '/api/monitor/usage/quota/limit';
|
|
16
|
+
const DEFAULT_ACCOUNT_ID = 'default';
|
|
17
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
18
|
+
function unavailableZaiQuotaState(error, meta = {}, capturedAt = new Date().toISOString()) {
|
|
19
|
+
return {
|
|
20
|
+
fiveHour: { utilization: 0, resetsAt: '' },
|
|
21
|
+
sevenDay: { utilization: 0, resetsAt: '' },
|
|
22
|
+
available: false,
|
|
23
|
+
error,
|
|
24
|
+
providerId: 'zai',
|
|
25
|
+
source: 'api',
|
|
26
|
+
capturedAt,
|
|
27
|
+
fiveHourLabel: '5-Hour',
|
|
28
|
+
sevenDayLabel: 'Weekly',
|
|
29
|
+
...meta,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function openCodeDataDirCandidates() {
|
|
33
|
+
const candidates = [];
|
|
34
|
+
const xdg = process.env.XDG_DATA_HOME;
|
|
35
|
+
if (xdg)
|
|
36
|
+
candidates.push(path_1.default.join(xdg, 'opencode'));
|
|
37
|
+
candidates.push(path_1.default.join(os_1.default.homedir(), '.local', 'share', 'opencode'));
|
|
38
|
+
if (process.platform === 'darwin') {
|
|
39
|
+
candidates.push(path_1.default.join(os_1.default.homedir(), 'Library', 'Application Support', 'opencode'));
|
|
40
|
+
}
|
|
41
|
+
else if (process.platform === 'win32') {
|
|
42
|
+
candidates.push(path_1.default.join(process.env.LOCALAPPDATA || process.env.APPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Local'), 'opencode'));
|
|
43
|
+
}
|
|
44
|
+
return Array.from(new Set(candidates));
|
|
45
|
+
}
|
|
46
|
+
function readOpenCodeAuthToken(openCodeDataDir) {
|
|
47
|
+
const candidates = openCodeDataDir ? [openCodeDataDir] : openCodeDataDirCandidates();
|
|
48
|
+
for (const dataDir of candidates) {
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(fs_1.default.readFileSync(path_1.default.join(dataDir, 'auth.json'), 'utf8'));
|
|
51
|
+
const codingPlan = parsed['zai-coding-plan'];
|
|
52
|
+
const zai = parsed.zai;
|
|
53
|
+
if (typeof codingPlan?.key === 'string' && codingPlan.key.trim())
|
|
54
|
+
return codingPlan.key.trim();
|
|
55
|
+
if (typeof zai?.key === 'string' && zai.key.trim())
|
|
56
|
+
return zai.key.trim();
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Try the next candidate.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function platformFromBaseUrl(baseUrl) {
|
|
65
|
+
if (baseUrl.includes('api.z.ai'))
|
|
66
|
+
return 'ZAI';
|
|
67
|
+
if (baseUrl.includes('open.bigmodel.cn') || baseUrl.includes('dev.bigmodel.cn'))
|
|
68
|
+
return 'ZHIPU';
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
function readZaiCredentials(options = {}) {
|
|
72
|
+
const openCodeToken = readOpenCodeAuthToken(options.openCodeDataDir);
|
|
73
|
+
if (openCodeToken) {
|
|
74
|
+
return {
|
|
75
|
+
authToken: openCodeToken,
|
|
76
|
+
baseUrl: DEFAULT_ZAI_BASE_URL,
|
|
77
|
+
platform: 'ZAI',
|
|
78
|
+
source: 'opencode',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const env = options.env ?? process.env;
|
|
82
|
+
const authToken = env.ANTHROPIC_AUTH_TOKEN?.trim();
|
|
83
|
+
const baseUrl = env.ANTHROPIC_BASE_URL?.trim();
|
|
84
|
+
if (!authToken || !baseUrl)
|
|
85
|
+
return null;
|
|
86
|
+
const platform = platformFromBaseUrl(baseUrl);
|
|
87
|
+
if (!platform)
|
|
88
|
+
return null;
|
|
89
|
+
return {
|
|
90
|
+
authToken,
|
|
91
|
+
baseUrl,
|
|
92
|
+
platform,
|
|
93
|
+
source: 'env',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function quotaLimitUrl(baseUrl) {
|
|
97
|
+
try {
|
|
98
|
+
const parsed = new URL(baseUrl);
|
|
99
|
+
return `${parsed.protocol}//${parsed.host}${QUOTA_PATH}`;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function tokenLimitEntries(payload) {
|
|
106
|
+
const limits = payload.data?.limits ?? payload.limits ?? [];
|
|
107
|
+
return limits.filter((item) => item?.type === 'TOKENS_LIMIT' && typeof item.percentage === 'number');
|
|
108
|
+
}
|
|
109
|
+
function isFiveHourLimit(item) {
|
|
110
|
+
return item.type === 'TOKENS_LIMIT' && item.unit === 3 && item.number === 5;
|
|
111
|
+
}
|
|
112
|
+
function isWeeklyLimit(item) {
|
|
113
|
+
return item.type === 'TOKENS_LIMIT' && item.unit === 6 && item.number === 1;
|
|
114
|
+
}
|
|
115
|
+
function isoFromEpochMs(value) {
|
|
116
|
+
if (value == null)
|
|
117
|
+
return '';
|
|
118
|
+
const ms = typeof value === 'number' ? value : Number(value);
|
|
119
|
+
if (!Number.isFinite(ms) || ms <= 0)
|
|
120
|
+
return '';
|
|
121
|
+
return new Date(ms).toISOString();
|
|
122
|
+
}
|
|
123
|
+
function displayPlanName(level) {
|
|
124
|
+
if (!level)
|
|
125
|
+
return 'z.ai Coding Plan';
|
|
126
|
+
return `z.ai ${level.charAt(0).toUpperCase()}${level.slice(1)}`;
|
|
127
|
+
}
|
|
128
|
+
function quotaStateFromZaiQuotaLimitPayload(payload, capturedAt = new Date().toISOString()) {
|
|
129
|
+
const data = payload;
|
|
130
|
+
const tokenLimits = tokenLimitEntries(data);
|
|
131
|
+
const fiveHourLimit = tokenLimits.find(isFiveHourLimit) ?? tokenLimits[0];
|
|
132
|
+
const weeklyLimit = tokenLimits.find(isWeeklyLimit) ?? tokenLimits.find(item => item !== fiveHourLimit);
|
|
133
|
+
const level = data.data?.level;
|
|
134
|
+
if (!fiveHourLimit || !weeklyLimit) {
|
|
135
|
+
return unavailableZaiQuotaState('z.ai quota API returned no token quota windows.', {
|
|
136
|
+
failureKind: 'unknown',
|
|
137
|
+
}, capturedAt);
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
fiveHour: {
|
|
141
|
+
utilization: fiveHourLimit.percentage ?? 0,
|
|
142
|
+
resetsAt: isoFromEpochMs(fiveHourLimit.nextResetTime),
|
|
143
|
+
},
|
|
144
|
+
sevenDay: {
|
|
145
|
+
utilization: weeklyLimit.percentage ?? 0,
|
|
146
|
+
resetsAt: isoFromEpochMs(weeklyLimit.nextResetTime),
|
|
147
|
+
},
|
|
148
|
+
available: true,
|
|
149
|
+
providerId: 'zai',
|
|
150
|
+
source: 'api',
|
|
151
|
+
capturedAt,
|
|
152
|
+
fiveHourLabel: '5-Hour',
|
|
153
|
+
sevenDayLabel: 'Weekly',
|
|
154
|
+
planType: level,
|
|
155
|
+
limitId: level ? `zai-${level}` : 'zai-coding-plan',
|
|
156
|
+
limitName: displayPlanName(level),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function parseRetryAfterMs(retryAfter) {
|
|
160
|
+
if (!retryAfter)
|
|
161
|
+
return undefined;
|
|
162
|
+
const seconds = Number(retryAfter);
|
|
163
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
164
|
+
return seconds * 1000;
|
|
165
|
+
const dateMs = Date.parse(retryAfter);
|
|
166
|
+
if (Number.isFinite(dateMs))
|
|
167
|
+
return Math.max(0, dateMs - Date.now());
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
function failureKindForStatus(status) {
|
|
171
|
+
if (status === 401 || status === 403)
|
|
172
|
+
return 'auth';
|
|
173
|
+
if (status === 429)
|
|
174
|
+
return 'rate_limit';
|
|
175
|
+
if (status >= 500)
|
|
176
|
+
return 'server';
|
|
177
|
+
return 'unknown';
|
|
178
|
+
}
|
|
179
|
+
function sanitizeErrorMessage(message, authToken) {
|
|
180
|
+
let result = message;
|
|
181
|
+
if (authToken) {
|
|
182
|
+
result = result.split(authToken).join('<redacted>');
|
|
183
|
+
}
|
|
184
|
+
return result.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/g, 'Bearer <redacted>');
|
|
185
|
+
}
|
|
186
|
+
async function fetchZaiQuotaFromApi(options = {}) {
|
|
187
|
+
const capturedAt = options.capturedAt ?? new Date().toISOString();
|
|
188
|
+
const credentials = options.credentials ?? readZaiCredentials(options);
|
|
189
|
+
if (!credentials) {
|
|
190
|
+
return unavailableZaiQuotaState('No z.ai credentials found. Sign in to OpenCode with z.ai or set ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN.', { failureKind: 'auth' }, capturedAt);
|
|
191
|
+
}
|
|
192
|
+
const url = quotaLimitUrl(credentials.baseUrl);
|
|
193
|
+
if (!url) {
|
|
194
|
+
return unavailableZaiQuotaState('Invalid z.ai base URL.', { failureKind: 'unknown' }, capturedAt);
|
|
195
|
+
}
|
|
196
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
197
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
198
|
+
const controller = new AbortController();
|
|
199
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
200
|
+
timeout.unref?.();
|
|
201
|
+
try {
|
|
202
|
+
const response = await fetchImpl(url, {
|
|
203
|
+
method: 'GET',
|
|
204
|
+
headers: {
|
|
205
|
+
Authorization: credentials.authToken,
|
|
206
|
+
'Accept-Language': 'en-US,en',
|
|
207
|
+
Accept: 'application/json',
|
|
208
|
+
'Content-Type': 'application/json',
|
|
209
|
+
},
|
|
210
|
+
signal: controller.signal,
|
|
211
|
+
});
|
|
212
|
+
const body = await response.text();
|
|
213
|
+
let parsed;
|
|
214
|
+
try {
|
|
215
|
+
parsed = JSON.parse(body);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
parsed = null;
|
|
219
|
+
}
|
|
220
|
+
if (!response.ok) {
|
|
221
|
+
const failureKind = failureKindForStatus(response.status);
|
|
222
|
+
const retryAfterMs = parseRetryAfterMs(response.headers.get('retry-after'));
|
|
223
|
+
const detail = parsed && typeof parsed === 'object' && 'msg' in parsed
|
|
224
|
+
? String(parsed.msg ?? '')
|
|
225
|
+
: '';
|
|
226
|
+
const baseMessage = failureKind === 'auth'
|
|
227
|
+
? `z.ai quota API rejected credentials (HTTP ${response.status}).`
|
|
228
|
+
: `z.ai quota API error (HTTP ${response.status}).`;
|
|
229
|
+
return unavailableZaiQuotaState(sanitizeErrorMessage([baseMessage, detail].filter(Boolean).join(' '), credentials.authToken), { failureKind, httpStatus: response.status, retryAfterMs }, capturedAt);
|
|
230
|
+
}
|
|
231
|
+
return quotaStateFromZaiQuotaLimitPayload(parsed, capturedAt);
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
const isAbort = error instanceof Error && error.name === 'AbortError';
|
|
235
|
+
return unavailableZaiQuotaState(isAbort ? 'z.ai quota API timed out.' : 'z.ai quota API network error.', { failureKind: 'network' }, capturedAt);
|
|
236
|
+
}
|
|
237
|
+
finally {
|
|
238
|
+
clearTimeout(timeout);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function enrichZaiQuota(state) {
|
|
242
|
+
return {
|
|
243
|
+
...state,
|
|
244
|
+
runtimeProvider: 'zai',
|
|
245
|
+
providerId: 'zai',
|
|
246
|
+
fiveHourLabel: state.fiveHourLabel ?? '5-Hour',
|
|
247
|
+
sevenDayLabel: state.sevenDayLabel ?? 'Weekly',
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
async function resolveZaiQuota(options = {}) {
|
|
251
|
+
const accountId = options.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
252
|
+
const readSnapshot = options.readSnapshot ?? quotaSnapshots_1.readQuotaSnapshot;
|
|
253
|
+
const writeSnapshot = options.writeSnapshot ?? quotaSnapshots_1.writeQuotaSnapshot;
|
|
254
|
+
const apiQuota = await fetchZaiQuotaFromApi(options);
|
|
255
|
+
if (apiQuota.available) {
|
|
256
|
+
writeSnapshot('zai', accountId, apiQuota);
|
|
257
|
+
return enrichZaiQuota(apiQuota);
|
|
258
|
+
}
|
|
259
|
+
const cached = readSnapshot('zai', accountId);
|
|
260
|
+
if (cached) {
|
|
261
|
+
return enrichZaiQuota({
|
|
262
|
+
...cached,
|
|
263
|
+
providerId: 'zai',
|
|
264
|
+
source: 'cache',
|
|
265
|
+
stale: true,
|
|
266
|
+
fiveHourLabel: cached.fiveHourLabel ?? '5-Hour',
|
|
267
|
+
sevenDayLabel: cached.sevenDayLabel ?? 'Weekly',
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
return enrichZaiQuota(apiQuota);
|
|
271
|
+
}
|
package/dist/zaiQuotaWatcher.js
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ZaiQuotaWatcher = void 0;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
* observed OpenCode assistant turns.
|
|
5
|
+
* Deprecated event-driven watcher that derives a z.ai coding-plan
|
|
6
|
+
* `QuotaState` from observed OpenCode assistant turns.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
* This watcher is retained for compatibility. Product code now uses
|
|
9
|
+
* `zaiQuotaApi.ts` to call z.ai's authoritative quota endpoint. The legacy
|
|
10
|
+
* watcher maintains rolling 5-hour / 7-day windows in memory and recomputes a
|
|
11
|
+
* derived `QuotaState` whenever a new turn arrives. The consumer is
|
|
12
|
+
* responsible for feeding it z.ai-routed assistant turns (and any trapped
|
|
13
|
+
* error events) — typically by subscribing to `SessionMonitor.onSessionEvent`
|
|
14
|
+
* and filtering on `providerID ∈ {zai, zai-coding-plan}`.
|
|
14
15
|
*
|
|
15
16
|
* Optionally writes snapshots + history samples for cross-session cache
|
|
16
17
|
* and trend visualisation, mirroring `CodexQuotaWatcher`.
|
package/package.json
CHANGED