open-multi-agent-kit 0.94.0 → 0.95.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 (109) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +100 -646
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +11 -11
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/auth-storage.d.ts +27 -2
  7. package/dist/core/auth-storage.d.ts.map +1 -1
  8. package/dist/core/auth-storage.js +239 -56
  9. package/dist/core/auth-storage.js.map +1 -1
  10. package/dist/core/context-budget-v2-cache-invalidation.d.ts.map +1 -1
  11. package/dist/core/context-budget-v2-cache-invalidation.js +1 -1
  12. package/dist/core/context-budget-v2-cache-invalidation.js.map +1 -1
  13. package/dist/core/context-budget-v2-cache-provider.d.ts +5 -1
  14. package/dist/core/context-budget-v2-cache-provider.d.ts.map +1 -1
  15. package/dist/core/context-budget-v2-cache-provider.js +37 -7
  16. package/dist/core/context-budget-v2-cache-provider.js.map +1 -1
  17. package/dist/core/context-budget-v2-cache-telemetry.d.ts +1 -1
  18. package/dist/core/context-budget-v2-cache-telemetry.d.ts.map +1 -1
  19. package/dist/core/context-budget-v2-cache-telemetry.js +2 -1
  20. package/dist/core/context-budget-v2-cache-telemetry.js.map +1 -1
  21. package/dist/core/context-budget-v2-cache.d.ts.map +1 -1
  22. package/dist/core/context-budget-v2-cache.js +3 -0
  23. package/dist/core/context-budget-v2-cache.js.map +1 -1
  24. package/dist/core/context-budget-v2-representation-cache.d.ts.map +1 -1
  25. package/dist/core/context-budget-v2-representation-cache.js +4 -3
  26. package/dist/core/context-budget-v2-representation-cache.js.map +1 -1
  27. package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
  28. package/dist/core/context-budget-v2-selection.js +4 -0
  29. package/dist/core/context-budget-v2-selection.js.map +1 -1
  30. package/dist/core/context-budget-v2-types.d.ts +3 -0
  31. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  32. package/dist/core/context-budget-v2-types.js.map +1 -1
  33. package/dist/core/extensions/types.d.ts +2 -0
  34. package/dist/core/extensions/types.d.ts.map +1 -1
  35. package/dist/core/extensions/types.js.map +1 -1
  36. package/dist/core/github-repository.d.ts +3 -0
  37. package/dist/core/github-repository.d.ts.map +1 -0
  38. package/dist/core/github-repository.js +3 -0
  39. package/dist/core/github-repository.js.map +1 -0
  40. package/dist/core/model-registry.d.ts +3 -3
  41. package/dist/core/model-registry.d.ts.map +1 -1
  42. package/dist/core/model-registry.js +9 -8
  43. package/dist/core/model-registry.js.map +1 -1
  44. package/dist/core/provider-usage.d.ts +48 -0
  45. package/dist/core/provider-usage.d.ts.map +1 -0
  46. package/dist/core/provider-usage.js +780 -0
  47. package/dist/core/provider-usage.js.map +1 -0
  48. package/dist/core/sdk.d.ts.map +1 -1
  49. package/dist/core/sdk.js +10 -0
  50. package/dist/core/sdk.js.map +1 -1
  51. package/dist/core/session-termination.d.ts.map +1 -1
  52. package/dist/core/session-termination.js +8 -1
  53. package/dist/core/session-termination.js.map +1 -1
  54. package/dist/core/settings-manager.d.ts +1 -0
  55. package/dist/core/settings-manager.d.ts.map +1 -1
  56. package/dist/core/settings-manager.js +15 -8
  57. package/dist/core/settings-manager.js.map +1 -1
  58. package/dist/core/skills-catalog-cache.d.ts +1 -0
  59. package/dist/core/skills-catalog-cache.d.ts.map +1 -1
  60. package/dist/core/skills-catalog-cache.js +42 -11
  61. package/dist/core/skills-catalog-cache.js.map +1 -1
  62. package/dist/core/slash-commands.d.ts.map +1 -1
  63. package/dist/core/slash-commands.js +1 -0
  64. package/dist/core/slash-commands.js.map +1 -1
  65. package/dist/core/tools/read.d.ts +1 -1
  66. package/dist/core/tools/read.d.ts.map +1 -1
  67. package/dist/core/tools/read.js.map +1 -1
  68. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  69. package/dist/modes/interactive/components/footer.js.map +1 -1
  70. package/dist/modes/interactive/components/index.d.ts +1 -0
  71. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  72. package/dist/modes/interactive/components/index.js +1 -0
  73. package/dist/modes/interactive/components/index.js.map +1 -1
  74. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  75. package/dist/modes/interactive/components/model-selector.js +5 -3
  76. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  77. package/dist/modes/interactive/components/oauth-account-selector.d.ts +16 -0
  78. package/dist/modes/interactive/components/oauth-account-selector.d.ts.map +1 -0
  79. package/dist/modes/interactive/components/oauth-account-selector.js +61 -0
  80. package/dist/modes/interactive/components/oauth-account-selector.js.map +1 -0
  81. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  82. package/dist/modes/interactive/components/oauth-selector.js +25 -4
  83. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  84. package/dist/modes/interactive/components/status-sidebar.d.ts +21 -1
  85. package/dist/modes/interactive/components/status-sidebar.d.ts.map +1 -1
  86. package/dist/modes/interactive/components/status-sidebar.js +165 -6
  87. package/dist/modes/interactive/components/status-sidebar.js.map +1 -1
  88. package/dist/modes/interactive/interactive-mode.d.ts +2 -0
  89. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  90. package/dist/modes/interactive/interactive-mode.js +99 -15
  91. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  92. package/dist/utils/image-resize-core.d.ts.map +1 -1
  93. package/dist/utils/image-resize-core.js +2 -2
  94. package/dist/utils/image-resize-core.js.map +1 -1
  95. package/docs/development.md +3 -2
  96. package/docs/providers.md +19 -4
  97. package/docs/settings.md +9 -9
  98. package/docs/usage.md +10 -9
  99. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  100. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  101. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  102. package/examples/extensions/gondolin/package-lock.json +2 -2
  103. package/examples/extensions/gondolin/package.json +1 -1
  104. package/examples/extensions/sandbox/package-lock.json +2 -2
  105. package/examples/extensions/sandbox/package.json +1 -1
  106. package/examples/extensions/with-deps/package-lock.json +2 -2
  107. package/examples/extensions/with-deps/package.json +1 -1
  108. package/npm-shrinkwrap.json +24 -24
  109. package/package.json +6 -6
