@cortexkit/opencode-openai-auth 0.11.1 → 1.0.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.
- package/README.md +15 -26
- package/dist/index-766sfrh5.js +1 -0
- package/dist/index-8kj7sa1p.js +12 -0
- package/dist/index-8rqfbhfw.js +221 -0
- package/dist/index-b3q3j39x.js +2 -0
- package/dist/index-bmd7x5aq.js +2 -0
- package/dist/index-eqh53k8v.js +2 -0
- package/dist/{index-sdt2d2ra.js → index-hy9030jh.js} +1 -1
- package/dist/index-jyr8y8k4.js +7 -0
- package/dist/index-rv08jmd4.js +2 -0
- package/dist/index.d.ts +578 -145
- package/dist/index.js +1 -110
- package/dist/pool-lifecycle-DbDmgabd.d.ts +2430 -0
- package/dist/port-file-Do3lQIEk.d.ts +29 -0
- package/dist/protocol-BOBdkqqm.d.ts +198 -0
- package/dist/rpc/port-file.d.ts +4 -17
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +2 -2
- package/dist/rpc/protocol.js +1 -0
- package/dist/rpc/rpc-client.d.ts +5 -4
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.d.ts +2 -0
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/sidebar-state-D6cUKgkm.d.ts +385 -0
- package/dist/sidebar-state.d.ts +2 -2
- package/dist/sidebar-state.js +1 -1
- package/dist/token-util-wdza526f.js +1 -0
- package/dist/{token-9823y0pc.js → token-ypk61xj0.js} +1 -1
- package/dist/tui-preferences.js +5 -2
- package/dist/tui.d.ts +9 -4
- package/dist/v2/server.d.ts +54 -0
- package/dist/v2/server.js +4 -0
- package/package.json +21 -9
- package/src/tui/entry.mjs +26 -25
- package/src/tui-compiled/raw/logger.js +24 -0
- package/src/tui-compiled/raw/rpc/protocol.js +3 -0
- package/src/tui-compiled/raw/rpc/rpc-client.js +7 -0
- package/src/tui-compiled/raw/rpc/rpc-dir.js +27 -0
- package/src/tui-compiled/raw/selector.js +21 -0
- package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +138 -0
- package/src/tui-compiled/raw/shared/04a6357627de5ed0-errors.js +38 -0
- package/src/tui-compiled/raw/shared/053cc17b5419261e-codec.js +12 -0
- package/src/tui-compiled/raw/shared/0766d78c6f481f18-watcher.js +157 -0
- package/src/tui-compiled/raw/shared/08eef45626a94992-reserved.js +13 -0
- package/src/tui-compiled/raw/shared/39c3cb05738fcf6d-projection.js +134 -0
- package/src/tui-compiled/raw/shared/3c2d4169d00f907e-tui-support.js +5 -0
- package/src/tui-compiled/raw/shared/3e7d45a77c6ad4d5-index.js +4 -0
- package/src/tui-compiled/raw/shared/415792e85d80819e-capture-sink.js +13 -0
- package/src/tui-compiled/raw/shared/42ae93ab554fcec2-open-url.js +28 -0
- package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
- package/src/tui-compiled/raw/shared/458b934e92490331-mutate.js +500 -0
- package/src/tui-compiled/raw/shared/45bf5343bbbf9add-admission.js +142 -0
- package/src/tui-compiled/raw/shared/46f3dff1266160e8-client.js +2 -0
- package/src/tui-compiled/raw/shared/4825e03209d6ae3e-with-lock.js +48 -0
- package/src/tui-compiled/raw/shared/4f36d333d081d040-merge.js +162 -0
- package/src/tui-compiled/raw/shared/54eba94fd64c8c3c-index.js +7 -0
- package/src/tui-compiled/raw/shared/7211cfec06a298bc-atomic-write.js +80 -0
- package/src/tui-compiled/raw/shared/754d6017f8e419cf-builtins.js +534 -0
- package/src/tui-compiled/raw/shared/75bd8c3bd63b315f-identity.js +79 -0
- package/src/tui-compiled/raw/shared/7a91bb630e6f4804-index.js +1 -0
- package/src/tui-compiled/raw/shared/826342a4b6a8b89b-model.js +16 -0
- package/src/tui-compiled/raw/shared/83068c0dbe8a6f13-publication.js +304 -0
- package/src/tui-compiled/raw/shared/836fc615de6afbd9-settings.js +120 -0
- package/src/tui-compiled/raw/shared/83bc3ba9d0fe2761-runtime.js +68 -0
- package/src/tui-compiled/raw/shared/8a18abe4fa2977c8-menu.js +300 -0
- package/src/tui-compiled/raw/shared/901e149c5a0dbbff-sticky.js +339 -0
- package/src/tui-compiled/raw/shared/9020c407ce64f672-ordered.js +58 -0
- package/src/tui-compiled/raw/shared/905bffc0220f43d0-map.js +105 -0
- package/src/tui-compiled/raw/shared/94820e324f2b4c09-seam.js +301 -0
- package/src/tui-compiled/raw/shared/97484d9bb98c705b-provider-state.js +226 -0
- package/src/tui-compiled/raw/shared/9968e3cade6969a1-index.js +2 -0
- package/src/tui-compiled/raw/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
- package/src/tui-compiled/raw/shared/9d3fc9e37456504e-refresh.js +225 -0
- package/src/tui-compiled/raw/shared/9deae3c4da4fd63b-hooks.js +34 -0
- package/src/tui-compiled/raw/shared/a09b6a533c11858f-tui-preferences.js +77 -0
- package/src/tui-compiled/raw/shared/a62a27a204266acd-attribution.js +60 -0
- package/src/tui-compiled/raw/shared/a910277ac43a8626-pull.js +143 -0
- package/src/tui-compiled/raw/shared/b85c5301fddcb98a-paths.js +5 -0
- package/src/tui-compiled/raw/shared/c14988621dc60a75-port-file.js +217 -0
- package/src/tui-compiled/raw/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
- package/src/tui-compiled/raw/shared/c6b176708c446d6d-pins.js +34 -0
- package/src/tui-compiled/raw/shared/c7d252bedee01600-pi.js +206 -0
- package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +120 -0
- package/src/tui-compiled/raw/shared/cc9e0702a752cea7-index.js +3 -0
- package/src/tui-compiled/raw/shared/cd94316a888b0a8e-schema.js +754 -0
- package/src/tui-compiled/raw/shared/d319517e3ea6ad70-redact.js +83 -0
- package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +5 -0
- package/src/tui-compiled/raw/shared/d669d72c40e9e116-logger.js +19 -0
- package/src/tui-compiled/raw/shared/d7dcba96fea1685c-engine.js +246 -0
- package/src/tui-compiled/raw/shared/dbef697d1df1cd84-index.js +5 -0
- package/src/tui-compiled/raw/shared/e4acc4cb713f300c-protocol.js +3 -0
- package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
- package/src/tui-compiled/raw/shared/ec971142548df90e-index.js +6 -0
- package/src/tui-compiled/raw/shared/f37fcefc7aedbf25-error.js +11 -0
- package/src/tui-compiled/raw/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
- package/src/tui-compiled/raw/shared/fd45e82f2f9c0a0d-torn.js +294 -0
- package/src/tui-compiled/raw/shared/fe02abeadc535f77-rows.js +1258 -0
- package/src/tui-compiled/raw/sidebar-state.js +960 -0
- package/src/tui-compiled/raw/tui/command-dialogs.tsx +424 -0
- package/src/tui-compiled/raw/tui-preferences.js +135 -0
- package/src/tui-compiled/raw/tui.tsx +135 -143
- package/src/tui-compiled/runtime/logger.js +24 -0
- package/src/tui-compiled/runtime/rpc/protocol.js +3 -0
- package/src/tui-compiled/runtime/rpc/rpc-client.js +7 -0
- package/src/tui-compiled/runtime/rpc/rpc-dir.js +27 -0
- package/src/tui-compiled/runtime/selector.js +21 -0
- package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +138 -0
- package/src/tui-compiled/runtime/shared/04a6357627de5ed0-errors.js +38 -0
- package/src/tui-compiled/runtime/shared/053cc17b5419261e-codec.js +12 -0
- package/src/tui-compiled/runtime/shared/0766d78c6f481f18-watcher.js +157 -0
- package/src/tui-compiled/runtime/shared/08eef45626a94992-reserved.js +13 -0
- package/src/tui-compiled/runtime/shared/39c3cb05738fcf6d-projection.js +134 -0
- package/src/tui-compiled/runtime/shared/3c2d4169d00f907e-tui-support.js +5 -0
- package/src/tui-compiled/runtime/shared/3e7d45a77c6ad4d5-index.js +4 -0
- package/src/tui-compiled/runtime/shared/415792e85d80819e-capture-sink.js +13 -0
- package/src/tui-compiled/runtime/shared/42ae93ab554fcec2-open-url.js +28 -0
- package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
- package/src/tui-compiled/runtime/shared/458b934e92490331-mutate.js +500 -0
- package/src/tui-compiled/runtime/shared/45bf5343bbbf9add-admission.js +142 -0
- package/src/tui-compiled/runtime/shared/46f3dff1266160e8-client.js +2 -0
- package/src/tui-compiled/runtime/shared/4825e03209d6ae3e-with-lock.js +48 -0
- package/src/tui-compiled/runtime/shared/4f36d333d081d040-merge.js +162 -0
- package/src/tui-compiled/runtime/shared/54eba94fd64c8c3c-index.js +7 -0
- package/src/tui-compiled/runtime/shared/7211cfec06a298bc-atomic-write.js +80 -0
- package/src/tui-compiled/runtime/shared/754d6017f8e419cf-builtins.js +534 -0
- package/src/tui-compiled/runtime/shared/75bd8c3bd63b315f-identity.js +79 -0
- package/src/tui-compiled/runtime/shared/7a91bb630e6f4804-index.js +1 -0
- package/src/tui-compiled/runtime/shared/826342a4b6a8b89b-model.js +16 -0
- package/src/tui-compiled/runtime/shared/83068c0dbe8a6f13-publication.js +304 -0
- package/src/tui-compiled/runtime/shared/836fc615de6afbd9-settings.js +120 -0
- package/src/tui-compiled/runtime/shared/83bc3ba9d0fe2761-runtime.js +68 -0
- package/src/tui-compiled/runtime/shared/8a18abe4fa2977c8-menu.js +300 -0
- package/src/tui-compiled/runtime/shared/901e149c5a0dbbff-sticky.js +339 -0
- package/src/tui-compiled/runtime/shared/9020c407ce64f672-ordered.js +58 -0
- package/src/tui-compiled/runtime/shared/905bffc0220f43d0-map.js +105 -0
- package/src/tui-compiled/runtime/shared/94820e324f2b4c09-seam.js +301 -0
- package/src/tui-compiled/runtime/shared/97484d9bb98c705b-provider-state.js +226 -0
- package/src/tui-compiled/runtime/shared/9968e3cade6969a1-index.js +2 -0
- package/src/tui-compiled/runtime/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
- package/src/tui-compiled/runtime/shared/9d3fc9e37456504e-refresh.js +225 -0
- package/src/tui-compiled/runtime/shared/9deae3c4da4fd63b-hooks.js +34 -0
- package/src/tui-compiled/runtime/shared/a09b6a533c11858f-tui-preferences.js +77 -0
- package/src/tui-compiled/runtime/shared/a62a27a204266acd-attribution.js +60 -0
- package/src/tui-compiled/runtime/shared/a910277ac43a8626-pull.js +143 -0
- package/src/tui-compiled/runtime/shared/b85c5301fddcb98a-paths.js +5 -0
- package/src/tui-compiled/runtime/shared/c14988621dc60a75-port-file.js +217 -0
- package/src/tui-compiled/runtime/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
- package/src/tui-compiled/runtime/shared/c6b176708c446d6d-pins.js +34 -0
- package/src/tui-compiled/runtime/shared/c7d252bedee01600-pi.js +206 -0
- package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +120 -0
- package/src/tui-compiled/runtime/shared/cc9e0702a752cea7-index.js +3 -0
- package/src/tui-compiled/runtime/shared/cd94316a888b0a8e-schema.js +754 -0
- package/src/tui-compiled/runtime/shared/d319517e3ea6ad70-redact.js +83 -0
- package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +5 -0
- package/src/tui-compiled/runtime/shared/d669d72c40e9e116-logger.js +19 -0
- package/src/tui-compiled/runtime/shared/d7dcba96fea1685c-engine.js +246 -0
- package/src/tui-compiled/runtime/shared/dbef697d1df1cd84-index.js +5 -0
- package/src/tui-compiled/runtime/shared/e4acc4cb713f300c-protocol.js +3 -0
- package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
- package/src/tui-compiled/runtime/shared/ec971142548df90e-index.js +6 -0
- package/src/tui-compiled/runtime/shared/f37fcefc7aedbf25-error.js +11 -0
- package/src/tui-compiled/runtime/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
- package/src/tui-compiled/runtime/shared/fd45e82f2f9c0a0d-torn.js +294 -0
- package/src/tui-compiled/runtime/shared/fe02abeadc535f77-rows.js +1258 -0
- package/src/tui-compiled/runtime/sidebar-state.js +960 -0
- package/src/tui-compiled/runtime/tui/command-dialogs.js +378 -0
- package/src/tui-compiled/runtime/tui-preferences.js +135 -0
- package/src/tui-compiled/runtime/{tui.tsx → tui.js} +288 -316
- package/dist/index-4effjmr6.js +0 -2
- package/dist/index-9ehhb051.js +0 -2
- package/dist/index-g3hsdjh4.js +0 -2
- package/dist/index-hh52v8sb.js +0 -2
- package/dist/index-jwn4r3sw.js +0 -1
- package/dist/index-sq36b120.js +0 -211
- package/dist/internal-BTndPmW2.d.ts +0 -914
- package/dist/quota-normalize-08hpp51v.js +0 -1
- package/dist/sidebar-state-CoKYKcLn.d.ts +0 -220
- package/dist/token-util-ypgz7rge.js +0 -1
- package/src/tui-compiled/raw/core-account-paths.ts +0 -114
- package/src/tui-compiled/raw/core-custody-state.ts +0 -243
- package/src/tui-compiled/raw/logger.ts +0 -53
- package/src/tui-compiled/raw/rpc-port-file.ts +0 -177
- package/src/tui-compiled/raw/rpc-protocol.ts +0 -12
- package/src/tui-compiled/raw/rpc-rpc-client.ts +0 -82
- package/src/tui-compiled/raw/rpc-rpc-dir.ts +0 -46
- package/src/tui-compiled/raw/sidebar-state.ts +0 -1659
- package/src/tui-compiled/raw/tui-command-dialogs.tsx +0 -1096
- package/src/tui-compiled/raw/tui-preferences.ts +0 -328
- package/src/tui-compiled/runtime/core-account-paths.ts +0 -114
- package/src/tui-compiled/runtime/core-custody-state.ts +0 -243
- package/src/tui-compiled/runtime/logger.ts +0 -53
- package/src/tui-compiled/runtime/rpc-port-file.ts +0 -177
- package/src/tui-compiled/runtime/rpc-protocol.ts +0 -12
- package/src/tui-compiled/runtime/rpc-rpc-client.ts +0 -82
- package/src/tui-compiled/runtime/rpc-rpc-dir.ts +0 -46
- package/src/tui-compiled/runtime/sidebar-state.ts +0 -1659
- package/src/tui-compiled/runtime/tui-command-dialogs.tsx +0 -855
- package/src/tui-compiled/runtime/tui-preferences.ts +0 -328
- package/src/tui-compiled/shared/internal.ts +0 -6
- package/src/tui-compiled/shared/logger.ts +0 -264
- package/src/tui-compiled/shared/paths.ts +0 -33
- package/src/tui-compiled/shared/refresh-file-lock.ts +0 -384
- package/src/tui-compiled/shared/util/error.ts +0 -9
- package/src/tui-compiled/shared/util/open-url.ts +0 -30
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
2
|
|
|
3
|
-
import { readFileSync } from
|
|
4
|
-
import { dirname, join } from
|
|
5
|
-
import { fileURLToPath } from
|
|
3
|
+
import { readFileSync } from "node:fs"
|
|
4
|
+
import { dirname, join } from "node:path"
|
|
5
|
+
import { fileURLToPath } from "node:url"
|
|
6
|
+
import { formatQuota, quotaTextParts } from "./shared/d5a8f1061ee2f114-index.js"
|
|
6
7
|
import type {
|
|
7
8
|
TuiPlugin,
|
|
8
9
|
TuiPluginApi,
|
|
9
10
|
TuiPluginModule,
|
|
10
|
-
} from
|
|
11
|
+
} from "@opencode-ai/plugin/tui"
|
|
11
12
|
import {
|
|
12
13
|
createEffect,
|
|
13
14
|
createSignal,
|
|
@@ -15,11 +16,11 @@ import {
|
|
|
15
16
|
type JSX,
|
|
16
17
|
onCleanup,
|
|
17
18
|
Show,
|
|
18
|
-
} from
|
|
19
|
-
import { createLogger } from
|
|
20
|
-
import type
|
|
21
|
-
import { createRpcClient } from
|
|
22
|
-
import { resolveRpcDir } from
|
|
19
|
+
} from "solid-js"
|
|
20
|
+
import { createLogger } from "./logger.js"
|
|
21
|
+
import { type ApplyRequest, isNotifyPayload } from "./rpc/protocol.js"
|
|
22
|
+
import { createRpcClient } from "./rpc/rpc-client.js"
|
|
23
|
+
import { resolveRpcDir } from "./rpc/rpc-dir.js"
|
|
23
24
|
import {
|
|
24
25
|
type AccountQuota,
|
|
25
26
|
computeQuotaPacing,
|
|
@@ -34,8 +35,9 @@ import {
|
|
|
34
35
|
resolveSessionStickyAccount,
|
|
35
36
|
type SidebarState,
|
|
36
37
|
type SpendControlReading,
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
sidebarQuotaProjection,
|
|
39
|
+
} from "./sidebar-state.js"
|
|
40
|
+
import { openCommandDialog } from "./tui/command-dialogs.tsx"
|
|
39
41
|
import {
|
|
40
42
|
type AppearancePrefs,
|
|
41
43
|
computeEffectiveOrder,
|
|
@@ -46,7 +48,7 @@ import {
|
|
|
46
48
|
readTuiPreferencesFile,
|
|
47
49
|
resolveOpenaiAuthPrefs,
|
|
48
50
|
watchTuiPreferences,
|
|
49
|
-
} from
|
|
51
|
+
} from "./tui-preferences.js"
|
|
50
52
|
|
|
51
53
|
const RPC_POLL_MS = 500
|
|
52
54
|
let rpcPollStarted = false
|
|
@@ -54,14 +56,20 @@ const log = createLogger('rpc-tui')
|
|
|
54
56
|
|
|
55
57
|
const ID = 'cortexkit.openai-auth'
|
|
56
58
|
|
|
59
|
+
/** One drawer action as the RPC apply request carries it, with its session. */
|
|
57
60
|
export function buildApplyRequest(
|
|
58
|
-
|
|
59
|
-
arguments_: string,
|
|
61
|
+
request: Omit<ApplyRequest, 'sessionId'>,
|
|
60
62
|
sessionId?: string,
|
|
61
63
|
): ApplyRequest {
|
|
62
|
-
return {
|
|
64
|
+
return { ...request, ...(sessionId !== undefined ? { sessionId } : {}) }
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
/**
|
|
68
|
+
* How long the drawer waits for one action: a reset-credit redemption polls
|
|
69
|
+
* the provider and can take most of this.
|
|
70
|
+
*/
|
|
71
|
+
const MENU_APPLY_TIMEOUT_MS = 90_000
|
|
72
|
+
|
|
65
73
|
// Read package metadata from the development source or either generated TUI
|
|
66
74
|
// variant. Avoid a JSON import because package.json sits outside the declaration
|
|
67
75
|
// build's rootDir.
|
|
@@ -245,6 +253,7 @@ export interface QuotaDisplayRow {
|
|
|
245
253
|
key: 'primary' | 'secondary' | 'spendControl'
|
|
246
254
|
label: string
|
|
247
255
|
labelWidth: number
|
|
256
|
+
text: string
|
|
248
257
|
window: QuotaWindow
|
|
249
258
|
pacing: QuotaPacing | null
|
|
250
259
|
}
|
|
@@ -276,7 +285,16 @@ function quotaRowLabels(quota: AccountQuota | null): string[] {
|
|
|
276
285
|
export function renderedQuotas(state: {
|
|
277
286
|
main?: { quota?: AccountQuota | null } | null
|
|
278
287
|
fallbacks?: ReadonlyArray<{ enabled?: boolean; quota?: AccountQuota | null }>
|
|
288
|
+
vaultAccounts?: ReadonlyArray<{
|
|
289
|
+
enabled?: boolean
|
|
290
|
+
quota?: AccountQuota | null
|
|
291
|
+
}>
|
|
279
292
|
}): ReadonlyArray<AccountQuota | null | undefined> {
|
|
293
|
+
// In vault mode only the vault accounts are rendered.
|
|
294
|
+
if (state.vaultAccounts !== undefined)
|
|
295
|
+
return state.vaultAccounts
|
|
296
|
+
.filter((account) => account.enabled)
|
|
297
|
+
.map((account) => account.quota)
|
|
280
298
|
return [
|
|
281
299
|
state.main?.quota ?? null,
|
|
282
300
|
...(state.fallbacks ?? [])
|
|
@@ -307,11 +325,20 @@ export function buildQuotaRowsForDisplay(
|
|
|
307
325
|
pacingEnabled: boolean,
|
|
308
326
|
labelWidth?: number,
|
|
309
327
|
): QuotaDisplayRow[] {
|
|
328
|
+
const projection = sidebarQuotaProjection(quota, now)
|
|
310
329
|
const rows: Array<Omit<QuotaDisplayRow, 'labelWidth'>> =
|
|
311
330
|
getPresentQuotaWindows(quota).map((row) => ({
|
|
312
331
|
key: row.key,
|
|
313
332
|
label: row.label,
|
|
314
333
|
window: row.window,
|
|
334
|
+
text: formatQuota(
|
|
335
|
+
projection && {
|
|
336
|
+
...projection,
|
|
337
|
+
budget: undefined,
|
|
338
|
+
limits: projection.limits.filter((limit) => limit.label === row.key),
|
|
339
|
+
},
|
|
340
|
+
{ now, form: 'compact', staleAfterMs: Number.POSITIVE_INFINITY },
|
|
341
|
+
),
|
|
315
342
|
pacing:
|
|
316
343
|
pacingEnabled && row.windowMs !== null
|
|
317
344
|
? computeQuotaPacing(row.window, row.windowMs, now)
|
|
@@ -322,6 +349,11 @@ export function buildQuotaRowsForDisplay(
|
|
|
322
349
|
rows.push({
|
|
323
350
|
key: 'spendControl',
|
|
324
351
|
label: SPEND_CONTROL_LABEL,
|
|
352
|
+
text: formatQuota(projection && { ...projection, limits: [] }, {
|
|
353
|
+
now,
|
|
354
|
+
form: 'compact',
|
|
355
|
+
staleAfterMs: Number.POSITIVE_INFINITY,
|
|
356
|
+
}),
|
|
325
357
|
window: {
|
|
326
358
|
usedPercent: spendControl.usedPercent,
|
|
327
359
|
remainingPercent: spendControl.remainingPercent,
|
|
@@ -332,6 +364,14 @@ export function buildQuotaRowsForDisplay(
|
|
|
332
364
|
}
|
|
333
365
|
const width =
|
|
334
366
|
labelWidth ?? Math.max(3, ...rows.map((row) => row.label.length + 1))
|
|
367
|
+
const parts = quotaTextParts(projection, {
|
|
368
|
+
now,
|
|
369
|
+
form: 'compact',
|
|
370
|
+
staleAfterMs: Number.POSITIVE_INFINITY,
|
|
371
|
+
})
|
|
372
|
+
rows.sort(
|
|
373
|
+
(left, right) => parts.indexOf(left.text) - parts.indexOf(right.text),
|
|
374
|
+
)
|
|
335
375
|
return rows.map((row) => ({ ...row, labelWidth: width }))
|
|
336
376
|
}
|
|
337
377
|
|
|
@@ -385,8 +425,8 @@ export function getAccountMetadataRows(
|
|
|
385
425
|
return rows
|
|
386
426
|
}
|
|
387
427
|
|
|
388
|
-
// Quota window row: muted label left, tone-colored bar
|
|
389
|
-
//
|
|
428
|
+
// Quota window row: muted label left, tone-colored usage bar and shared quota text.
|
|
429
|
+
// When pacing data is present, the bar
|
|
390
430
|
// gains a pace segment and an off-pace window adds a muted subline with the
|
|
391
431
|
// reserve/deficit delta and the projected runout.
|
|
392
432
|
function QuotaRow(props: {
|
|
@@ -394,11 +434,11 @@ function QuotaRow(props: {
|
|
|
394
434
|
appearance: AppearancePrefs
|
|
395
435
|
label: string
|
|
396
436
|
labelWidth: number
|
|
437
|
+
text: string
|
|
397
438
|
window: { usedPercent: number; resetsAt?: string } | undefined
|
|
398
439
|
pacing: QuotaPacing | null
|
|
399
440
|
}) {
|
|
400
441
|
const used = () => props.window?.usedPercent ?? 0
|
|
401
|
-
const reset = () => formatResetIn(props.window?.resetsAt)
|
|
402
442
|
const paceLine = () => {
|
|
403
443
|
const pacing = props.pacing
|
|
404
444
|
if (!pacing || pacing.state === 'on-pace') return null
|
|
@@ -420,9 +460,7 @@ function QuotaRow(props: {
|
|
|
420
460
|
</box>
|
|
421
461
|
}
|
|
422
462
|
>
|
|
423
|
-
{/*
|
|
424
|
-
bars and percentages line up across rows; the reset time is pushed to
|
|
425
|
-
the right edge so reset times align in their own right column. */}
|
|
463
|
+
{/* Bars show usage; the adjacent figure shows quota left, as in the dialog. */}
|
|
426
464
|
<box width='100%' flexDirection='row' justifyContent='space-between'>
|
|
427
465
|
<box flexDirection='row'>
|
|
428
466
|
<text fg={props.theme.textMuted}>
|
|
@@ -438,12 +476,9 @@ function QuotaRow(props: {
|
|
|
438
476
|
<text
|
|
439
477
|
fg={toneColor(props.theme, usageTone(used(), props.appearance))}
|
|
440
478
|
>
|
|
441
|
-
{` ${
|
|
479
|
+
{` ${props.text.slice(props.label.length + 1)}`}
|
|
442
480
|
</text>
|
|
443
481
|
</box>
|
|
444
|
-
<Show when={reset()}>
|
|
445
|
-
<text fg={props.theme.textMuted}>{reset()}</text>
|
|
446
|
-
</Show>
|
|
447
482
|
</box>
|
|
448
483
|
<Show when={paceLine()}>
|
|
449
484
|
<box width='100%' flexDirection='row'>
|
|
@@ -513,6 +548,7 @@ function AccountBlock(props: {
|
|
|
513
548
|
appearance={props.appearance}
|
|
514
549
|
label={row.label}
|
|
515
550
|
labelWidth={row.labelWidth}
|
|
551
|
+
text={row.text}
|
|
516
552
|
window={row.window}
|
|
517
553
|
pacing={row.pacing}
|
|
518
554
|
/>
|
|
@@ -520,6 +556,14 @@ function AccountBlock(props: {
|
|
|
520
556
|
</For>
|
|
521
557
|
</Show>
|
|
522
558
|
</Show>
|
|
559
|
+
<For
|
|
560
|
+
each={quotaTextParts(sidebarQuotaProjection(props.quota, Date.now()), {
|
|
561
|
+
now: Date.now(),
|
|
562
|
+
form: 'compact',
|
|
563
|
+
}).filter((part) => part.startsWith('checked '))}
|
|
564
|
+
>
|
|
565
|
+
{(age) => <text fg={props.theme.textMuted}>{age}</text>}
|
|
566
|
+
</For>
|
|
523
567
|
<For
|
|
524
568
|
each={getAccountMetadataRows(
|
|
525
569
|
props.resetCredits,
|
|
@@ -539,77 +583,6 @@ function AccountBlock(props: {
|
|
|
539
583
|
)
|
|
540
584
|
}
|
|
541
585
|
|
|
542
|
-
// --- Quota dialog content ---------------------------------------------------
|
|
543
|
-
|
|
544
|
-
// Renders the same rich visualization as the sidebar (account blocks with
|
|
545
|
-
// quota bars + pacing) so the modal matches the sidebar look.
|
|
546
|
-
function QuotaDialogContent(props: {
|
|
547
|
-
api: TuiPluginApi
|
|
548
|
-
controller: SidebarController
|
|
549
|
-
sessionId: string | undefined
|
|
550
|
-
}) {
|
|
551
|
-
const prefs = props.controller.prefs
|
|
552
|
-
const [state, setState] = createSignal<SidebarState>(DEFAULT_SIDEBAR_STATE)
|
|
553
|
-
let lastUpdated = 0
|
|
554
|
-
async function refresh() {
|
|
555
|
-
const next = await readStateFromFile()
|
|
556
|
-
if (next.lastUpdated !== lastUpdated) {
|
|
557
|
-
lastUpdated = next.lastUpdated
|
|
558
|
-
setState(next)
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
createEffect(() => {
|
|
562
|
-
const timer = setInterval(refresh, prefs().pollMs)
|
|
563
|
-
onCleanup(() => clearInterval(timer))
|
|
564
|
-
})
|
|
565
|
-
setTimeout(refresh, 0)
|
|
566
|
-
const theme = () => props.api.theme.current
|
|
567
|
-
const enabledFallbacks = () =>
|
|
568
|
-
(state().fallbacks ?? []).filter((f) => f.enabled)
|
|
569
|
-
const activeId = () => resolveQuotaDialogActiveId(state(), props.sessionId)
|
|
570
|
-
const quotaLabelWidth = () => computeQuotaLabelWidth(renderedQuotas(state()))
|
|
571
|
-
return (
|
|
572
|
-
<box flexDirection='column' padding={2} width='100%' alignItems='center'>
|
|
573
|
-
<box flexDirection='column' width={58}>
|
|
574
|
-
<box width='100%' justifyContent='center' marginBottom={1}>
|
|
575
|
-
<text fg={theme().text}>
|
|
576
|
-
<b>Codex Quota</b>
|
|
577
|
-
</text>
|
|
578
|
-
</box>
|
|
579
|
-
<AccountBlock
|
|
580
|
-
theme={theme()}
|
|
581
|
-
appearance={prefs().appearance}
|
|
582
|
-
name='main'
|
|
583
|
-
quota={state().main?.quota ?? null}
|
|
584
|
-
killed={state().main?.killed ?? false}
|
|
585
|
-
active={activeId() === 'main'}
|
|
586
|
-
pacingEnabled={prefs().sections.pacing}
|
|
587
|
-
labelWidth={quotaLabelWidth()}
|
|
588
|
-
resetCredits={state().main?.resetCredits}
|
|
589
|
-
/>
|
|
590
|
-
<Show when={prefs().sections.fallbackAccounts}>
|
|
591
|
-
<For each={enabledFallbacks()}>
|
|
592
|
-
{(fb) => (
|
|
593
|
-
<AccountBlock
|
|
594
|
-
theme={theme()}
|
|
595
|
-
appearance={prefs().appearance}
|
|
596
|
-
name={fb.label ?? fb.id}
|
|
597
|
-
quota={fb.quota}
|
|
598
|
-
killed={fb.killed}
|
|
599
|
-
active={activeId() === fb.id}
|
|
600
|
-
pacingEnabled={prefs().sections.pacing}
|
|
601
|
-
labelWidth={quotaLabelWidth()}
|
|
602
|
-
resetCredits={fb.resetCredits}
|
|
603
|
-
marginTop={1}
|
|
604
|
-
/>
|
|
605
|
-
)}
|
|
606
|
-
</For>
|
|
607
|
-
</Show>
|
|
608
|
-
</box>
|
|
609
|
-
</box>
|
|
610
|
-
)
|
|
611
|
-
}
|
|
612
|
-
|
|
613
586
|
// --- State plumbing ---------------------------------------------------------
|
|
614
587
|
|
|
615
588
|
export async function readStateFromFile(): Promise<SidebarState> {
|
|
@@ -764,8 +737,15 @@ function QuotaSidebar(props: {
|
|
|
764
737
|
const theme = () => props.api.theme.current
|
|
765
738
|
const enabledFallbacks = () =>
|
|
766
739
|
(state().fallbacks ?? []).filter((f) => f.enabled)
|
|
740
|
+
// Vault mode: the host uses only the vault's accounts, which the file lists
|
|
741
|
+
// in `vaultAccounts`; main and the local fallbacks are not shown.
|
|
742
|
+
const vaultMode = () => state().vaultAccounts !== undefined
|
|
743
|
+
const enabledVaultAccounts = () =>
|
|
744
|
+
(state().vaultAccounts ?? []).filter((account) => account.enabled)
|
|
767
745
|
const hasData = () =>
|
|
768
|
-
|
|
746
|
+
vaultMode()
|
|
747
|
+
? enabledVaultAccounts().length > 0
|
|
748
|
+
: state().main?.quota != null || enabledFallbacks().length > 0
|
|
769
749
|
|
|
770
750
|
const headerLabel = () => {
|
|
771
751
|
const name = prefs().header.label
|
|
@@ -904,34 +884,56 @@ function QuotaSidebar(props: {
|
|
|
904
884
|
{/* Quota */}
|
|
905
885
|
<Show when={prefs().sections.quota}>
|
|
906
886
|
<SectionHeader theme={theme()} title='Quota' />
|
|
907
|
-
<
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
)}
|
|
934
|
-
|
|
887
|
+
<Show
|
|
888
|
+
when={!vaultMode()}
|
|
889
|
+
fallback={
|
|
890
|
+
<For each={enabledVaultAccounts()}>
|
|
891
|
+
{(account, index) => (
|
|
892
|
+
<AccountBlock
|
|
893
|
+
theme={theme()}
|
|
894
|
+
appearance={prefs().appearance}
|
|
895
|
+
name={account.label ?? account.id}
|
|
896
|
+
quota={account.quota}
|
|
897
|
+
killed={account.killed}
|
|
898
|
+
active={sessionRouting().activeId === account.id}
|
|
899
|
+
pacingEnabled={prefs().sections.pacing}
|
|
900
|
+
labelWidth={quotaLabelWidth()}
|
|
901
|
+
resetCredits={account.resetCredits}
|
|
902
|
+
marginTop={index() === 0 ? 0 : 1}
|
|
903
|
+
/>
|
|
904
|
+
)}
|
|
905
|
+
</For>
|
|
906
|
+
}
|
|
907
|
+
>
|
|
908
|
+
<AccountBlock
|
|
909
|
+
theme={theme()}
|
|
910
|
+
appearance={prefs().appearance}
|
|
911
|
+
name='main'
|
|
912
|
+
quota={state().main?.quota ?? null}
|
|
913
|
+
killed={state().main?.killed ?? false}
|
|
914
|
+
active={sessionRouting().activeId === 'main'}
|
|
915
|
+
pacingEnabled={prefs().sections.pacing}
|
|
916
|
+
labelWidth={quotaLabelWidth()}
|
|
917
|
+
resetCredits={state().main?.resetCredits}
|
|
918
|
+
/>
|
|
919
|
+
<Show when={prefs().sections.fallbackAccounts}>
|
|
920
|
+
<For each={enabledFallbacks()}>
|
|
921
|
+
{(fb) => (
|
|
922
|
+
<AccountBlock
|
|
923
|
+
theme={theme()}
|
|
924
|
+
appearance={prefs().appearance}
|
|
925
|
+
name={fb.label ?? fb.id}
|
|
926
|
+
quota={fb.quota}
|
|
927
|
+
killed={fb.killed}
|
|
928
|
+
active={sessionRouting().activeId === fb.id}
|
|
929
|
+
pacingEnabled={prefs().sections.pacing}
|
|
930
|
+
labelWidth={quotaLabelWidth()}
|
|
931
|
+
resetCredits={fb.resetCredits}
|
|
932
|
+
marginTop={1}
|
|
933
|
+
/>
|
|
934
|
+
)}
|
|
935
|
+
</For>
|
|
936
|
+
</Show>
|
|
935
937
|
</Show>
|
|
936
938
|
</Show>
|
|
937
939
|
</Show>
|
|
@@ -1041,26 +1043,16 @@ const tui: TuiPlugin = async (api) => {
|
|
|
1041
1043
|
.then((messages) => {
|
|
1042
1044
|
for (const message of [...messages].sort((a, b) => a.id - b.id)) {
|
|
1043
1045
|
lastNotificationId = Math.max(lastNotificationId, message.id)
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
api.ui.
|
|
1047
|
-
<QuotaDialogContent
|
|
1048
|
-
api={api}
|
|
1049
|
-
controller={controller}
|
|
1050
|
-
sessionId={sessionId}
|
|
1051
|
-
/>
|
|
1052
|
-
))
|
|
1046
|
+
const payload = message.payload
|
|
1047
|
+
if (isNotifyPayload(payload)) {
|
|
1048
|
+
api.ui.toast({ message: payload.notify.message })
|
|
1053
1049
|
continue
|
|
1054
1050
|
}
|
|
1055
|
-
openCommandDialog(
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
buildApplyRequest(command, args, sessionId),
|
|
1061
|
-
command === 'openai-reset' ? 90_000 : undefined,
|
|
1062
|
-
),
|
|
1063
|
-
sessionId,
|
|
1051
|
+
openCommandDialog(api, payload, (request) =>
|
|
1052
|
+
rpcClient.apply(
|
|
1053
|
+
buildApplyRequest(request, sessionId),
|
|
1054
|
+
MENU_APPLY_TIMEOUT_MS,
|
|
1055
|
+
),
|
|
1064
1056
|
)
|
|
1065
1057
|
}
|
|
1066
1058
|
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { tmpdir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
flushLogs,
|
|
5
|
+
initLogger
|
|
6
|
+
} from "./shared/3c2d4169d00f907e-tui-support.js";
|
|
7
|
+
export {
|
|
8
|
+
createLogger,
|
|
9
|
+
flushForTest,
|
|
10
|
+
flushLogs,
|
|
11
|
+
redact,
|
|
12
|
+
redactStrings,
|
|
13
|
+
setLogLevel
|
|
14
|
+
} from "./shared/3c2d4169d00f907e-tui-support.js";
|
|
15
|
+
const LEVELS = ["error", "warn", "info", "debug", "trace"];
|
|
16
|
+
export function getLogFile() {
|
|
17
|
+
return process.env.OPENCODE_OPENAI_AUTH_LOG_FILE ?? join(tmpdir(), "opencode-openai-auth.log");
|
|
18
|
+
}
|
|
19
|
+
export function getEnvLogLevel() {
|
|
20
|
+
const env = process.env.OPENCODE_OPENAI_AUTH_LOG_LEVEL;
|
|
21
|
+
return env && LEVELS.includes(env) ? env : void 0;
|
|
22
|
+
}
|
|
23
|
+
initLogger({ file: getLogFile, level: getEnvLogLevel });
|
|
24
|
+
process.on("exit", flushLogs);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRpcClient as createCommonRpcClient
|
|
3
|
+
} from "../shared/46f3dff1266160e8-client.js";
|
|
4
|
+
export { DEFAULT_RPC_TIMEOUT_MS } from "../shared/46f3dff1266160e8-client.js";
|
|
5
|
+
export function createRpcClient(dir, expectedPid, onSelected) {
|
|
6
|
+
return createCommonRpcClient(dir, expectedPid, onSelected);
|
|
7
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { homedir, tmpdir } from "node:os";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
const RPC_DIR_ENV = "OPENCODE_OPENAI_AUTH_RPC_DIR";
|
|
5
|
+
export const RPC_DIRECTORY_PREFIX = "openai-auth-";
|
|
6
|
+
function rpcHash(projectDirectory) {
|
|
7
|
+
return createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
8
|
+
}
|
|
9
|
+
function defaultRpcRoot() {
|
|
10
|
+
const base = process.env.XDG_STATE_HOME || join(homedir(), ".local", "state");
|
|
11
|
+
return join(base, "cortexkit", "openai-auth", "rpc");
|
|
12
|
+
}
|
|
13
|
+
export function getRpcDir(projectDirectory) {
|
|
14
|
+
const override = process.env[RPC_DIR_ENV]?.trim();
|
|
15
|
+
if (override)
|
|
16
|
+
return resolve(projectDirectory, override);
|
|
17
|
+
return join(defaultRpcRoot(), `${RPC_DIRECTORY_PREFIX}${rpcHash(projectDirectory)}`);
|
|
18
|
+
}
|
|
19
|
+
export async function resolveRpcDir(projectDirectory) {
|
|
20
|
+
const override = process.env[RPC_DIR_ENV]?.trim();
|
|
21
|
+
if (override)
|
|
22
|
+
return { dir: resolve(projectDirectory, override), secureDir: !1 };
|
|
23
|
+
const dir = getRpcDir(projectDirectory);
|
|
24
|
+
return { dir, secureDir: !0, sweepRoot: dirname(dir) };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { tmpdir };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { isAbsolute } from 'node:path';
|
|
2
|
+
export async function loadTui({ rawEntry, runtimeEntry, importModule = (entry) => import(entry), }) {
|
|
3
|
+
for (const [option, entry] of Object.entries({ rawEntry, runtimeEntry })) {
|
|
4
|
+
if (!isAbsolute(entry) &&
|
|
5
|
+
!(entry.startsWith('file:') && new URL(entry).pathname.startsWith('/'))) {
|
|
6
|
+
throw new Error(`${option} must be an absolute path or file URL: ${entry}`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
let entry = runtimeEntry;
|
|
10
|
+
try {
|
|
11
|
+
await importModule(`opentui:runtime-module:${encodeURIComponent('@opentui/solid')}`);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
15
|
+
if (!/Cannot find|Could not resolve|Module not found|Unable to resolve/.test(message) ||
|
|
16
|
+
!message.includes('opentui:runtime-module:'))
|
|
17
|
+
throw error;
|
|
18
|
+
entry = rawEntry;
|
|
19
|
+
}
|
|
20
|
+
return (await importModule(entry)).default;
|
|
21
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// The one quota wording every surface shows: the command menu's dialogs on
|
|
2
|
+
// OpenCode and Pi, the terminal auth menu, and the plugins' sidebars. Each of
|
|
3
|
+
// them calls `formatQuota` (or `quotaTextParts` for one line per window), so
|
|
4
|
+
// the wording cannot drift between plugins or between a dialog and a sidebar.
|
|
5
|
+
//
|
|
6
|
+
// The rules: a window is named by its length (`5h`, `7d`), or, for a window
|
|
7
|
+
// that binds only one model family, by the label the plugin stored it under.
|
|
8
|
+
// Every figure is "% left". A window the provider did not report (an absence
|
|
9
|
+
// record or a retirement tombstone) is left out rather than written as "not
|
|
10
|
+
// reported". A reset is a short time from now (`resets 2h`). A reading older
|
|
11
|
+
// than fifteen minutes says how old it is (`checked 3h ago`).
|
|
12
|
+
//
|
|
13
|
+
// Like the rest of this module it reads no clock: `now` is a parameter.
|
|
14
|
+
import { ALL_SCOPE } from "./905bffc0220f43d0-map.js";
|
|
15
|
+
/** A reading older than this is marked with its age. */
|
|
16
|
+
export const QUOTA_STALE_AFTER_MS = 15 * 60_000;
|
|
17
|
+
/** What `formatQuota` returns when there is no reading at all. */
|
|
18
|
+
export const NO_QUOTA_READING = 'no quota reading yet';
|
|
19
|
+
/**
|
|
20
|
+
* What `formatQuota` returns when the provider answered but reported no
|
|
21
|
+
* limit for any window (every window is an absence record or a tombstone).
|
|
22
|
+
*/
|
|
23
|
+
export const NO_LIMITS_REPORTED = 'no limits reported';
|
|
24
|
+
/**
|
|
25
|
+
* A length or a time span in its largest whole-ish unit: `45m`, `5h`, `3d`.
|
|
26
|
+
* Spans under an hour are minutes, under a day hours, otherwise days, each
|
|
27
|
+
* rounded to the nearest unit and never shown as zero.
|
|
28
|
+
*/
|
|
29
|
+
function shortSpan(ms) {
|
|
30
|
+
const minutes = Math.max(1, Math.round(ms / 60_000));
|
|
31
|
+
if (minutes < 60)
|
|
32
|
+
return `${minutes}m`;
|
|
33
|
+
const hours = Math.round(minutes / 60);
|
|
34
|
+
if (hours < 24)
|
|
35
|
+
return `${hours}h`;
|
|
36
|
+
return `${Math.max(1, Math.round(minutes / 1440))}d`;
|
|
37
|
+
}
|
|
38
|
+
/** A stored window length in minutes as its exact name: `5h`, `7d`, `90m`. */
|
|
39
|
+
function lengthName(minutes) {
|
|
40
|
+
if (minutes % 1440 === 0)
|
|
41
|
+
return `${minutes / 1440}d`;
|
|
42
|
+
if (minutes % 60 === 0)
|
|
43
|
+
return `${minutes / 60}h`;
|
|
44
|
+
return `${minutes}m`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The name a window is shown under. A general window (scope `all`) is named
|
|
48
|
+
* by its stored length; a model-scoped window keeps the label the plugin
|
|
49
|
+
* stored it under, which already names the model (`seven_day_opus`). A
|
|
50
|
+
* general window stored without a length (a snapshot from before lengths
|
|
51
|
+
* were recorded) falls back to its label.
|
|
52
|
+
*/
|
|
53
|
+
export function quotaWindowName(limit) {
|
|
54
|
+
if (limit.scope !== ALL_SCOPE || limit.windowMinutes === undefined)
|
|
55
|
+
return limit.label;
|
|
56
|
+
return lengthName(limit.windowMinutes);
|
|
57
|
+
}
|
|
58
|
+
function percentLeft(remaining) {
|
|
59
|
+
return `${Math.round(Math.min(100, Math.max(0, remaining)))}% left`;
|
|
60
|
+
}
|
|
61
|
+
function resetText(resetsAt, now) {
|
|
62
|
+
if (resetsAt === undefined)
|
|
63
|
+
return '';
|
|
64
|
+
const at = Date.parse(resetsAt);
|
|
65
|
+
// A reset already passed says nothing about the future; leave it out.
|
|
66
|
+
if (!Number.isFinite(at) || at <= now)
|
|
67
|
+
return '';
|
|
68
|
+
return `, resets ${shortSpan(at - now)}`;
|
|
69
|
+
}
|
|
70
|
+
/** Shortest window first, so `5h` reads before `7d`; unknown lengths last. */
|
|
71
|
+
function displayOrder(left, right) {
|
|
72
|
+
const l = left.windowMinutes;
|
|
73
|
+
const r = right.windowMinutes;
|
|
74
|
+
if (l !== undefined && r !== undefined && l !== r)
|
|
75
|
+
return l - r;
|
|
76
|
+
if ((l === undefined) !== (r === undefined))
|
|
77
|
+
return l === undefined ? 1 : -1;
|
|
78
|
+
if (left.scope !== right.scope) {
|
|
79
|
+
if (left.scope === ALL_SCOPE)
|
|
80
|
+
return -1;
|
|
81
|
+
if (right.scope === ALL_SCOPE)
|
|
82
|
+
return 1;
|
|
83
|
+
return left.scope < right.scope ? -1 : 1;
|
|
84
|
+
}
|
|
85
|
+
return left.label < right.label ? -1 : left.label > right.label ? 1 : 0;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The pieces of a quota text, in order: one per reported window
|
|
89
|
+
* (`5h 58% left, resets 2h`), then the credit budget when there is one, then
|
|
90
|
+
* `checked 3h ago` when the oldest piece shown is stale. Empty when nothing
|
|
91
|
+
* was reported; `formatQuota` turns that into a sentence. A caller printing
|
|
92
|
+
* one line per window (the terminal auth menu) uses these directly.
|
|
93
|
+
*/
|
|
94
|
+
export function quotaTextParts(quota, options) {
|
|
95
|
+
if (!quota)
|
|
96
|
+
return [];
|
|
97
|
+
const { now } = options;
|
|
98
|
+
const full = (options.form ?? 'full') === 'full';
|
|
99
|
+
const parts = [];
|
|
100
|
+
const times = [];
|
|
101
|
+
const readings = quota.limits
|
|
102
|
+
.filter((limit) => limit.kind === 'reading' && limit.remainingPercent !== undefined)
|
|
103
|
+
.sort(displayOrder);
|
|
104
|
+
for (const limit of readings) {
|
|
105
|
+
times.push(limit.checkedAt);
|
|
106
|
+
parts.push(`${quotaWindowName(limit)} ${percentLeft(limit.remainingPercent ?? 0)}${full ? resetText(limit.resetsAt, now) : ''}`);
|
|
107
|
+
}
|
|
108
|
+
const budget = quota.budget;
|
|
109
|
+
if (budget) {
|
|
110
|
+
times.push(budget.checkedAt);
|
|
111
|
+
const figure = budget.reached
|
|
112
|
+
? 'credits spent'
|
|
113
|
+
: budget.remainingPercent !== undefined
|
|
114
|
+
? `credits ${percentLeft(budget.remainingPercent)}`
|
|
115
|
+
: 'credits available';
|
|
116
|
+
parts.push(`${figure}${full ? resetText(budget.resetsAt, now) : ''}`);
|
|
117
|
+
}
|
|
118
|
+
if (times.length > 0) {
|
|
119
|
+
const age = now - Math.min(...times);
|
|
120
|
+
if (age > (options.staleAfterMs ?? QUOTA_STALE_AFTER_MS))
|
|
121
|
+
parts.push(`checked ${shortSpan(age)} ago`);
|
|
122
|
+
}
|
|
123
|
+
return parts;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* A row's quota as one line: `5h 58% left · 7d 90% left` in the compact
|
|
127
|
+
* form, with `, resets 2h` after each window in the full form. With nothing
|
|
128
|
+
* reported it is `NO_QUOTA_READING`, or `NO_LIMITS_REPORTED` when the
|
|
129
|
+
* provider answered without a limit for any window.
|
|
130
|
+
*/
|
|
131
|
+
export function formatQuota(quota, options) {
|
|
132
|
+
const parts = quotaTextParts(quota, options);
|
|
133
|
+
if (parts.length > 0)
|
|
134
|
+
return parts.join(' · ');
|
|
135
|
+
return quota && quota.limits.length > 0
|
|
136
|
+
? NO_LIMITS_REPORTED
|
|
137
|
+
: NO_QUOTA_READING;
|
|
138
|
+
}
|