@maci0/dsh-quota-check 0.12.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/LICENSE +21 -0
- package/README.md +220 -0
- package/cordis.patch.yml +13 -0
- package/icon.svg +5 -0
- package/lib/client.js +471 -0
- package/lib/host.js +12 -0
- package/lib/index.js +469 -0
- package/lib/local-usage.js +453 -0
- package/lib/probes.js +848 -0
- package/lib/types/host.d.ts +128 -0
- package/lib/types/index.d.ts +113 -0
- package/lib/types/local-usage.d.ts +45 -0
- package/lib/types/probes.d.ts +126 -0
- package/lib/types/util.d.ts +14 -0
- package/lib/util.js +28 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +104 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The slice of the DeepSeek Harness host surface this plugin uses, declared
|
|
3
|
+
* structurally.
|
|
4
|
+
*
|
|
5
|
+
* The plugin installs from outside the harness checkout and has no runtime
|
|
6
|
+
* dependency on harness packages: the services it reaches are typed here, and
|
|
7
|
+
* a composition that mounts none of them simply omits that capability. The
|
|
8
|
+
* same shape also lets the unit tests drive `apply` with a plain fake context.
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-quota-check/host
|
|
11
|
+
*/
|
|
12
|
+
/** Disposer returned by every host registration. */
|
|
13
|
+
export type Disposable = () => void;
|
|
14
|
+
/** One registered settings namespace, as `describe()` reports it. */
|
|
15
|
+
export interface SettingsDescriptorLike {
|
|
16
|
+
/** Registered namespace name, e.g. `llm-pi-ai`. */
|
|
17
|
+
readonly ns: string;
|
|
18
|
+
/** Current resolved value: schema defaults, then base, then the user layer. */
|
|
19
|
+
readonly value: unknown;
|
|
20
|
+
}
|
|
21
|
+
/** One configurable provider route, as the LLM registry declares it. */
|
|
22
|
+
export interface ConfigurableProviderLike {
|
|
23
|
+
/** Route id the model picker shows, e.g. `zai`. */
|
|
24
|
+
readonly provider: string;
|
|
25
|
+
/** Display name for the provider, when it declares one. */
|
|
26
|
+
readonly displayName?: string;
|
|
27
|
+
/** Settings namespace holding this route's profile. */
|
|
28
|
+
readonly settingsNs: string;
|
|
29
|
+
/** Path inside that namespace's value to this route's profile. */
|
|
30
|
+
readonly settingsPath: readonly string[];
|
|
31
|
+
}
|
|
32
|
+
/** The slice of the LLM registry this plugin reads. */
|
|
33
|
+
export interface LlmRegistryLike {
|
|
34
|
+
/**
|
|
35
|
+
* List every declared configurable provider, registered or dormant.
|
|
36
|
+
* @returns detached directory entries in declaration order.
|
|
37
|
+
*/
|
|
38
|
+
listConfigurableProviders(): readonly ConfigurableProviderLike[];
|
|
39
|
+
}
|
|
40
|
+
/** The slice of the credentials service this plugin reads. */
|
|
41
|
+
export interface CredentialsLike {
|
|
42
|
+
/**
|
|
43
|
+
* Resolve one credential reference to its value.
|
|
44
|
+
* @param ref - environment-variable-style reference name.
|
|
45
|
+
* @returns the resolved value, or `undefined` when nothing is stored.
|
|
46
|
+
*/
|
|
47
|
+
resolve(ref: string): Promise<{
|
|
48
|
+
readonly value: string;
|
|
49
|
+
} | undefined>;
|
|
50
|
+
}
|
|
51
|
+
/** Request subset the trust fence reads. */
|
|
52
|
+
export interface RequestLike {
|
|
53
|
+
/** Request method, uppercased by the caller. */
|
|
54
|
+
readonly method?: string | undefined;
|
|
55
|
+
/** Request target, including the query string. */
|
|
56
|
+
readonly url?: string | undefined;
|
|
57
|
+
/** Request headers, read by the composition's trust fence. */
|
|
58
|
+
readonly headers: object | undefined;
|
|
59
|
+
}
|
|
60
|
+
/** Response subset this plugin writes. */
|
|
61
|
+
export interface ResponseLike {
|
|
62
|
+
/** HTTP status code. */
|
|
63
|
+
statusCode: number;
|
|
64
|
+
/** Set one response header. */
|
|
65
|
+
setHeader(name: string, value: string): void;
|
|
66
|
+
/** End the response, optionally with a body. */
|
|
67
|
+
end(body?: string): void;
|
|
68
|
+
}
|
|
69
|
+
/** One exact-path route registration. */
|
|
70
|
+
export interface WebRouteLike {
|
|
71
|
+
/** Match kind; this plugin registers only exact paths. */
|
|
72
|
+
readonly kind: 'exact';
|
|
73
|
+
/** Absolute pathname, no trailing slash. */
|
|
74
|
+
readonly path: string;
|
|
75
|
+
/** Owns the full response lifecycle. */
|
|
76
|
+
handler: (req: RequestLike, res: ResponseLike) => void | Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
/** The slice of the HTTP carrier this plugin registers on. */
|
|
79
|
+
export interface WebServerLike {
|
|
80
|
+
/**
|
|
81
|
+
* Register one route.
|
|
82
|
+
* @param route - path, match kind, and handler.
|
|
83
|
+
* @returns the disposer that withdraws it.
|
|
84
|
+
*/
|
|
85
|
+
register(route: WebRouteLike): Disposable;
|
|
86
|
+
}
|
|
87
|
+
/** The composition's trust fence. */
|
|
88
|
+
export interface ConnectionLike {
|
|
89
|
+
/**
|
|
90
|
+
* Reject an untrusted or unauthenticated request.
|
|
91
|
+
* @param request - headers of the incoming request.
|
|
92
|
+
* @returns the rejection status, or `undefined` when the request may proceed.
|
|
93
|
+
*/
|
|
94
|
+
requestRejection(request: {
|
|
95
|
+
readonly headers: object | undefined;
|
|
96
|
+
}): 401 | 403 | undefined;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Structural view of the Cordis context the host half uses.
|
|
100
|
+
*
|
|
101
|
+
* `inject` guarantees `webServer` and `connection`; everything else is
|
|
102
|
+
* optional and read through `get`, so a composition without the settings,
|
|
103
|
+
* credentials, or LLM seam degrades to "no figure to report" instead of
|
|
104
|
+
* failing to mount.
|
|
105
|
+
*/
|
|
106
|
+
export interface HostContext {
|
|
107
|
+
/** Bind a registration's lifetime to this plugin's fiber. */
|
|
108
|
+
effect(callback: () => Disposable | void, label?: string): unknown;
|
|
109
|
+
/**
|
|
110
|
+
* Subscribe to a host event. This plugin watches `loader/volatile-update`,
|
|
111
|
+
* which is what a settings write emits once the live row references moved.
|
|
112
|
+
* @param event - the event name.
|
|
113
|
+
* @param listener - the callback.
|
|
114
|
+
* @returns the disposer that removes this listener.
|
|
115
|
+
*/
|
|
116
|
+
on(event: 'loader/volatile-update', listener: () => void): Disposable;
|
|
117
|
+
/** Read one optional service. */
|
|
118
|
+
get(name: string): unknown;
|
|
119
|
+
/** Structured log surface. */
|
|
120
|
+
readonly logger: {
|
|
121
|
+
warn(message: string): void;
|
|
122
|
+
error(message: string): void;
|
|
123
|
+
};
|
|
124
|
+
/** HTTP route carrier (guaranteed by `inject`). */
|
|
125
|
+
readonly webServer: WebServerLike;
|
|
126
|
+
/** Trust fence the route checks first (guaranteed by `inject`). */
|
|
127
|
+
readonly connection: ConnectionLike;
|
|
128
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-quota-check: one statusbar figure for the provider the current session
|
|
3
|
+
* is using: the remaining balance on a pure API-billing route, or the plan
|
|
4
|
+
* quota on a subscription route.
|
|
5
|
+
*
|
|
6
|
+
* Two halves, one endpoint:
|
|
7
|
+
*
|
|
8
|
+
* - this host half owns the provider credential and the outbound request, and
|
|
9
|
+
* serves one JSON reply from `GET /quota-check?provider=<id>`. That is the
|
|
10
|
+
* only way a browser half can reach a provider key: the key stays in this
|
|
11
|
+
* process, and the browser only ever sees the formatted figure.
|
|
12
|
+
* - `lib/client.js` registers the chip in `conversation.composer.dock` (the
|
|
13
|
+
* statusbar row directly under the composer and its model selector) and
|
|
14
|
+
* asks this route for the provider the session's model selection names.
|
|
15
|
+
*
|
|
16
|
+
* Which endpoints answer is `probes.ts`; the credential and the reading are
|
|
17
|
+
* resolved once per provider and cached for {@link DEFAULT_CACHE_SECONDS}, so
|
|
18
|
+
* a rerender, a session switch, or a second tab never multiplies provider
|
|
19
|
+
* traffic.
|
|
20
|
+
*
|
|
21
|
+
* @module dsh-quota-check
|
|
22
|
+
*/
|
|
23
|
+
import type { Volatile } from '@deepseek-ai/cordis';
|
|
24
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
25
|
+
import type { HostContext } from './host.ts';
|
|
26
|
+
/** Plugin name as it appears in the loader. */
|
|
27
|
+
export declare const name = "quota-check";
|
|
28
|
+
/**
|
|
29
|
+
* The route carrier, and the trust fence the route checks first: without
|
|
30
|
+
* `connection` nothing would refuse a cross-origin or unauthenticated caller,
|
|
31
|
+
* so the plugin waits for it rather than serving unfenced.
|
|
32
|
+
*/
|
|
33
|
+
export declare const inject: string[];
|
|
34
|
+
/** The route the browser half reads. */
|
|
35
|
+
export declare const ROUTE = "/quota-check";
|
|
36
|
+
/** Seconds one provider's reading is served without re-asking the provider. */
|
|
37
|
+
export declare const DEFAULT_CACHE_SECONDS = 60;
|
|
38
|
+
/** Per-request ceiling for a provider call, in milliseconds. */
|
|
39
|
+
export declare const DEFAULT_TIMEOUT_MS = 10000;
|
|
40
|
+
/** Seconds the browser half waits between re-reads, unless configured otherwise. */
|
|
41
|
+
export declare const DEFAULT_REFRESH_SECONDS = 300;
|
|
42
|
+
/**
|
|
43
|
+
* Configuration this plugin's row resolves to, as `apply` receives it.
|
|
44
|
+
*
|
|
45
|
+
* Every field is `volatile()`, so the loader hands a live reference rather than
|
|
46
|
+
* a value: the settings document accepts writes only under a volatile node, and
|
|
47
|
+
* the Plugins page's Quota check card edits exactly these three. Each is read
|
|
48
|
+
* per request, so a save changes the next reading, cache window, and polling
|
|
49
|
+
* cadence without remounting the route.
|
|
50
|
+
*/
|
|
51
|
+
export interface Config {
|
|
52
|
+
/** Seconds a reading stays cached. `0` re-asks on every request. @default 60 */
|
|
53
|
+
readonly cacheSeconds: Volatile<number>;
|
|
54
|
+
/** Per-request provider deadline in milliseconds. @default 10000 */
|
|
55
|
+
readonly timeoutMs: Volatile<number>;
|
|
56
|
+
/** Seconds between the browser half's re-reads. @default 300 */
|
|
57
|
+
readonly refreshSeconds: Volatile<number>;
|
|
58
|
+
}
|
|
59
|
+
/** Raw row values, as a profile patch states them and as direct callers pass them. */
|
|
60
|
+
export type Options = {
|
|
61
|
+
[K in keyof Config]?: Config[K] extends Volatile<infer T> ? T : Config[K];
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Row schema as Cordis resolves it: what this plugin's `config` is validated
|
|
65
|
+
* against, and where each default lives. Every value here is a deployment
|
|
66
|
+
* choice (the cadences and the deadline vary by machine), so none is a
|
|
67
|
+
* constant only this plugin could change, and all three are editable from the
|
|
68
|
+
* Plugins page.
|
|
69
|
+
*/
|
|
70
|
+
export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
|
|
71
|
+
cacheSeconds: Schema<number, number, "volatile-defined">;
|
|
72
|
+
timeoutMs: Schema<number, number, "volatile-defined">;
|
|
73
|
+
refreshSeconds: Schema<number, number, "volatile-defined">;
|
|
74
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
75
|
+
cacheSeconds: Schema<number, number, "volatile-defined">;
|
|
76
|
+
timeoutMs: Schema<number, number, "volatile-defined">;
|
|
77
|
+
refreshSeconds: Schema<number, number, "volatile-defined">;
|
|
78
|
+
}>>, "plain">;
|
|
79
|
+
/**
|
|
80
|
+
* Turn a row (live references or plain values) into validated plain options.
|
|
81
|
+
* @param row - the configured row.
|
|
82
|
+
* @returns the resolved options, defaults filled by the schema.
|
|
83
|
+
*/
|
|
84
|
+
export declare function resolveRow(row?: Config | Options): Required<Options>;
|
|
85
|
+
/** One provider's answer, as the browser half reads it. */
|
|
86
|
+
export interface QuotaReport {
|
|
87
|
+
/** Route id the report belongs to. */
|
|
88
|
+
readonly provider: string;
|
|
89
|
+
/** Display name for the route, when the registry declares one. */
|
|
90
|
+
readonly displayName: string;
|
|
91
|
+
/** `ok` renders the figure; `unsupported` and `error` render nothing. */
|
|
92
|
+
readonly status: 'ok' | 'unsupported' | 'error';
|
|
93
|
+
/** Which kind of figure this is, on `ok`. */
|
|
94
|
+
readonly kind?: 'balance' | 'quota';
|
|
95
|
+
/** Compact statusbar text, on `ok`. */
|
|
96
|
+
readonly text?: string;
|
|
97
|
+
/** Tooltip detail lines, on `ok`. */
|
|
98
|
+
readonly lines?: readonly string[];
|
|
99
|
+
/** Remaining quota as a percentage, on `ok` when the reading is metered. */
|
|
100
|
+
readonly remaining?: number;
|
|
101
|
+
/** Why there is no figure, on `unsupported` and `error`. */
|
|
102
|
+
readonly message?: string;
|
|
103
|
+
/** When the reading was taken, epoch milliseconds. */
|
|
104
|
+
readonly fetchedAt: number;
|
|
105
|
+
/** How long the browser half should wait before re-reading, in milliseconds. */
|
|
106
|
+
readonly refreshMs: number;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Mount the host half.
|
|
110
|
+
* @param ctx - host context carrying the route carrier.
|
|
111
|
+
* @param config - this plugin's row configuration.
|
|
112
|
+
*/
|
|
113
|
+
export declare function apply(ctx: HostContext, row?: Config | Options): void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local CLI credentials for the subscription providers: where the token lives,
|
|
3
|
+
* when it is stale, how it rotates, and the exact headers the vendor's usage
|
|
4
|
+
* endpoint expects.
|
|
5
|
+
*
|
|
6
|
+
* These four providers bill a plan, not a key, so there is no settings API key
|
|
7
|
+
* to resolve: the token already sits on this machine, written by the CLI the
|
|
8
|
+
* human signed into. This module is the port of `quota-widget`'s fetchers
|
|
9
|
+
* (`~/Desktop/Projects/quota-widget/package/contents/code/fetch_quota.py`);
|
|
10
|
+
* it keeps the observable behavior (file paths, expiry skews, refresh bodies,
|
|
11
|
+
* and the write-back that keeps the CLI itself signed in) and drops the parts
|
|
12
|
+
* a statusbar chip does not need (disk caches, Retry-After sleeps, the TUI
|
|
13
|
+
* JSON shape).
|
|
14
|
+
*
|
|
15
|
+
* Rotation writes back only through an atomic replace in the file's own
|
|
16
|
+
* directory, mode 0600, preserving every other field, so a crash mid-write can
|
|
17
|
+
* never leave a CLI with a truncated credential.
|
|
18
|
+
*
|
|
19
|
+
* @module dsh-quota-check/local-usage
|
|
20
|
+
*/
|
|
21
|
+
/** The subscription providers whose credentials live on this machine. */
|
|
22
|
+
export type LocalProvider = 'claude' | 'codex' | 'grok' | 'cursor';
|
|
23
|
+
/** One outbound request built from a local credential. */
|
|
24
|
+
export interface LocalRequest {
|
|
25
|
+
/** Absolute URL to GET. */
|
|
26
|
+
readonly url: string;
|
|
27
|
+
/** Headers the vendor expects, including the credential itself. */
|
|
28
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
29
|
+
}
|
|
30
|
+
/** Options for reading a local credential. */
|
|
31
|
+
export interface LocalOptions {
|
|
32
|
+
/** Home directory the CLI files hang off; defaults to the running user's. */
|
|
33
|
+
readonly home?: string;
|
|
34
|
+
/** Refresh even when the token looks unexpired; the 401 retry path uses this. */
|
|
35
|
+
readonly forceRefresh?: boolean;
|
|
36
|
+
/** Deadline for each token-endpoint request, in milliseconds. */
|
|
37
|
+
readonly timeoutMs: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build the outbound requests for one subscription provider.
|
|
41
|
+
* @param provider - which CLI credential to read.
|
|
42
|
+
* @param options - home directory, forced-refresh flag, and token-request deadline.
|
|
43
|
+
* @returns one or two requests, or `[]` when nothing usable is on disk.
|
|
44
|
+
*/
|
|
45
|
+
export declare function localRequests(provider: LocalProvider, options: LocalOptions): Promise<readonly LocalRequest[]>;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider quota/balance probes: which endpoint answers for a provider route,
|
|
3
|
+
* and how its payload becomes one statusbar line plus tooltip detail.
|
|
4
|
+
*
|
|
5
|
+
* A probe resolves from a provider id and an optional configured base URL, and
|
|
6
|
+
* a parser turns a decoded JSON payload into a {@link ProbeReading}. Host-only
|
|
7
|
+
* concerns (credentials, HTTP, caching) live in `index.ts`, which is what
|
|
8
|
+
* makes these rules testable without a network. One exception: an endpoint whose
|
|
9
|
+
* ids the configuration cannot name (OmniRoute asks per upstream connection)
|
|
10
|
+
* declares `requests`, which reads the listing before the host asks each id.
|
|
11
|
+
*
|
|
12
|
+
* Four endpoints report a *server-side* figure: DeepSeek's `/user/balance`,
|
|
13
|
+
* OpenRouter's `/credits`, the z.ai / BigModel Coding Plan quota, and LiteLLM's
|
|
14
|
+
* `/key/info` (spend and remaining budget for the calling key). LiteLLM is also
|
|
15
|
+
* the fallback for any otherwise-unknown route with a configured base URL,
|
|
16
|
+
* because a proxy deployment names its routes after the models it serves, not
|
|
17
|
+
* after the proxy: an unknown host that answers `/key/info` is a LiteLLM. A
|
|
18
|
+
* route whose host publishes neither resolves no probe and the statusbar stays
|
|
19
|
+
* empty.
|
|
20
|
+
*
|
|
21
|
+
* @module dsh-quota-check/probes
|
|
22
|
+
*/
|
|
23
|
+
import type { LocalProvider, LocalRequest } from './local-usage.ts';
|
|
24
|
+
/** Short reading for one provider: the chip text and its tooltip lines. */
|
|
25
|
+
export interface ProbeReading {
|
|
26
|
+
/** Compact statusbar text, e.g. `¥110.00` or `GLM 58%`. */
|
|
27
|
+
readonly text: string;
|
|
28
|
+
/** Tooltip detail lines, in display order. */
|
|
29
|
+
readonly lines: readonly string[];
|
|
30
|
+
/** Remaining quota as a percentage, when the reading is a metered quota. */
|
|
31
|
+
readonly remaining?: number;
|
|
32
|
+
}
|
|
33
|
+
/** What one provider reports: a spendable balance, or a plan quota. */
|
|
34
|
+
export type ProbeKind = 'balance' | 'quota';
|
|
35
|
+
/**
|
|
36
|
+
* The bodies a probe asked for, aligned with its requests: `undefined` marks a
|
|
37
|
+
* request that failed, so a two-meter provider (Grok's weekly and monthly
|
|
38
|
+
* calls) still reports when only one of them answered.
|
|
39
|
+
*/
|
|
40
|
+
export type ProbePayloads = readonly (unknown | undefined)[];
|
|
41
|
+
/** What a probe that discovers its own endpoints is handed. */
|
|
42
|
+
export interface ProbeExpandContext {
|
|
43
|
+
/** The route's resolved credential. */
|
|
44
|
+
readonly key: string;
|
|
45
|
+
/** Per-request deadline in milliseconds, for the listing request itself. */
|
|
46
|
+
readonly timeoutMs: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Build a probe's requests from an earlier answer, for endpoints whose ids the
|
|
50
|
+
* route's configuration cannot name.
|
|
51
|
+
* @param context - the credential and deadline to list with.
|
|
52
|
+
* @returns the requests to ask, in the order their payloads arrive.
|
|
53
|
+
*/
|
|
54
|
+
export type ProbeExpander = (context: ProbeExpandContext) => Promise<readonly LocalRequest[]>;
|
|
55
|
+
/** Everything a probe shares, whatever its credential source. */
|
|
56
|
+
interface ProbeBase {
|
|
57
|
+
/** Which kind of figure this is. */
|
|
58
|
+
readonly kind: ProbeKind;
|
|
59
|
+
/** Credential references tried when the provider configuration names none. */
|
|
60
|
+
readonly envNames?: readonly string[];
|
|
61
|
+
/**
|
|
62
|
+
* Requests discovered from an earlier answer, for a route whose endpoint
|
|
63
|
+
* names an id only the provider itself knows.
|
|
64
|
+
*/
|
|
65
|
+
readonly requests?: ProbeExpander;
|
|
66
|
+
/**
|
|
67
|
+
* True when this probe is a guess about a host the route never named: an
|
|
68
|
+
* absent endpoint or an unconfigured credential then means the host is not
|
|
69
|
+
* that product, which is absent data rather than a failed reading.
|
|
70
|
+
*/
|
|
71
|
+
readonly tentative?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Turn the decoded payloads into a reading.
|
|
74
|
+
* @param payloads - decoded JSON bodies, in request order.
|
|
75
|
+
* @returns the reading, or `null` when none carries a usable figure.
|
|
76
|
+
*/
|
|
77
|
+
parse(payloads: ProbePayloads): ProbeReading | null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* One probe: the endpoint it reads, and the credential that endpoint expects.
|
|
81
|
+
* A key probe fetches one URL with a bearer token resolved from settings; a
|
|
82
|
+
* local probe reads the subscription credential a CLI left on this machine and
|
|
83
|
+
* may ask two endpoints of the same account.
|
|
84
|
+
*/
|
|
85
|
+
export type Probe = ProbeBase & ({
|
|
86
|
+
/**
|
|
87
|
+
* Absolute URL to GET with the provider's bearer credential. Absent when
|
|
88
|
+
* the probe discovers its own requests instead.
|
|
89
|
+
*/
|
|
90
|
+
readonly url?: string;
|
|
91
|
+
readonly local?: undefined;
|
|
92
|
+
} | {
|
|
93
|
+
/** Local CLI credential this endpoint reads instead of a settings key. */
|
|
94
|
+
readonly local: LocalProvider;
|
|
95
|
+
readonly url?: undefined;
|
|
96
|
+
});
|
|
97
|
+
/**
|
|
98
|
+
* Format an amount for the statusbar.
|
|
99
|
+
* @param amount - absolute amount.
|
|
100
|
+
* @param currency - ISO currency code, when the provider reports one.
|
|
101
|
+
* @returns symbol-prefixed amount with two decimals, or the amount with its code.
|
|
102
|
+
*/
|
|
103
|
+
export declare function formatMoney(amount: number, currency?: string): string;
|
|
104
|
+
/**
|
|
105
|
+
* Resolve the probe answering for one provider route.
|
|
106
|
+
*
|
|
107
|
+
* A configured base URL decides alone when it names a known host, because the
|
|
108
|
+
* host is the endpoint that actually answers. The provider id only decides
|
|
109
|
+
* when no base URL is configured (the route relies on its library's own
|
|
110
|
+
* default), so a route named after a provider but pointed somewhere else (a
|
|
111
|
+
* local vLLM serving DeepSeek weights, or Vertex-hosted Claude, say) is never
|
|
112
|
+
* read from that provider's own subscription credential.
|
|
113
|
+
*
|
|
114
|
+
* The subscription probes come first among the id-based rules because their
|
|
115
|
+
* host is the vendor itself, not a reseller: a route called `anthropic` with no
|
|
116
|
+
* base URL is Claude Code's plan meter, while `google-vertex-anthropic` carries
|
|
117
|
+
* a googleapis.com host and resolves nothing.
|
|
118
|
+
*
|
|
119
|
+
* A route that names no vendor and still has a base URL gets the LiteLLM
|
|
120
|
+
* key-budget probe, which is how a proxy deployment is recognized at all.
|
|
121
|
+
* @param providerId - route id as the model picker names it, e.g. `deepseek-official`.
|
|
122
|
+
* @param baseURL - the route's configured base URL, when it has one.
|
|
123
|
+
* @returns the probe, or `undefined` when no balance or quota route is known.
|
|
124
|
+
*/
|
|
125
|
+
export declare function resolveProbe(providerId: string, baseURL?: string): Probe | undefined;
|
|
126
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrowing helpers shared by the probes, the local-usage reader, and the
|
|
3
|
+
* route: every provider answer is an unknown until one of these reads it.
|
|
4
|
+
*
|
|
5
|
+
* @module util
|
|
6
|
+
*/
|
|
7
|
+
/** Narrow an unknown to an indexable object. */
|
|
8
|
+
export declare function record(value: unknown): Record<string, unknown> | undefined;
|
|
9
|
+
/** Read one field as a non-empty string, or `undefined`. */
|
|
10
|
+
export declare function stringOf(value: unknown): string | undefined;
|
|
11
|
+
/** Read one field as a finite number, or `undefined`. */
|
|
12
|
+
export declare function numberOf(value: unknown): number | undefined;
|
|
13
|
+
/** Parse an ISO timestamp as epoch milliseconds, or `undefined`. */
|
|
14
|
+
export declare function isoToMs(value: unknown): number | undefined;
|
package/lib/util.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrowing helpers shared by the probes, the local-usage reader, and the
|
|
3
|
+
* route: every provider answer is an unknown until one of these reads it.
|
|
4
|
+
*
|
|
5
|
+
* @module util
|
|
6
|
+
*/
|
|
7
|
+
/** Narrow an unknown to an indexable object. */
|
|
8
|
+
export function record(value) {
|
|
9
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
10
|
+
? value
|
|
11
|
+
: undefined;
|
|
12
|
+
}
|
|
13
|
+
/** Read one field as a non-empty string, or `undefined`. */
|
|
14
|
+
export function stringOf(value) {
|
|
15
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
/** Read one field as a finite number, or `undefined`. */
|
|
18
|
+
export function numberOf(value) {
|
|
19
|
+
const parsed = typeof value === 'string' && value.trim() !== '' ? Number(value) : value;
|
|
20
|
+
return typeof parsed === 'number' && Number.isFinite(parsed) ? parsed : undefined;
|
|
21
|
+
}
|
|
22
|
+
/** Parse an ISO timestamp as epoch milliseconds, or `undefined`. */
|
|
23
|
+
export function isoToMs(value) {
|
|
24
|
+
if (typeof value !== 'string' || value === '')
|
|
25
|
+
return undefined;
|
|
26
|
+
const parsed = Date.parse(value);
|
|
27
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
28
|
+
}
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@maci0/dsh-quota-check",
|
|
3
|
+
"version": "0.12.2",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public",
|
|
6
|
+
"registry": "https://registry.npmjs.org/"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "lib/index.js",
|
|
10
|
+
"types": "lib/types/index.d.ts",
|
|
11
|
+
"description": "Quota and API-balance readout for the selected provider, in the DeepSeek Harness composer statusbar.",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./lib/types/index.d.ts",
|
|
16
|
+
"default": "./lib/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./client": {
|
|
19
|
+
"default": "./lib/client.js"
|
|
20
|
+
},
|
|
21
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
22
|
+
"./locale/*.json": "./locale/*.json",
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"dsh": {
|
|
26
|
+
"bundle": {
|
|
27
|
+
"patch": "./cordis.patch.yml"
|
|
28
|
+
},
|
|
29
|
+
"client": {
|
|
30
|
+
"platform": "web",
|
|
31
|
+
"inject": [
|
|
32
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
33
|
+
"@deepseek-ai/dsh-client-ui-session",
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
35
|
+
"@deepseek-ai/dsh-client-locale",
|
|
36
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
37
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"compatibility": {
|
|
41
|
+
"dsh": ">=0.2.0-rc.2 <0.3.0"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"icon.svg",
|
|
46
|
+
"locale/*.json",
|
|
47
|
+
"lib/**/*.js",
|
|
48
|
+
"lib/types/**/*.d.ts",
|
|
49
|
+
"cordis.patch.yml",
|
|
50
|
+
"README.md",
|
|
51
|
+
"LICENSE"
|
|
52
|
+
],
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "bunx --bun tsc -p tsconfig.build.json",
|
|
55
|
+
"test": "bun test",
|
|
56
|
+
"typecheck": "bunx --bun tsc -p tsconfig.json",
|
|
57
|
+
"test:node": "node --test tests/*.test.*"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@deepseek-ai/cordis": "4.0.5-alpha.1",
|
|
64
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.1-alpha.1",
|
|
65
|
+
"@types/node": "^22.20.2",
|
|
66
|
+
"typescript": "^7.0.2"
|
|
67
|
+
},
|
|
68
|
+
"repository": {
|
|
69
|
+
"type": "git",
|
|
70
|
+
"url": "https://github.com/maci0/dsh-quota-check.git"
|
|
71
|
+
},
|
|
72
|
+
"icon": "./icon.svg",
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"@deepseek-ai/schemastery": "^3.18.4"
|
|
75
|
+
},
|
|
76
|
+
"peerDependencies": {
|
|
77
|
+
"@deepseek-ai/dsh-client-ui-renderer": "*",
|
|
78
|
+
"@deepseek-ai/dsh-client-ui-session": "*",
|
|
79
|
+
"@deepseek-ai/dsh-client-ui-conversation": "*",
|
|
80
|
+
"@deepseek-ai/dsh-client-locale": "*",
|
|
81
|
+
"@deepseek-ai/dsh-client-ui-settings": "*",
|
|
82
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager": "*"
|
|
83
|
+
},
|
|
84
|
+
"peerDependenciesMeta": {
|
|
85
|
+
"@deepseek-ai/dsh-client-ui-renderer": {
|
|
86
|
+
"optional": true
|
|
87
|
+
},
|
|
88
|
+
"@deepseek-ai/dsh-client-ui-session": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
91
|
+
"@deepseek-ai/dsh-client-ui-conversation": {
|
|
92
|
+
"optional": true
|
|
93
|
+
},
|
|
94
|
+
"@deepseek-ai/dsh-client-locale": {
|
|
95
|
+
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"@deepseek-ai/dsh-client-ui-settings": {
|
|
98
|
+
"optional": true
|
|
99
|
+
},
|
|
100
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager": {
|
|
101
|
+
"optional": true
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|