@@ -0,0 +1,780 @@
1
+ import { createHash } from "node:crypto";
2
+ const FIVE_HOUR_SECONDS = 5 * 60 * 60;
3
+ const SEVEN_DAY_SECONDS = 7 * 24 * 60 * 60;
4
+ const WINDOW_TOLERANCE_SECONDS = 120;
5
+ const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
6
+ const CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
7
+ const CLAUDE_MESSAGES_URL = "https://api.anthropic.com/v1/messages";
8
+ const KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
9
+ const OPENAI_AUTH_CLAIM = "https://api.openai.com/auth";
10
+ const MAX_USAGE_RESPONSE_BYTES = 1024 * 1024;
11
+ const MAX_USAGE_LIMITS = 64;
12
+ const MAX_PASSIVE_ACCOUNTS = 64;
13
+ const PASSIVE_USAGE_TTL_MS = 6 * 60 * 60 * 1000;
14
+ const CLAUDE_QUOTA_PROBE_COOLDOWN_MS = 60 * 60 * 1000;
15
+ const CLAUDE_QUOTA_PROBE_TIMEOUT_MS = 10_000;
16
+ const passiveCodexUsage = new Map();
17
+ const passiveClaudeUsage = new Map();
18
+ const claudeQuotaProbeAttempts = new Map();
19
+ const claudeQuotaProbesInFlight = new Map();
20
+ const subscriptionUsageRevisions = new Map();
21
+ const MINUTE_TTL_MS = 60_000;
22
+ const VISIBLE_USAGE_PROVIDERS = [
23
+ "openai-codex",
24
+ "anthropic",
25
+ "kimi-coding",
26
+ "zai",
27
+ "modelstudio-maas",
28
+ "qwen-oauth",
29
+ "grok-oauth-proxy",
30
+ ];
31
+ const SOURCES = {
32
+ "openai-codex": source("CODEX", "codex", [{ provider: "openai-codex", oauthOnly: true }]),
33
+ anthropic: source("CLAUDE", "claude", [{ provider: "anthropic", oauthOnly: true }], 5 * MINUTE_TTL_MS),
34
+ "qwen-oauth": source("QWEN", "unavailable", [{ provider: "qwen-oauth", oauthOnly: true }]),
35
+ "modelstudio-maas": source("QWEN TOKEN PLAN", "unavailable", [{ provider: "modelstudio-maas", oauthOnly: false }], MINUTE_TTL_MS, "console-only quota"),
36
+ "kimi-code": source("KIMI", "kimi", [
37
+ { provider: "kimi-code", oauthOnly: true },
38
+ { provider: "kimi-coding", oauthOnly: false },
39
+ ]),
40
+ "kimi-coding": source("KIMI", "kimi", [
41
+ { provider: "kimi-code", oauthOnly: true },
42
+ { provider: "kimi-coding", oauthOnly: false },
43
+ ]),
44
+ "zhipu-coding-plan": source("GLM", "zai", [{ provider: "zhipu-coding-plan", oauthOnly: true }]),
45
+ zai: source("GLM", "zai", [
46
+ { provider: "zai", oauthOnly: false },
47
+ { provider: "zai-coding-cn", oauthOnly: false },
48
+ { provider: "zhipu-coding-plan", oauthOnly: true },
49
+ ]),
50
+ "zai-coding-cn": source("GLM", "zai", [
51
+ { provider: "zai-coding-cn", oauthOnly: false },
52
+ { provider: "zhipu-coding-plan", oauthOnly: true },
53
+ ]),
54
+ "grok-oauth-proxy": source("GROK", "unavailable", [
55
+ { provider: "grok-oauth-proxy", oauthOnly: true },
56
+ { provider: "xai", oauthOnly: true },
57
+ ]),
58
+ xai: source("GROK", "unavailable", [{ provider: "xai", oauthOnly: true }]),
59
+ };
60
+ function source(label, kind, credentials, ttlMs = MINUTE_TTL_MS, unavailableMessage) {
61
+ return { label, kind, credentials, ttlMs, unavailableMessage };
62
+ }
63
+ export function getSubscriptionUsageSource(provider) {
64
+ if (!provider)
65
+ return undefined;
66
+ const configured = SOURCES[provider];
67
+ if (configured)
68
+ return configured;
69
+ if (/^openai-codex-\d+$/.test(provider)) {
70
+ return source("CODEX", "codex", [
71
+ { provider, oauthOnly: true },
72
+ { provider: "openai-codex", oauthOnly: true },
73
+ ]);
74
+ }
75
+ return undefined;
76
+ }
77
+ function usageRevisionKey(provider) {
78
+ const kind = getSubscriptionUsageSource(provider)?.kind;
79
+ if (kind === "codex")
80
+ return "codex";
81
+ if (kind === "claude")
82
+ return "claude";
83
+ return provider;
84
+ }
85
+ export function getSubscriptionUsageRevision(provider) {
86
+ return subscriptionUsageRevisions.get(usageRevisionKey(provider)) ?? 0;
87
+ }
88
+ function bumpSubscriptionUsageRevision(provider) {
89
+ const key = usageRevisionKey(provider);
90
+ subscriptionUsageRevisions.set(key, (subscriptionUsageRevisions.get(key) ?? 0) + 1);
91
+ }
92
+ export function supportsSubscriptionUsage(session, provider = session.state.model?.provider) {
93
+ const usageSource = getSubscriptionUsageSource(provider);
94
+ return usageSource?.credentials.some((candidate) => credentialConfigured(session, candidate)) ?? false;
95
+ }
96
+ /** Configured quota groups shown in the rail, with the active group first. */
97
+ export function getConfiguredSubscriptionUsageProviders(session) {
98
+ const activeProvider = session.state.model?.provider;
99
+ const activeSource = getSubscriptionUsageSource(activeProvider);
100
+ const providers = VISIBLE_USAGE_PROVIDERS.filter((provider) => supportsSubscriptionUsage(session, provider));
101
+ if (!activeProvider || !activeSource || !supportsSubscriptionUsage(session, activeProvider))
102
+ return providers;
103
+ const activeCanonical = providers.find((provider) => getSubscriptionUsageSource(provider)?.label === activeSource.label);
104
+ if (!activeCanonical)
105
+ return [activeProvider, ...providers];
106
+ return [activeCanonical, ...providers.filter((provider) => provider !== activeCanonical)];
107
+ }
108
+ export async function loadSubscriptionUsage(session, fetchImpl = fetch, provider = session.state.model?.provider) {
109
+ const model = session.state.model;
110
+ const usageSource = getSubscriptionUsageSource(provider);
111
+ if (!model || !usageSource || !supportsSubscriptionUsage(session, provider))
112
+ return undefined;
113
+ if (usageSource.kind === "unavailable") {
114
+ return {
115
+ label: usageSource.label,
116
+ windows: [],
117
+ message: usageSource.unavailableMessage ?? "quota API unavailable",
118
+ };
119
+ }
120
+ if (offline())
121
+ return { label: usageSource.label, windows: [], message: "offline" };
122
+ const credential = await resolveCredential(session, usageSource);
123
+ if (!credential)
124
+ return { label: usageSource.label, windows: [], message: "usage unavailable" };
125
+ try {
126
+ switch (usageSource.kind) {
127
+ case "codex":
128
+ return await fetchCodexUsage(usageSource.label, credential.apiKey, fetchImpl);
129
+ case "claude":
130
+ return await fetchClaudeUsage(usageSource.label, credential.apiKey, fetchImpl);
131
+ case "kimi":
132
+ return await fetchKimiUsage(usageSource.label, credential.apiKey, fetchImpl);
133
+ case "zai":
134
+ return await fetchZaiUsage(usageSource.label, provider ?? model.provider, credential, fetchImpl);
135
+ }
136
+ }
137
+ catch {
138
+ return { label: usageSource.label, windows: [], message: "usage unavailable" };
139
+ }
140
+ }
141
+ export function parseCodexUsageSnapshot(value, nowSeconds = Date.now() / 1000) {
142
+ const rateLimit = record(record(value)?.rate_limit);
143
+ const primary = codexWindow(rateLimit?.primary_window, nowSeconds);
144
+ const secondary = codexWindow(rateLimit?.secondary_window, nowSeconds);
145
+ const windows = [primary, secondary].filter((window) => window !== undefined);
146
+ const fiveHour = windows.find((window) => near(window.windowSeconds, FIVE_HOUR_SECONDS));
147
+ const sevenDay = windows.find((window) => near(window.windowSeconds, SEVEN_DAY_SECONDS));
148
+ const fallbackFiveHour = fiveHour ?? (primary?.windowSeconds === undefined ? primary : undefined);
149
+ const fallbackSevenDay = sevenDay ?? (secondary?.windowSeconds === undefined ? secondary : undefined);
150
+ if (!fallbackFiveHour && !fallbackSevenDay)
151
+ return undefined;
152
+ return {
153
+ ...(fallbackFiveHour ? { fiveHour: publicCodexWindow(fallbackFiveHour) } : {}),
154
+ ...(fallbackSevenDay ? { sevenDay: publicCodexWindow(fallbackSevenDay) } : {}),
155
+ };
156
+ }
157
+ export function parseClaudeUsageSnapshot(value) {
158
+ const payload = record(value);
159
+ if (!payload)
160
+ return undefined;
161
+ const entries = Array.isArray(payload.limits)
162
+ ? payload.limits.slice(0, MAX_USAGE_LIMITS).map(record).filter(isDefined)
163
+ : [];
164
+ const fiveHour = claudeWindow(payload.five_hour) ?? claudeWindow(entries.find((entry) => entry.kind === "session"));
165
+ const sevenDay = claudeWindow(payload.seven_day) ?? claudeWindow(entries.find((entry) => entry.kind === "weekly_all"));
166
+ const windows = [
167
+ fiveHour ? withLabel("5H", fiveHour) : undefined,
168
+ sevenDay ? withLabel("7D", sevenDay) : undefined,
169
+ ].filter(isDefined);
170
+ return windows.length > 0 ? windows : undefined;
171
+ }
172
+ export function parseKimiUsageSnapshot(value, nowSeconds = Date.now() / 1000) {
173
+ const payload = record(value);
174
+ if (!payload)
175
+ return undefined;
176
+ const windows = [];
177
+ const total = usageRatio(record(payload.usage), nowSeconds);
178
+ if (total)
179
+ windows.push(withLabel("TOTAL", total));
180
+ if (Array.isArray(payload.limits)) {
181
+ for (const rawLimit of payload.limits.slice(0, MAX_USAGE_LIMITS)) {
182
+ const limit = record(rawLimit);
183
+ if (!limit)
184
+ continue;
185
+ const detail = record(limit.detail) ?? limit;
186
+ const windowData = record(limit.window);
187
+ const parsed = usageRatio(detail, nowSeconds);
188
+ if (!parsed)
189
+ continue;
190
+ const resetsAt = parseReset(windowData, nowSeconds) ?? parsed.resetsAt;
191
+ windows.push({
192
+ label: durationLabel(windowData) ?? shortLabel(limit.name ?? limit.title ?? limit.scope, windows.length),
193
+ usedPercent: parsed.usedPercent,
194
+ ...(resetsAt === undefined ? {} : { resetsAt }),
195
+ });
196
+ }
197
+ }
198
+ return windows.length > 0 ? windows.slice(0, 4) : undefined;
199
+ }
200
+ export function parseZaiUsageSnapshot(value) {
201
+ const payload = record(value);
202
+ const limits = record(payload?.data)?.limits;
203
+ if (payload?.success !== true || !Array.isArray(limits))
204
+ return undefined;
205
+ const parsed = limits.slice(0, MAX_USAGE_LIMITS).map(zaiWindow).filter(isDefined);
206
+ const requestWindows = parsed.filter((window) => window.type === "TIME_LIMIT" && !window.featureOnly);
207
+ const selected = requestWindows.length > 0 ? requestWindows : parsed.filter((window) => window.type === "TOKENS_LIMIT");
208
+ selected.sort((left, right) => left.durationSeconds - right.durationSeconds);
209
+ const windows = selected
210
+ .slice(0, 4)
211
+ .map(({ type: _type, durationSeconds: _duration, featureOnly: _feature, ...window }) => window);
212
+ return windows.length > 0 ? windows : undefined;
213
+ }
214
+ export function recordCodexPassiveUsage(apiKey, snapshot, nowMs = Date.now()) {
215
+ const accountId = codexAccountId(apiKey);
216
+ const limitId = snapshot.limitId?.trim().toLowerCase().replace(/-/g, "_");
217
+ if (!accountId || !Number.isFinite(nowMs) || nowMs < 0 || (limitId && limitId !== "codex"))
218
+ return;
219
+ prunePassiveCodexUsage(nowMs);
220
+ const cacheKey = passiveCredentialCacheKey(accountId);
221
+ const primary = normalizePassiveCodexWindow(snapshot.primary, nowMs);
222
+ const secondary = normalizePassiveCodexWindow(snapshot.secondary, nowMs);
223
+ if (!primary && !secondary)
224
+ return;
225
+ const previous = passiveCodexUsage.get(cacheKey);
226
+ const next = {
227
+ primary: primary ? { window: primary, observedAt: nowMs } : freshObservedCodexWindow(previous?.primary, nowMs),
228
+ secondary: secondary
229
+ ? { window: secondary, observedAt: nowMs }
230
+ : freshObservedCodexWindow(previous?.secondary, nowMs),
231
+ };
232
+ passiveCodexUsage.delete(cacheKey);
233
+ passiveCodexUsage.set(cacheKey, next);
234
+ while (passiveCodexUsage.size > MAX_PASSIVE_ACCOUNTS) {
235
+ const oldestCacheKey = passiveCodexUsage.keys().next().value;
236
+ if (oldestCacheKey === undefined)
237
+ break;
238
+ passiveCodexUsage.delete(oldestCacheKey);
239
+ }
240
+ bumpSubscriptionUsageRevision("openai-codex");
241
+ }
242
+ export function recordClaudePassiveUsage(apiKey, snapshot, nowMs = Date.now()) {
243
+ const limitId = snapshot.limitId?.trim().toLowerCase().replace(/-/g, "_");
244
+ if (!apiKey ||
245
+ apiKey.length > 32_768 ||
246
+ !Number.isFinite(nowMs) ||
247
+ nowMs < 0 ||
248
+ (limitId && limitId !== "anthropic_unified")) {
249
+ return;
250
+ }
251
+ prunePassiveClaudeUsage(nowMs);
252
+ const cacheKey = passiveCredentialCacheKey(apiKey);
253
+ const primary = normalizePassiveCodexWindow(snapshot.primary, nowMs);
254
+ const secondary = normalizePassiveCodexWindow(snapshot.secondary, nowMs);
255
+ if (!primary && !secondary)
256
+ return;
257
+ const previous = passiveClaudeUsage.get(cacheKey);
258
+ const next = {
259
+ primary: primary ? { window: primary, observedAt: nowMs } : freshObservedCodexWindow(previous?.primary, nowMs),
260
+ secondary: secondary
261
+ ? { window: secondary, observedAt: nowMs }
262
+ : freshObservedCodexWindow(previous?.secondary, nowMs),
263
+ };
264
+ passiveClaudeUsage.delete(cacheKey);
265
+ passiveClaudeUsage.set(cacheKey, next);
266
+ while (passiveClaudeUsage.size > MAX_PASSIVE_ACCOUNTS) {
267
+ const oldestCacheKey = passiveClaudeUsage.keys().next().value;
268
+ if (oldestCacheKey === undefined)
269
+ break;
270
+ passiveClaudeUsage.delete(oldestCacheKey);
271
+ }
272
+ bumpSubscriptionUsageRevision("anthropic");
273
+ }
274
+ async function fetchCodexUsage(label, apiKey, fetchImpl) {
275
+ const accountId = codexAccountId(apiKey);
276
+ if (!accountId)
277
+ return { label, windows: [], message: "usage unavailable" };
278
+ const passive = passiveCodexSnapshot(apiKey);
279
+ let polled;
280
+ try {
281
+ const payload = await fetchJson(fetchImpl, CODEX_USAGE_URL, {
282
+ headers: {
283
+ Authorization: `Bearer ${apiKey}`,
284
+ Accept: "application/json",
285
+ "chatgpt-account-id": accountId,
286
+ "User-Agent": "omk",
287
+ },
288
+ });
289
+ polled = parseCodexUsageSnapshot(payload);
290
+ }
291
+ catch (error) {
292
+ if (!passive)
293
+ throw error;
294
+ }
295
+ const merged = {
296
+ fiveHour: polled?.fiveHour ?? passive?.fiveHour,
297
+ sevenDay: polled?.sevenDay ?? passive?.sevenDay,
298
+ };
299
+ if (!merged.fiveHour && !merged.sevenDay)
300
+ return { label, windows: [], message: "usage unavailable" };
301
+ return {
302
+ label,
303
+ windows: [
304
+ merged.fiveHour ? withLabel("5H", merged.fiveHour) : undefined,
305
+ merged.sevenDay ? withLabel("7D", merged.sevenDay) : undefined,
306
+ ].filter(isDefined),
307
+ };
308
+ }
309
+ async function fetchClaudeUsage(label, apiKey, fetchImpl) {
310
+ let passive = passiveClaudeSnapshot(apiKey);
311
+ const response = await fetchJsonResult(fetchImpl, CLAUDE_USAGE_URL, {
312
+ headers: {
313
+ Authorization: `Bearer ${apiKey}`,
314
+ Accept: "application/json",
315
+ "anthropic-beta": "claude-code-20250219,oauth-2025-04-20",
316
+ "User-Agent": "claude-cli/2.1.177 (external, cli)",
317
+ },
318
+ });
319
+ const windows = [...(parseClaudeUsageSnapshot(response.payload) ?? [])];
320
+ if (response.status === 429 && (!passive?.fiveHour || !passive?.sevenDay)) {
321
+ await probeClaudeQuota(apiKey, fetchImpl);
322
+ passive = passiveClaudeSnapshot(apiKey);
323
+ }
324
+ for (const passiveWindow of [
325
+ passive?.fiveHour ? withLabel("5H", passive.fiveHour) : undefined,
326
+ passive?.sevenDay ? withLabel("7D", passive.sevenDay) : undefined,
327
+ ].filter(isDefined)) {
328
+ if (!windows.some((window) => window.label === passiveWindow.label))
329
+ windows.push(passiveWindow);
330
+ }
331
+ return windows.length > 0
332
+ ? { label, windows }
333
+ : { label, windows: [], message: response.status === 429 ? "rate limited · retry later" : "usage unavailable" };
334
+ }
335
+ /**
336
+ * Claude Code 2.1.177 performs the same `quota`/`max_tokens: 1` request during startup.
337
+ * Keep it account-scoped and hourly so a pinned sidebar cannot turn the fallback into polling.
338
+ */
339
+ async function probeClaudeQuota(apiKey, fetchImpl, nowMs = Date.now()) {
340
+ if (!apiKey || apiKey.length > 32_768 || !Number.isFinite(nowMs) || nowMs < 0)
341
+ return;
342
+ const cacheKey = passiveCredentialCacheKey(apiKey);
343
+ const activeProbe = claudeQuotaProbesInFlight.get(cacheKey);
344
+ if (activeProbe)
345
+ return activeProbe;
346
+ const lastAttempt = claudeQuotaProbeAttempts.get(cacheKey);
347
+ if (lastAttempt !== undefined && nowMs - lastAttempt < CLAUDE_QUOTA_PROBE_COOLDOWN_MS)
348
+ return;
349
+ if (claudeQuotaProbesInFlight.size >= MAX_PASSIVE_ACCOUNTS)
350
+ return;
351
+ claudeQuotaProbeAttempts.delete(cacheKey);
352
+ claudeQuotaProbeAttempts.set(cacheKey, nowMs);
353
+ while (claudeQuotaProbeAttempts.size > MAX_PASSIVE_ACCOUNTS) {
354
+ const oldestCacheKey = claudeQuotaProbeAttempts.keys().next().value;
355
+ if (oldestCacheKey === undefined)
356
+ break;
357
+ claudeQuotaProbeAttempts.delete(oldestCacheKey);
358
+ }
359
+ const probe = (async () => {
360
+ try {
361
+ const response = await fetchImpl(CLAUDE_MESSAGES_URL, {
362
+ method: "POST",
363
+ headers: {
364
+ Authorization: `Bearer ${apiKey}`,
365
+ Accept: "application/json",
366
+ "Content-Type": "application/json",
367
+ "anthropic-version": "2023-06-01",
368
+ "anthropic-beta": "claude-code-20250219,oauth-2025-04-20",
369
+ "anthropic-dangerous-direct-browser-access": "true",
370
+ "User-Agent": "claude-cli/2.1.177 (external, cli)",
371
+ "x-app": "cli",
372
+ },
373
+ body: JSON.stringify({
374
+ model: "claude-haiku-4-5",
375
+ max_tokens: 1,
376
+ messages: [{ role: "user", content: "quota" }],
377
+ }),
378
+ signal: AbortSignal.timeout(CLAUDE_QUOTA_PROBE_TIMEOUT_MS),
379
+ });
380
+ const snapshot = parseClaudeQuotaProbeHeaders(response.headers, nowMs);
381
+ if (snapshot)
382
+ recordClaudePassiveUsage(apiKey, snapshot, nowMs);
383
+ try {
384
+ await response.body?.cancel();
385
+ }
386
+ catch {
387
+ // The quota snapshot lives entirely in response headers.
388
+ }
389
+ }
390
+ catch {
391
+ // This mirrors Claude Code's best-effort startup quota check.
392
+ }
393
+ })().finally(() => {
394
+ claudeQuotaProbesInFlight.delete(cacheKey);
395
+ });
396
+ claudeQuotaProbesInFlight.set(cacheKey, probe);
397
+ return probe;
398
+ }
399
+ function parseClaudeQuotaProbeHeaders(headers, nowMs) {
400
+ const parseWindow = (prefix, windowSeconds) => {
401
+ const rawUtilization = headers.get(`anthropic-ratelimit-unified-${prefix}-utilization`);
402
+ const rawReset = headers.get(`anthropic-ratelimit-unified-${prefix}-reset`);
403
+ if (rawUtilization === null || rawReset === null)
404
+ return undefined;
405
+ const utilization = Number(rawUtilization);
406
+ const resetsAt = Number(rawReset);
407
+ const maxResetAt = nowMs / 1000 + 10 * 365 * 24 * 60 * 60;
408
+ if (!Number.isFinite(utilization) ||
409
+ utilization < 0 ||
410
+ utilization > 1 ||
411
+ !Number.isSafeInteger(resetsAt) ||
412
+ resetsAt <= 0 ||
413
+ resetsAt > maxResetAt) {
414
+ return undefined;
415
+ }
416
+ return { usedPercent: Math.round(utilization * 10_000) / 100, windowSeconds, resetsAt };
417
+ };
418
+ const primary = parseWindow("5h", FIVE_HOUR_SECONDS);
419
+ const secondary = parseWindow("7d", SEVEN_DAY_SECONDS);
420
+ return primary || secondary ? { limitId: "anthropic-unified", primary, secondary } : undefined;
421
+ }
422
+ async function fetchKimiUsage(label, apiKey, fetchImpl) {
423
+ const payload = await fetchJson(fetchImpl, KIMI_USAGE_URL, {
424
+ headers: {
425
+ Authorization: `Bearer ${apiKey}`,
426
+ Accept: "application/json",
427
+ "User-Agent": "KimiCLI/1.5",
428
+ "X-Msh-Platform": "kimi_cli",
429
+ "X-Msh-Version": "1.5",
430
+ },
431
+ });
432
+ const windows = parseKimiUsageSnapshot(payload);
433
+ return windows ? { label, windows } : { label, windows: [], message: "usage unavailable" };
434
+ }
435
+ async function fetchZaiUsage(label, modelProvider, credential, fetchImpl) {
436
+ const origin = modelProvider === "zai-coding-cn" ||
437
+ credential.provider === "zai-coding-cn" ||
438
+ credential.provider === "zhipu-coding-plan"
439
+ ? "https://open.bigmodel.cn"
440
+ : "https://api.z.ai";
441
+ const payload = await fetchJson(fetchImpl, `${origin}/api/monitor/usage/quota/limit`, {
442
+ headers: {
443
+ Authorization: credential.apiKey,
444
+ Accept: "application/json",
445
+ "User-Agent": "omk",
446
+ },
447
+ });
448
+ const windows = parseZaiUsageSnapshot(payload);
449
+ return windows ? { label, windows } : { label, windows: [], message: "usage unavailable" };
450
+ }
451
+ async function fetchJson(fetchImpl, url, init) {
452
+ return (await fetchJsonResult(fetchImpl, url, init)).payload;
453
+ }
454
+ async function fetchJsonResult(fetchImpl, url, init) {
455
+ const response = await fetchImpl(url, { ...init, signal: AbortSignal.timeout(10_000) });
456
+ if (!response.ok)
457
+ return { status: response.status };
458
+ const declaredBytes = Number(response.headers?.get("content-length"));
459
+ if (Number.isFinite(declaredBytes) && declaredBytes > MAX_USAGE_RESPONSE_BYTES) {
460
+ return { status: response.status };
461
+ }
462
+ if (!response.body || typeof response.body.getReader !== "function") {
463
+ return { status: response.status, payload: await response.json() };
464
+ }
465
+ const reader = response.body.getReader();
466
+ const chunks = [];
467
+ let totalBytes = 0;
468
+ while (true) {
469
+ const { done, value } = await reader.read();
470
+ if (done)
471
+ break;
472
+ totalBytes += value.byteLength;
473
+ if (totalBytes > MAX_USAGE_RESPONSE_BYTES) {
474
+ await reader.cancel();
475
+ return { status: response.status };
476
+ }
477
+ chunks.push(value);
478
+ }
479
+ const body = new Uint8Array(totalBytes);
480
+ let offset = 0;
481
+ for (const chunk of chunks) {
482
+ body.set(chunk, offset);
483
+ offset += chunk.byteLength;
484
+ }
485
+ try {
486
+ return { status: response.status, payload: JSON.parse(new TextDecoder().decode(body)) };
487
+ }
488
+ catch {
489
+ return { status: response.status };
490
+ }
491
+ }
492
+ function credentialConfigured(session, candidate) {
493
+ if (candidate.oauthOnly)
494
+ return session.modelRegistry.isUsingOAuthProvider(candidate.provider);
495
+ const status = session.modelRegistry.getProviderAuthStatus(candidate.provider);
496
+ return status.configured || status.source !== undefined;
497
+ }
498
+ async function resolveCredential(session, usageSource) {
499
+ for (const candidate of usageSource.credentials) {
500
+ if (!credentialConfigured(session, candidate))
501
+ continue;
502
+ const apiKey = await session.modelRegistry.getApiKeyForProvider(candidate.provider);
503
+ if (apiKey)
504
+ return { provider: candidate.provider, apiKey };
505
+ }
506
+ return undefined;
507
+ }
508
+ function codexWindow(value, nowSeconds) {
509
+ const window = record(value);
510
+ const usedPercent = finiteNumber(window?.used_percent);
511
+ if (usedPercent === undefined)
512
+ return undefined;
513
+ const windowSeconds = finiteNumber(window?.limit_window_seconds);
514
+ const resetAt = finiteNumber(window?.reset_at);
515
+ const resetAfter = finiteNumber(window?.reset_after_seconds);
516
+ const resetsAt = resetAt ?? (resetAfter === undefined ? undefined : nowSeconds + resetAfter);
517
+ return {
518
+ usedPercent: clampPercent(usedPercent),
519
+ ...(windowSeconds === undefined ? {} : { windowSeconds }),
520
+ ...(resetsAt === undefined ? {} : { resetsAt }),
521
+ };
522
+ }
523
+ function claudeWindow(value) {
524
+ const bucket = record(value);
525
+ if (!bucket || bucket.is_active === false)
526
+ return undefined;
527
+ const usedPercent = finiteNumber(bucket.utilization ?? bucket.percent);
528
+ if (usedPercent === undefined)
529
+ return undefined;
530
+ const resetsAt = epochSeconds(bucket.resets_at);
531
+ return { usedPercent: clampPercent(usedPercent), ...(resetsAt === undefined ? {} : { resetsAt }) };
532
+ }
533
+ function usageRatio(data, nowSeconds) {
534
+ if (!data)
535
+ return undefined;
536
+ const limit = finiteNumber(data.limit);
537
+ let used = finiteNumber(data.used);
538
+ const remaining = finiteNumber(data.remaining);
539
+ if (used === undefined && limit !== undefined && remaining !== undefined)
540
+ used = limit - remaining;
541
+ if (used === undefined || limit === undefined || limit <= 0)
542
+ return undefined;
543
+ const resetsAt = parseReset(data, nowSeconds);
544
+ return {
545
+ usedPercent: clampPercent((used / limit) * 100),
546
+ ...(resetsAt === undefined ? {} : { resetsAt }),
547
+ };
548
+ }
549
+ function zaiWindow(value) {
550
+ const item = record(value);
551
+ if (!item || (item.type !== "TIME_LIMIT" && item.type !== "TOKENS_LIMIT"))
552
+ return undefined;
553
+ const percentage = finiteNumber(item.percentage);
554
+ const current = finiteNumber(item.currentValue);
555
+ const limit = finiteNumber(item.usage);
556
+ const usedPercent = percentage ?? (current !== undefined && limit !== undefined && limit > 0 ? (current / limit) * 100 : undefined);
557
+ if (usedPercent === undefined)
558
+ return undefined;
559
+ const duration = zaiDuration(finiteNumber(item.unit), finiteNumber(item.number));
560
+ const resetsAt = epochSeconds(item.nextResetTime);
561
+ const detailCodes = Array.isArray(item.usageDetails)
562
+ ? item.usageDetails
563
+ .map((detail) => record(detail)?.modelCode)
564
+ .filter((code) => typeof code === "string")
565
+ : [];
566
+ return {
567
+ type: item.type,
568
+ label: duration.label,
569
+ durationSeconds: duration.seconds,
570
+ featureOnly: ["search-prime", "web-reader", "zread"].every((code) => detailCodes.includes(code)),
571
+ usedPercent: clampPercent(usedPercent),
572
+ ...(resetsAt === undefined ? {} : { resetsAt }),
573
+ };
574
+ }
575
+ function zaiDuration(unit, countValue) {
576
+ const count = countValue && countValue > 0 ? countValue : 1;
577
+ if (unit === 3)
578
+ return { label: `${count}H`, seconds: count * 60 * 60 };
579
+ if (unit === 4)
580
+ return { label: `${count}D`, seconds: count * 24 * 60 * 60 };
581
+ if (unit === 5)
582
+ return { label: count === 1 ? "30D" : `${count}MO`, seconds: count * 30 * 24 * 60 * 60 };
583
+ if (unit === 6)
584
+ return { label: "7D", seconds: 7 * 24 * 60 * 60 };
585
+ return { label: "QUOTA", seconds: Number.POSITIVE_INFINITY };
586
+ }
587
+ function durationLabel(window) {
588
+ if (!window)
589
+ return undefined;
590
+ const duration = finiteNumber(window.duration);
591
+ const unit = typeof window.timeUnit === "string" ? window.timeUnit.toUpperCase() : "";
592
+ if (duration === undefined)
593
+ return undefined;
594
+ if (unit.includes("MINUTE"))
595
+ return duration % 60 === 0 ? `${duration / 60}H` : `${duration}M`;
596
+ if (unit.includes("HOUR"))
597
+ return `${duration}H`;
598
+ if (unit.includes("DAY"))
599
+ return `${duration}D`;
600
+ if (unit.includes("SECOND"))
601
+ return `${duration}S`;
602
+ return undefined;
603
+ }
604
+ function parseReset(data, nowSeconds) {
605
+ if (!data)
606
+ return undefined;
607
+ for (const key of ["reset_at", "resetAt", "reset_time", "resetTime"]) {
608
+ const parsed = epochSeconds(data[key]);
609
+ if (parsed !== undefined)
610
+ return parsed;
611
+ }
612
+ for (const key of ["reset_in", "resetIn", "ttl"]) {
613
+ const seconds = finiteNumber(data[key]);
614
+ if (seconds !== undefined)
615
+ return nowSeconds + seconds;
616
+ }
617
+ return undefined;
618
+ }
619
+ function epochSeconds(value) {
620
+ const numeric = finiteNumber(value);
621
+ if (numeric !== undefined)
622
+ return numeric > 1_000_000_000_000 ? numeric / 1000 : numeric;
623
+ if (typeof value !== "string" || !value.trim())
624
+ return undefined;
625
+ const parsed = Date.parse(value);
626
+ return Number.isFinite(parsed) ? parsed / 1000 : undefined;
627
+ }
628
+ function normalizePassiveCodexWindow(window, nowMs) {
629
+ if (!window || !Number.isFinite(window.usedPercent))
630
+ return undefined;
631
+ const windowSeconds = window.windowSeconds !== undefined &&
632
+ Number.isFinite(window.windowSeconds) &&
633
+ window.windowSeconds > 0 &&
634
+ window.windowSeconds <= Number.MAX_SAFE_INTEGER
635
+ ? Math.round(window.windowSeconds)
636
+ : undefined;
637
+ const maxResetAt = nowMs / 1000 + 10 * 365 * 24 * 60 * 60;
638
+ const resetsAtValue = window.resetsAt === undefined ? undefined : Math.floor(window.resetsAt);
639
+ const resetsAt = resetsAtValue !== undefined &&
640
+ Number.isSafeInteger(resetsAtValue) &&
641
+ resetsAtValue > 0 &&
642
+ resetsAtValue <= maxResetAt
643
+ ? resetsAtValue
644
+ : undefined;
645
+ return {
646
+ usedPercent: clampPercent(window.usedPercent),
647
+ ...(windowSeconds === undefined ? {} : { windowSeconds }),
648
+ ...(resetsAt === undefined ? {} : { resetsAt }),
649
+ };
650
+ }
651
+ function passiveCredentialCacheKey(identifier) {
652
+ return createHash("sha256").update(identifier).digest("base64url");
653
+ }
654
+ function freshObservedCodexWindow(observed, nowMs) {
655
+ if (!observed || nowMs - observed.observedAt > PASSIVE_USAGE_TTL_MS)
656
+ return undefined;
657
+ if (observed.window.resetsAt !== undefined && observed.window.resetsAt <= nowMs / 1000)
658
+ return undefined;
659
+ return observed;
660
+ }
661
+ function prunePassiveCodexUsage(nowMs) {
662
+ for (const [cacheKey, entry] of passiveCodexUsage) {
663
+ if (!freshObservedCodexWindow(entry.primary, nowMs) && !freshObservedCodexWindow(entry.secondary, nowMs)) {
664
+ passiveCodexUsage.delete(cacheKey);
665
+ }
666
+ }
667
+ }
668
+ function prunePassiveClaudeUsage(nowMs) {
669
+ for (const [cacheKey, entry] of passiveClaudeUsage) {
670
+ if (!freshObservedCodexWindow(entry.primary, nowMs) && !freshObservedCodexWindow(entry.secondary, nowMs)) {
671
+ passiveClaudeUsage.delete(cacheKey);
672
+ }
673
+ }
674
+ }
675
+ function passiveCodexSnapshot(apiKey, nowMs = Date.now()) {
676
+ const accountId = codexAccountId(apiKey);
677
+ if (!accountId || !Number.isFinite(nowMs) || nowMs < 0)
678
+ return undefined;
679
+ prunePassiveCodexUsage(nowMs);
680
+ const cacheKey = passiveCredentialCacheKey(accountId);
681
+ const entry = passiveCodexUsage.get(cacheKey);
682
+ if (!entry)
683
+ return undefined;
684
+ const primary = freshObservedCodexWindow(entry.primary, nowMs);
685
+ const secondary = freshObservedCodexWindow(entry.secondary, nowMs);
686
+ if (!primary && !secondary) {
687
+ passiveCodexUsage.delete(cacheKey);
688
+ return undefined;
689
+ }
690
+ const freshEntry = { primary, secondary };
691
+ passiveCodexUsage.delete(cacheKey);
692
+ passiveCodexUsage.set(cacheKey, freshEntry);
693
+ const windows = [primary?.window, secondary?.window].filter(isDefined);
694
+ const fiveHour = windows.find((window) => near(window.windowSeconds, FIVE_HOUR_SECONDS));
695
+ const sevenDay = windows.find((window) => near(window.windowSeconds, SEVEN_DAY_SECONDS));
696
+ if (!fiveHour && !sevenDay)
697
+ return undefined;
698
+ return {
699
+ ...(fiveHour ? { fiveHour: publicCodexWindow(fiveHour) } : {}),
700
+ ...(sevenDay ? { sevenDay: publicCodexWindow(sevenDay) } : {}),
701
+ };
702
+ }
703
+ function passiveClaudeSnapshot(apiKey, nowMs = Date.now()) {
704
+ if (!apiKey || apiKey.length > 32_768 || !Number.isFinite(nowMs) || nowMs < 0)
705
+ return undefined;
706
+ prunePassiveClaudeUsage(nowMs);
707
+ const cacheKey = passiveCredentialCacheKey(apiKey);
708
+ const entry = passiveClaudeUsage.get(cacheKey);
709
+ if (!entry)
710
+ return undefined;
711
+ const primary = freshObservedCodexWindow(entry.primary, nowMs);
712
+ const secondary = freshObservedCodexWindow(entry.secondary, nowMs);
713
+ if (!primary && !secondary) {
714
+ passiveClaudeUsage.delete(cacheKey);
715
+ return undefined;
716
+ }
717
+ passiveClaudeUsage.delete(cacheKey);
718
+ passiveClaudeUsage.set(cacheKey, { primary, secondary });
719
+ const windows = [primary?.window, secondary?.window].filter(isDefined);
720
+ const fiveHour = windows.find((window) => near(window.windowSeconds, FIVE_HOUR_SECONDS));
721
+ const sevenDay = windows.find((window) => near(window.windowSeconds, SEVEN_DAY_SECONDS));
722
+ if (!fiveHour && !sevenDay)
723
+ return undefined;
724
+ return {
725
+ ...(fiveHour ? { fiveHour: publicCodexWindow(fiveHour) } : {}),
726
+ ...(sevenDay ? { sevenDay: publicCodexWindow(sevenDay) } : {}),
727
+ };
728
+ }
729
+ function codexAccountId(token) {
730
+ try {
731
+ const payload = token.split(".")[1];
732
+ if (!payload)
733
+ return undefined;
734
+ const claims = record(JSON.parse(Buffer.from(payload, "base64url").toString("utf8")));
735
+ const auth = record(claims?.[OPENAI_AUTH_CLAIM]);
736
+ const accountId = typeof auth?.chatgpt_account_id === "string" ? auth.chatgpt_account_id.trim() : "";
737
+ return accountId && accountId.length <= 256 ? accountId : undefined;
738
+ }
739
+ catch {
740
+ return undefined;
741
+ }
742
+ }
743
+ function publicCodexWindow(window) {
744
+ return { usedPercent: window.usedPercent, ...(window.resetsAt === undefined ? {} : { resetsAt: window.resetsAt }) };
745
+ }
746
+ function withLabel(label, window) {
747
+ return { label, ...window };
748
+ }
749
+ function shortLabel(value, index) {
750
+ if (typeof value !== "string")
751
+ return `LIMIT${index + 1}`;
752
+ const safe = value
753
+ .replace(/[\u0000-\u001f\u007f-\u009f\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, "")
754
+ .replace(/\s+/g, " ")
755
+ .trim();
756
+ return safe ? safe.toUpperCase().slice(0, 8) : `LIMIT${index + 1}`;
757
+ }
758
+ function finiteNumber(value) {
759
+ const number = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
760
+ return Number.isFinite(number) ? number : undefined;
761
+ }
762
+ function clampPercent(value) {
763
+ return Number(Math.max(0, Math.min(100, value)).toFixed(2));
764
+ }
765
+ function near(actual, expected) {
766
+ return actual !== undefined && Math.abs(actual - expected) <= WINDOW_TOLERANCE_SECONDS;
767
+ }
768
+ function record(value) {
769
+ return typeof value === "object" && value !== null && !Array.isArray(value)
770
+ ? value
771
+ : undefined;
772
+ }
773
+ function isDefined(value) {
774
+ return value !== undefined;
775
+ }
776
+ function offline() {
777
+ const value = process.env.OMK_OFFLINE?.toLowerCase();
778
+ return value === "1" || value === "true" || value === "yes";
779
+ }
780
+ //# sourceMappingURL=provider-usage.js.map