dsh-workbuddy-xdpool 0.2.0 → 0.3.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/lib/index.d.ts CHANGED
@@ -1,629 +1,716 @@
1
- import z from "@deepseek-ai/schemastery";
2
- import { Api, Model } from "@earendil-works/pi-ai";
3
- import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
4
- import { Context } from "@deepseek-ai/cordis";
5
- import { SettingsNamespace } from "@deepseek-ai/dsh-settings";
6
- //#region src/accounts.d.ts
7
- /**
8
- * Account pool: discovers every WorkBuddy credential snapshot the desktop app
9
- * has left on this machine and hands out one healthy account per request,
10
- * rotating away from any account the upstream has rate-limited.
11
- *
12
- * Discovery is read-only: the desktop app's files are never written. Each
13
- * account is keyed by its billing identity (`uin`, falling back to `uid`), so
14
- * re-logging the same account refreshes in place instead of creating a duplicate.
15
- *
16
- * @module dsh-workbuddy-xdpool/accounts
17
- */
18
- /** Minimal upstream surface the pool needs to refresh a token (no circular import). */
19
- interface TokenRefresher {
20
- refreshToken(credential: WorkBuddyCredential): Promise<{
21
- accessToken: string;
22
- refreshToken?: string;
23
- expiresInSec?: number;
24
- domain?: string;
25
- }>;
26
- }
27
- /** Live auth file name the WorkBuddy desktop app writes. */
28
- export declare const WORKBUDDY_LIVE_FILENAME = "workbuddy-desktop.info";
29
- /** Env override for the auth file or its directory. */
30
- export declare const WORKBUDDY_AUTH_FILE_ENV = "WORKBUDDY_AUTH_FILE";
31
- /** One parsed WorkBuddy credential. */
32
- interface WorkBuddyCredential {
33
- accessToken: string;
34
- refreshToken: string;
35
- expiresAtMs: number;
36
- refreshExpiresAtMs?: number;
37
- nickname?: string;
38
- uin?: string;
39
- uid?: string;
40
- enterpriseId?: string;
41
- domain: string;
42
- /** Where this credential came from, for diagnostics. */
43
- sourcePath: string;
44
- }
45
- /** An account is a credential plus pool bookkeeping. */
46
- interface WorkBuddyAccount {
47
- /** Stable pool key: sha256 of the billing identity. */
48
- id: string;
49
- /** Short human label, e.g. `青楫渡` or `青楫渡#29890334`. */
50
- label: string;
51
- credential: WorkBuddyCredential;
52
- /**
53
- * Epoch ms until which this account is skipped for EVERY model. Only set by
54
- * account-wide cooldowns (callers that penalize without a model id). The
55
- * upstream rate limit is actually per-model ("可切换其他模型继续使用"), so
56
- * routine 429s are tracked in {@link modelCooldowns} instead and never ban a
57
- * whole account.
58
- */
59
- cooldownUntilMs: number;
60
- /**
61
- * Per-model cooldowns, keyed by upstream model id → epoch ms until that model
62
- * on THIS account is skipped. A 429 on `hy4-preview` cools only that model
63
- * here; `hy3`/`glm-*` on the same account keep serving.
64
- */
65
- modelCooldowns: Record<string, number>;
66
- /** Consecutive rate-limit hits, for diagnostics. */
67
- rateLimitHits: number;
68
- }
69
- /**
70
- * Platform-default directories holding the desktop app's auth files.
71
- * Windows probes Local before Roaming; a redirected profile still resolves
72
- * through the env location.
73
- */
74
- export declare function defaultDesktopAuthDirs(platform?: NodeJS.Platform, home?: string, env?: NodeJS.ProcessEnv): string[];
75
- /**
76
- * Parse a WorkBuddy auth document. Accepts the nested desktop shape
77
- * `{"auth":{...},"account":{...}}` and the flat panel shape; returns undefined
78
- * when there is no usable access token.
79
- */
80
- export declare function parseWorkBuddyAuth(text: string, sourcePath: string): WorkBuddyCredential | undefined;
81
- /**
82
- * Stable account id. `uin` is the billing identity the upstream keys on and
83
- * survives re-login; `uid` is the fallback.
84
- */
85
- export declare function workbuddyAccountId(credential: Pick<WorkBuddyCredential, 'uin' | 'uid' | 'nickname'>): string;
86
- /** Every directory the pool should scan, in probe order. */
87
- export declare function candidateAuthDirs(env?: NodeJS.ProcessEnv): string[];
88
- interface AccountPoolOptions {
89
- /** Logger for discovery and rotation events. */
90
- logger?: {
91
- info?(...args: unknown[]): void;
92
- warn(...args: unknown[]): void;
93
- error?(...args: unknown[]): void;
94
- };
95
- /** Override the directories scanned (tests). */
96
- authDirs?: readonly string[];
97
- /** How long a rate-limited account stays out of rotation. */
98
- cooldownMs?: number;
99
- /** Upstream client used to refresh near-expiry tokens. */
100
- client?: TokenRefresher;
101
- /** Refresh this long before actual expiry; default five minutes. */
102
- refreshMarginMs?: number;
103
- }
104
- /**
105
- * Read-only pool of every discovered WorkBuddy account, with rate-limit
106
- * cooldown and round-robin failover.
107
- */
108
- export declare class WorkBuddyAccountPool {
109
- private readonly logger;
110
- private authDirs;
111
- private cooldownMs;
112
- private readonly client;
113
- private readonly refreshMarginMs;
114
- private accounts;
115
- private cursor;
116
- private lastScanAtMs;
117
- private preferredId;
118
- private refreshInflight;
119
- constructor(options?: AccountPoolOptions);
120
- /**
121
- * Re-apply configuration that only affects discovery and cooldown policy,
122
- * without rebuilding the pool. A later `scan()` uses the new auth dirs and
123
- * cooldown window; existing accounts keep their in-memory state.
124
- */
125
- applyConfig(options: {
126
- authDirs?: readonly string[];
127
- cooldownMs?: number;
128
- }): void;
129
- /** Rescan the auth directories and merge newly discovered accounts. */
130
- scan(): Promise<WorkBuddyAccount[]>;
131
- /** All accounts, cooldown state included. */
132
- list(): readonly WorkBuddyAccount[];
133
- /**
134
- * Accounts currently eligible to serve a request.
135
- *
136
- * With a `modelId`, an account is eligible when it is not account-wide cooled
137
- * AND that model is not cooling on it — so a 429 on `hy4-preview` only keeps
138
- * that model out while `hy3` on the same account stays usable. Without a
139
- * model id the legacy account-wide check applies (callers that cannot name a
140
- * model, e.g. CLI diagnostics).
141
- */
142
- private available;
143
- /**
144
- * Pick the next usable account for an optional model. Scans on first use,
145
- * and rescans when every known account is cooling down — a fresh desktop
146
- * login is the usual way out of an exhausted pool. A preferred
147
- * (user-selected) account that is healthy is tried first; otherwise the
148
- * cursor round-robins so consecutive requests spread across accounts and a
149
- * still-cooling preferred account is skipped.
150
- */
151
- acquire(modelId?: string): Promise<WorkBuddyAccount | undefined>;
152
- /** Pin the account the plugin card should prefer; tokens stay out of settings. */
153
- prefer(accountId: string | undefined): void;
154
- /** Best-effort refresh of one account after a session-dead upstream answer. */
155
- refreshAccount(accountId: string): Promise<void>;
156
- /**
157
- * Refresh the account's access token when it is within the margin (or already
158
- * expired), in-flight de-duped per account. A failed refresh keeps the
159
- * existing token when it has not yet expired, so an unreachable refresh
160
- * endpoint never takes down a working session.
161
- */
162
- private ensureFresh;
163
- /**
164
- * Mark an account (or one of its models) rate-limited.
165
- *
166
- * With `modelId`, only that model on the account is cooled — the account's
167
- * other models stay in rotation, matching the upstream's per-model rate
168
- * limit ("可切换其他模型继续使用"). Without a model id the whole account is
169
- * cooled, which callers should reserve for limits that truly span every model.
170
- */
171
- penalize(accountId: string, resetAtMs?: number, modelId?: string): void;
172
- /** Clear all cooldowns (account-wide and per-model), e.g. from a reset command. */
173
- resetCooldowns(): void;
174
- /** Diagnostics snapshot. Account-wide cooling count (per-model cooling excluded:
175
- * the account as a whole stays usable when only one model is limited). */
176
- status(): {
177
- count: number;
178
- cooling: number;
179
- lastScanAtMs: number;
180
- };
181
- }
182
- //#endregion
183
- //#region src/upstream.d.ts
184
- /** Upstream failure classes the shim maps onto distinct HTTP answers. */
185
- type UpstreamErrorKind = 'hard_credit' | 'soft_rate' | 'session_dead' | 'not_found' | 'server' | 'client';
186
- /** Token-refresh answer; fields the upstream omits stay absent. */
187
- interface WorkBuddyRefreshOutcome {
188
- accessToken: string;
189
- refreshToken?: string;
190
- expiresInSec?: number;
191
- domain?: string;
192
- }
193
- /** One CLI-usable model, carrying what the plugin card displays. */
194
- interface WorkBuddyUpstreamModel {
195
- id: string;
196
- name: string;
197
- contextWindow: number;
198
- maxTokens: number;
199
- creditMultiplier?: number;
200
- multimodal?: boolean;
201
- reasoning?: {
202
- supportedEfforts?: readonly string[];
203
- defaultEffort?: string;
204
- canDisableThinking?: boolean;
205
- };
206
- descriptionZh?: string;
207
- descriptionEn?: string;
208
- supportsToolCall?: boolean;
209
- }
210
- /** One billing package, already normalised. */
211
- interface WorkBuddyCreditPackage {
212
- packageName: string;
213
- remain: number;
214
- size: number;
215
- monthly: boolean;
216
- refreshAtMs?: number;
217
- expiresAtMs?: number;
218
- }
219
- /** Aggregated credit answer for one credential. */
220
- interface WorkBuddyCredits {
221
- total: number;
222
- packages: readonly WorkBuddyCreditPackage[];
223
- expiringSoon: number;
224
- nearestExpiryMs?: number;
225
- }
226
- /** Daily check-in activity state. */
227
- interface WorkBuddyCheckinStatus {
228
- active: boolean;
229
- todayCheckedIn: boolean;
230
- streakDays: number;
231
- dailyCredit: number;
232
- todayCredit: number;
233
- isStreakDay: boolean;
234
- nextStreakDay: number;
235
- streakBonusDays: number;
236
- streakBonusCredit: number;
237
- /** Upstream-supplied button label; the card falls back to its own copy. */
238
- claimButtonText?: string;
239
- }
240
- /** Daily check-in claim result. */
241
- interface WorkBuddyCheckinClaim {
242
- credit: number;
243
- streakDays: number;
244
- isStreakDay: boolean;
245
- }
246
- /** Result of one upstream chat attempt. */
247
- type ChatStreamResult = {
248
- ok: true;
249
- response: Response;
250
- } | {
251
- ok: false;
252
- kind: UpstreamErrorKind;
253
- status: number;
254
- message: string;
255
- };
256
- interface UpstreamClientOptions {
257
- /** Injectable fetch, primarily for tests. */
258
- fetchImpl?: typeof fetch;
259
- /** Client version string sent to the upstream. */
260
- clientVersion?: string;
261
- }
262
- /**
263
- * Classify an upstream failure from its HTTP status and body excerpt.
264
- * Body markers win over status, because the upstream reuses 400/200 for
265
- * several distinct conditions.
266
- */
267
- export declare function classifyUpstreamError(status: number, body: string): UpstreamErrorKind;
268
- /**
269
- * Parse the reset time the upstream reports for a rate limit, when present.
270
- * Recognises an epoch-millisecond field and the Chinese-localised sentence
271
- * form, so the pool can resume exactly when the window reopens.
272
- */
273
- export declare function parseRateLimitReset(body: string): number | undefined;
274
- export declare class WorkBuddyUpstreamClient {
275
- private readonly fetchImpl;
276
- private readonly clientVersion;
277
- constructor(options?: UpstreamClientOptions);
278
- /**
279
- * Normalize an OpenAI chat-completions body for the WorkBuddy upstream:
280
- * force `stream: true` (the upstream rejects non-streaming), convert the
281
- * DSH `developer` role into `system` (upstream rejects `developer` with
282
- * business code 11128), and flatten `tool_choice` into its string form.
283
- */
284
- prepareChatBody(raw: string): string;
285
- /** Forward one chat completion. Never throws for upstream failures. */
286
- chatStream(credential: WorkBuddyCredential, prepared: string, signal?: AbortSignal): Promise<ChatStreamResult>;
287
- /** POST the token-refresh endpoint; the caller merges the outcome. */
288
- refreshToken(credential: WorkBuddyCredential): Promise<WorkBuddyRefreshOutcome>;
289
- /** GET the personal model catalog, keeping the `cli` agent's models only. */
290
- fetchModels(credential: WorkBuddyCredential, signal?: AbortSignal): Promise<readonly WorkBuddyUpstreamModel[]>;
291
- /** Read-only credits query, aggregated by package. Does not consume credits. */
292
- fetchCredits(credential: WorkBuddyCredential): Promise<WorkBuddyCredits>;
293
- /** Query today's check-in status without changing account state. */
294
- fetchCheckinStatus(credential: WorkBuddyCredential): Promise<WorkBuddyCheckinStatus>;
295
- /** Claim today's check-in reward. The browser route guards this mutation. */
296
- claimDailyCheckin(credential: WorkBuddyCredential): Promise<WorkBuddyCheckinClaim>;
297
- /** Legacy thin wrapper kept for `status`/`doctor`: returns raw envelope data. */
298
- credits(credential: WorkBuddyCredential): Promise<{
299
- ok: true;
300
- data: unknown;
301
- } | {
302
- ok: false;
303
- message: string;
304
- }>;
305
- }
306
- //#endregion
307
- //#region src/catalog.d.ts
308
- /** One model the provider exposes. */
309
- interface WorkBuddyModelInfo {
310
- id: string;
311
- /** Display name; the multiplier is appended for the picker. */
312
- name: string;
313
- contextWindow: number;
314
- maxOutputTokens: number;
315
- /** Relative credit cost, e.g. 0.79 for `x0.79`. */
316
- multiplier?: number;
317
- /** Upstream-declared thinking levels. */
318
- supportedEfforts?: readonly string[];
319
- supportsImages: boolean;
320
- /** Upstream tags: free / limited-free / night-discount. */
321
- tags?: readonly string[];
322
- }
323
- /** Static fallback used before the first live catalog fetch. */
324
- export declare const FALLBACK_WORKBUDDY_MODELS: readonly WorkBuddyModelInfo[];
325
- /** Live catalog with a static fallback behind it. */
326
- export declare class WorkBuddyCatalog {
327
- private models;
328
- private listeners;
329
- current(): readonly WorkBuddyModelInfo[];
330
- /** Replace the catalog and notify the adapter to rebuild its model list. */
331
- update(models: readonly WorkBuddyModelInfo[]): void;
332
- /** Restore the static fallback, e.g. when the upstream stops answering. */
333
- reset(): void;
334
- onChange(listener: () => void): () => void;
335
- find(id: string): WorkBuddyModelInfo | undefined;
336
- /** Replace the catalog from the live upstream list; keeps the fallback if empty. */
337
- updateFromUpstream(models: readonly WorkBuddyUpstreamModel[]): void;
338
- }
339
- //#endregion
340
- //#region src/shim.d.ts
341
- interface ShimLogger {
342
- info?(...args: unknown[]): void;
343
- warn(...args: unknown[]): void;
344
- error(...args: unknown[]): void;
345
- }
346
- interface WorkBuddyShim {
347
- ready: Promise<void>;
348
- baseUrl(): string;
349
- token(): string;
350
- close(): Promise<void>;
351
- }
352
- interface WorkBuddyShimOptions {
353
- pool: WorkBuddyAccountPool;
354
- client: WorkBuddyUpstreamClient;
355
- catalog: WorkBuddyCatalog;
356
- logger?: ShimLogger;
357
- /** Max accounts to try per request before giving up. */
358
- maxAttempts?: number;
359
- }
360
- export declare function createWorkBuddyShim(options: WorkBuddyShimOptions): WorkBuddyShim;
361
- //#endregion
362
- //#region src/adapter.d.ts
363
- /** Provider route this bundle owns. */
364
- export declare const WORKBUDDY_POOL_PROVIDER = "workbuddy-xdpool";
365
- interface WorkBuddyAdapterOptions {
366
- shim: WorkBuddyShim;
367
- catalog: WorkBuddyCatalog;
368
- providerId?: string;
369
- displayName?: string;
370
- }
371
- /** What {@link createWorkBuddyAdapter} hands back. */
372
- interface WorkBuddyAdapter {
373
- providerId: string;
374
- displayName: string;
375
- adapter: PiAiAdapter;
376
- /** Rebuild the pi-ai model list from the current catalog. */
377
- buildModels: () => Model<Api>[];
378
- /** Rebuild the adapter's provider snapshot; call after a catalog update. */
379
- invalidate: () => void;
380
- }
381
- /**
382
- * Assemble the adapter. `getModels` re-reads the live catalog, and every
383
- * model's `baseUrl` is re-resolved per read so the shim's ephemeral port
384
- * applies from the first snapshot after startup. Call only after `shim.ready`.
385
- */
386
- export declare function createWorkBuddyAdapter(options: WorkBuddyAdapterOptions): WorkBuddyAdapter;
387
- //#endregion
388
- //#region src/status.d.ts
389
- /** One account's status row. */
390
- interface AccountStatus {
391
- id: string;
392
- label: string;
393
- nickname?: string;
394
- domain: string;
395
- /** ISO timestamp when the access token expires. */
396
- expiresAt?: string;
397
- /** Account-wide cooldown (every model blocked). */
398
- cooling: boolean;
399
- cooldownUntil?: string;
400
- /** Per-model cooldowns active right now (modelId → ISO until); the account
401
- * itself is not `cooling` while only some models are limited. */
402
- modelCooldowns?: readonly {
403
- modelId: string;
404
- until: string;
405
- }[];
406
- rateLimitHits: number;
407
- /** Read-only aggregated credit summary for the account. */
408
- credits?: WorkBuddyCredits;
409
- creditsError?: string;
410
- sourcePath: string;
411
- }
412
- /** Whole-plugin status document. */
413
- interface WorkBuddyStatus {
414
- ok: boolean;
415
- accounts: AccountStatus[];
416
- activeAccountId?: string;
417
- cooling: number;
418
- models: {
419
- id: string;
420
- name: string;
421
- multiplier?: number;
422
- tags?: readonly string[];
423
- }[];
424
- shim: {
425
- running: boolean;
426
- baseUrl?: string;
427
- };
428
- }
429
- interface StatusOptions {
430
- pool: WorkBuddyAccountPool;
431
- catalog: WorkBuddyCatalog;
432
- client: WorkBuddyUpstreamClient;
433
- shim?: {
434
- running: boolean;
435
- baseUrl?: string;
436
- };
437
- /** Query credits per account. Off for cheap diagnostics runs. */
438
- includeCredits?: boolean;
439
- }
440
- /** Build the status document. Never throws. */
441
- export declare function buildStatus(options: StatusOptions): Promise<WorkBuddyStatus>;
442
- /** Format the status document for a terminal. */
443
- export declare function formatStatus(status: WorkBuddyStatus): string;
444
- /** Format the per-model credit multipliers. */
445
- export declare function formatRates(status: WorkBuddyStatus): string;
446
- //#endregion
447
- //#region src/status-paths.d.ts
448
- /**
449
- * Node-free constants and types shared by the Host and browser halves of the
450
- * WorkBuddy XD Pool settings card.
451
- *
452
- * Pool's runtime state already lives in `src/status.ts` (`buildStatus` /
453
- * `WorkBuddyStatus`); this module only carves the cross-domain (Host→browser)
454
- * JSON document into a shape that stays token-free and matches what the
455
- * browser card renders. Route paths are plugin-owned and mounted on the Host's
456
- * same-origin web server (see `src/web-status.ts`).
457
- *
458
- * @module dsh-workbuddy-xdpool/status-paths
459
- */
460
- /** Plugin-owned read-only pool status endpoint (account rows + models + shim). */
461
- export declare const POOL_STATUS_PATH = "/plugins/dsh-workbuddy-xdpool/status";
462
- /** Plugin-owned local account rescan endpoint (re-read desktop snapshots). */
463
- export declare const POOL_RESCAN_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/rescan";
464
- /** Plugin-owned cooldown reset endpoint (clear all 429 cooldowns). */
465
- export declare const POOL_RESET_COOLDOWN_PATH = "/plugins/dsh-workbuddy-xdpool/cooldowns/reset";
466
- /** Plugin-owned daily check-in action endpoint (claim today's reward). */
467
- export declare const POOL_CHECKIN_PATH = "/plugins/dsh-workbuddy-xdpool/checkin";
468
- /** One pool account's row, token-free. */
469
- interface PoolWebAccount {
470
- id: string;
471
- label: string;
472
- nickname?: string;
473
- domain: string;
474
- /** ISO timestamp; absent when the credential carries no expiry. */
475
- expiresAt?: string;
476
- /** Account-wide cooldown (every model blocked); only after a no-model penalize. */
477
- cooling: boolean;
478
- /** ISO timestamp when the account-wide 429 cooldown lifts; only while cooling. */
479
- cooldownUntil?: string;
480
- /**
481
- * Per-model cooldowns currently active. The account is NOT `cooling` while a
482
- * model is limited — its other models still serve — but each entry tells the
483
- * card which model is out until when (e.g. `hy4-preview` cooling to 10:14,
484
- * `hy3` normal).
485
- */
486
- modelCooldowns?: ReadonlyArray<{
487
- modelId: string;
488
- until: string;
489
- }>;
490
- rateLimitHits: number;
491
- /** ISO timestamp of the last successful use (best-effort pool bookkeeping). */
492
- lastUsedAt?: string;
493
- /** Aggregated credit summary for the account, read-only. */
494
- credits?: PoolWebCredits;
495
- creditsError?: string;
496
- /**
497
- * Today's check-in state for this account, read-only. Present only when the
498
- * per-account check-in probe succeeded and the program is active. The card
499
- * renders one claim button per account, so a multi-account pool can collect
500
- * every account's daily reward without switching accounts by hand.
501
- */
502
- checkin?: PoolWebCheckin;
503
- checkinError?: string;
504
- }
505
- /** One credit package (as surfaced by the pool's upstream client), node-free. */
506
- interface PoolWebCreditPackage {
507
- packageName: string;
508
- remain?: number;
509
- size?: number;
510
- /** CapacityType 4 — refreshed each cycle and never expires. */
511
- monthly?: boolean;
512
- /** Next cycle refresh point, ms. */
513
- cycleRefreshMs?: number;
514
- /** One-off expiry, ms. */
515
- expiresAtMs?: number;
516
- }
517
- /** Aggregated credit answer the card renders under one account. */
518
- interface PoolWebCredits {
519
- total?: number;
520
- packages: readonly PoolWebCreditPackage[];
521
- /** Credits expiring within 3 days. */
522
- expiringSoon?: number;
523
- /** When the nearest package expires, ms. */
524
- nearestExpiryMs?: number;
525
- }
526
- /**
527
- * Daily check-in state the card renders under one account's credits. Mirrors
528
- * the upstream activity endpoint, minus anything the browser does not need.
529
- */
530
- interface PoolWebCheckin {
531
- /** The activity is running; a claim button is offered only while true. */
532
- active: boolean;
533
- /** Already collected today — the button renders as a done state. */
534
- todayCheckedIn: boolean;
535
- /** Consecutive days checked in. */
536
- streakDays: number;
537
- /** Credits a single day grants. */
538
- dailyCredit: number;
539
- /** Credits collected today (0 before claiming). */
540
- todayCredit: number;
541
- /** Today is a streak milestone day. */
542
- isStreakDay: boolean;
543
- /** The day count the next milestone lands on. */
544
- nextStreakDay: number;
545
- /** Bonus credits granted on a milestone day. */
546
- streakBonusCredit: number;
547
- }
548
- /** Result of one claim, so the card can confirm what was collected. */
549
- interface PoolWebCheckinClaim {
550
- credit: number;
551
- streakDays: number;
552
- isStreakDay: boolean;
553
- }
554
- /** One model the pool exposes to DSH, with cost / free tags. */
555
- interface PoolWebModel {
556
- id: string;
557
- name: string;
558
- /** Relative credit cost, e.g. 0.79 for x0.79. */
559
- multiplier?: number;
560
- /** Upstream tags: free / limited-free / night-discount. */
561
- tags?: readonly string[];
562
- supportsImages: boolean;
563
- contextWindow: number;
564
- }
565
- /** The JSON document the pool card renders. */
566
- interface PoolWebStatus {
567
- ok: boolean;
568
- accounts: readonly PoolWebAccount[];
569
- /** The next account the pool would use (rotation cursor). */
570
- activeAccountId?: string;
571
- cooling: number;
572
- models: readonly PoolWebModel[];
573
- shim: {
574
- running: boolean;
575
- baseUrl?: string;
576
- };
577
- }
578
- //#endregion
579
- //#region src/index.d.ts
580
- /** Stable Cordis plugin name. */
581
- export declare const name = "llm-workbuddy-xdpool";
582
- /** The model registry required before the provider can register. */
583
- export declare const inject: string[];
584
- /**
585
- * Settings namespace for the WorkBuddy XD Pool card. Registering a section here
586
- * is what makes the provider appear on the Models settings page and causes the
587
- * Host to mount the plugin's client card under Plugin configuration — exactly
588
- * the mechanism the single-account connector uses.
589
- */
590
- export declare const WORKBUDDY_POOL_SETTINGS_NS: SettingsNamespace;
591
- /** Plugin configuration. */
592
- export interface Config {
593
- /** Explicit WorkBuddy desktop auth-file path override. */
594
- authFile?: string;
595
- /** Rate-limit cooldown per account, milliseconds. */
596
- cooldownMs?: number;
597
- }
598
- export declare const Config: z<Config>;
599
- /** Everything the CLI needs from a live plugin instance. */
600
- export interface WorkBuddyPoolApi {
601
- pool: WorkBuddyAccountPool;
602
- catalog: WorkBuddyCatalog;
603
- client: WorkBuddyUpstreamClient;
604
- shim: WorkBuddyShim;
605
- adapter: WorkBuddyAdapter | undefined;
606
- rescan(): Promise<number>;
607
- status(includeCredits?: boolean): Promise<Awaited<ReturnType<typeof buildStatus>>>;
608
- resetCooldowns(): void;
609
- }
610
- /** The live API, or undefined when the plugin has not applied yet. */
611
- export declare function currentApi(): WorkBuddyPoolApi | undefined;
612
- /** Test seam: install an API instance without booting cordis. */
613
- export declare function setApi(next: WorkBuddyPoolApi | undefined): void;
614
- /** Assemble the runtime objects without registering anything. */
615
- export declare function createCore(logger?: {
616
- warn(...args: unknown[]): void;
617
- }): {
618
- pool: WorkBuddyAccountPool;
619
- catalog: WorkBuddyCatalog;
620
- client: WorkBuddyUpstreamClient;
621
- };
622
- /**
623
- * Start the loopback endpoint, register the `workbuddy-xdpool` provider, and
624
- * discover accounts. The provider registers only after `shim.ready` resolves,
625
- * because its models read the shim origin at construction time.
626
- */
627
- export declare function apply(ctx: Context, config?: Config): void;
628
- //#endregion
629
- export type { AccountStatus, Context, PoolWebCheckin, PoolWebCheckinClaim, PoolWebStatus, UpstreamErrorKind, WorkBuddyAccount, WorkBuddyAdapter, WorkBuddyCredential, WorkBuddyModelInfo, WorkBuddyShim, WorkBuddyStatus };
1
+ import z from "@deepseek-ai/schemastery";
2
+ import { Api, Model } from "@earendil-works/pi-ai";
3
+ import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
4
+ import { Context } from "@deepseek-ai/cordis";
5
+ import { SettingsNamespace } from "@deepseek-ai/dsh-settings";
6
+ //#region src/upstream.d.ts
7
+ /** Upstream failure classes the shim maps onto distinct HTTP answers. */
8
+ type UpstreamErrorKind = 'hard_credit' | 'soft_rate' | 'session_dead' | 'not_found' | 'server' | 'client';
9
+ /** Token-refresh answer; fields the upstream omits stay absent. */
10
+ interface WorkBuddyRefreshOutcome {
11
+ accessToken: string;
12
+ refreshToken?: string;
13
+ expiresInSec?: number;
14
+ domain?: string;
15
+ }
16
+ /** One CLI-usable model, carrying what the plugin card displays. */
17
+ interface WorkBuddyUpstreamModel {
18
+ id: string;
19
+ name: string;
20
+ contextWindow: number;
21
+ maxTokens: number;
22
+ creditMultiplier?: number;
23
+ multimodal?: boolean;
24
+ reasoning?: {
25
+ supportedEfforts?: readonly string[];
26
+ defaultEffort?: string;
27
+ canDisableThinking?: boolean;
28
+ };
29
+ descriptionZh?: string;
30
+ descriptionEn?: string;
31
+ supportsToolCall?: boolean;
32
+ }
33
+ /** One billing package, already normalised. */
34
+ interface WorkBuddyCreditPackage {
35
+ packageName: string;
36
+ remain: number;
37
+ size: number;
38
+ monthly: boolean;
39
+ refreshAtMs?: number;
40
+ expiresAtMs?: number;
41
+ }
42
+ /** Aggregated credit answer for one credential. */
43
+ interface WorkBuddyCredits {
44
+ total: number;
45
+ packages: readonly WorkBuddyCreditPackage[];
46
+ expiringSoon: number;
47
+ nearestExpiryMs?: number;
48
+ }
49
+ /** Daily check-in activity state. */
50
+ interface WorkBuddyCheckinStatus {
51
+ active: boolean;
52
+ todayCheckedIn: boolean;
53
+ streakDays: number;
54
+ dailyCredit: number;
55
+ todayCredit: number;
56
+ isStreakDay: boolean;
57
+ nextStreakDay: number;
58
+ streakBonusDays: number;
59
+ streakBonusCredit: number;
60
+ /** Upstream-supplied button label; the card falls back to its own copy. */
61
+ claimButtonText?: string;
62
+ }
63
+ /** Daily check-in claim result. */
64
+ interface WorkBuddyCheckinClaim {
65
+ credit: number;
66
+ streakDays: number;
67
+ isStreakDay: boolean;
68
+ }
69
+ /** Result of one upstream chat attempt. */
70
+ type ChatStreamResult = {
71
+ ok: true;
72
+ response: Response;
73
+ } | {
74
+ ok: false;
75
+ kind: UpstreamErrorKind;
76
+ status: number;
77
+ message: string;
78
+ };
79
+ interface UpstreamClientOptions {
80
+ /** Injectable fetch, primarily for tests. */
81
+ fetchImpl?: typeof fetch;
82
+ /** Client version string sent to the upstream. */
83
+ clientVersion?: string;
84
+ }
85
+ /** Region for a login domain; an empty domain means CN (matching upstream tooling). */
86
+ /** The two gateways WorkBuddy serves: the domestic one and the international one. */
87
+ type WorkBuddyRegion = 'cn' | 'global';
88
+ /**
89
+ * Classify an upstream failure from its HTTP status and body excerpt.
90
+ * Body markers win over status, because the upstream reuses 400/200 for
91
+ * several distinct conditions.
92
+ */
93
+ export declare function classifyUpstreamError(status: number, body: string): UpstreamErrorKind;
94
+ /**
95
+ * Parse the reset time the upstream reports for a rate limit, when present.
96
+ * Recognises an epoch-millisecond field and the Chinese-localised sentence
97
+ * form, so the pool can resume exactly when the window reopens.
98
+ */
99
+ export declare function parseRateLimitReset(body: string): number | undefined;
100
+ export declare class WorkBuddyUpstreamClient {
101
+ private readonly fetchImpl;
102
+ private readonly clientVersion;
103
+ constructor(options?: UpstreamClientOptions);
104
+ /**
105
+ * Normalize an OpenAI chat-completions body for the WorkBuddy upstream:
106
+ * force `stream: true` (the upstream rejects non-streaming), convert the
107
+ * DSH `developer` role into `system` (upstream rejects `developer` with
108
+ * business code 11128), and flatten `tool_choice` into its string form.
109
+ */
110
+ prepareChatBody(raw: string): string;
111
+ /** Forward one chat completion. Never throws for upstream failures. */
112
+ chatStream(credential: WorkBuddyCredential, prepared: string, signal?: AbortSignal): Promise<ChatStreamResult>;
113
+ /** POST the token-refresh endpoint; the caller merges the outcome. */
114
+ refreshToken(credential: WorkBuddyCredential): Promise<WorkBuddyRefreshOutcome>;
115
+ /** GET the personal model catalog, keeping the `cli` agent's models only. */
116
+ fetchModels(credential: WorkBuddyCredential, signal?: AbortSignal): Promise<readonly WorkBuddyUpstreamModel[]>;
117
+ /** Read-only credits query, aggregated by package. Does not consume credits. */
118
+ fetchCredits(credential: WorkBuddyCredential): Promise<WorkBuddyCredits>;
119
+ /** Query today's check-in status without changing account state. */
120
+ fetchCheckinStatus(credential: WorkBuddyCredential): Promise<WorkBuddyCheckinStatus>;
121
+ /** Claim today's check-in reward. The browser route guards this mutation. */
122
+ claimDailyCheckin(credential: WorkBuddyCredential): Promise<WorkBuddyCheckinClaim>;
123
+ /** Legacy thin wrapper kept for `status`/`doctor`: returns raw envelope data. */
124
+ credits(credential: WorkBuddyCredential): Promise<{
125
+ ok: true;
126
+ data: unknown;
127
+ } | {
128
+ ok: false;
129
+ message: string;
130
+ }>;
131
+ }
132
+ //#endregion
133
+ //#region src/accounts.d.ts
134
+ /** Minimal upstream surface the pool needs to refresh a token (no circular import). */
135
+ interface TokenRefresher {
136
+ refreshToken(credential: WorkBuddyCredential): Promise<{
137
+ accessToken: string;
138
+ refreshToken?: string;
139
+ expiresInSec?: number;
140
+ domain?: string;
141
+ }>;
142
+ }
143
+ /** Live auth file name the WorkBuddy desktop app writes. */
144
+ export declare const WORKBUDDY_LIVE_FILENAME = "workbuddy-desktop.info";
145
+ /** Env override for the auth file or its directory. */
146
+ export declare const WORKBUDDY_AUTH_FILE_ENV = "WORKBUDDY_AUTH_FILE";
147
+ /** One parsed WorkBuddy credential. */
148
+ interface WorkBuddyCredential {
149
+ accessToken: string;
150
+ refreshToken: string;
151
+ expiresAtMs: number;
152
+ refreshExpiresAtMs?: number;
153
+ nickname?: string;
154
+ uin?: string;
155
+ uid?: string;
156
+ enterpriseId?: string;
157
+ domain: string;
158
+ /** Where this credential came from, for diagnostics. */
159
+ sourcePath: string;
160
+ }
161
+ /** An account is a credential plus pool bookkeeping. */
162
+ interface WorkBuddyAccount {
163
+ /** Stable pool key: sha256 of the billing identity. */
164
+ id: string;
165
+ /** Short human label, e.g. `青楫渡` or `青楫渡#29890334`. */
166
+ label: string;
167
+ credential: WorkBuddyCredential;
168
+ /**
169
+ * Epoch ms until which this account is skipped for EVERY model. Only set by
170
+ * account-wide cooldowns (callers that penalize without a model id). The
171
+ * upstream rate limit is actually per-model ("可切换其他模型继续使用"), so
172
+ * routine 429s are tracked in {@link modelCooldowns} instead and never ban a
173
+ * whole account.
174
+ */
175
+ cooldownUntilMs: number;
176
+ /**
177
+ * Per-model cooldowns, keyed by upstream model id → epoch ms until that model
178
+ * on THIS account is skipped. A 429 on `hy4-preview` cools only that model
179
+ * here; `hy3`/`glm-*` on the same account keep serving.
180
+ */
181
+ modelCooldowns: Record<string, number>;
182
+ /** Consecutive rate-limit hits, for diagnostics. */
183
+ rateLimitHits: number;
184
+ }
185
+ /**
186
+ * Platform-default directories holding the desktop app's auth files.
187
+ * Windows probes Local before Roaming; a redirected profile still resolves
188
+ * through the env location.
189
+ */
190
+ export declare function defaultDesktopAuthDirs(platform?: NodeJS.Platform, home?: string, env?: NodeJS.ProcessEnv): string[];
191
+ /**
192
+ * Parse a WorkBuddy auth document. Accepts the nested desktop shape
193
+ * `{"auth":{...},"account":{...}}` and the flat panel shape; returns undefined
194
+ * when there is no usable access token.
195
+ */
196
+ export declare function parseWorkBuddyAuth(text: string, sourcePath: string): WorkBuddyCredential | undefined;
197
+ /**
198
+ * Stable account id. `uin` is the billing identity the upstream keys on and
199
+ * survives re-login; `uid` is the fallback.
200
+ */
201
+ export declare function workbuddyAccountId(credential: Pick<WorkBuddyCredential, 'uin' | 'uid' | 'nickname'>): string;
202
+ /** Every directory the pool should scan, in probe order. */
203
+ export declare function candidateAuthDirs(env?: NodeJS.ProcessEnv): string[];
204
+ interface AccountPoolOptions {
205
+ /** Logger for discovery and rotation events. */
206
+ logger?: {
207
+ info?(...args: unknown[]): void;
208
+ warn(...args: unknown[]): void;
209
+ error?(...args: unknown[]): void;
210
+ };
211
+ /** Override the directories scanned (tests). */
212
+ authDirs?: readonly string[];
213
+ /** How long a rate-limited account stays out of rotation. */
214
+ cooldownMs?: number;
215
+ /** Upstream client used to refresh near-expiry tokens. */
216
+ client?: TokenRefresher;
217
+ /** Refresh this long before actual expiry; default five minutes. */
218
+ refreshMarginMs?: number;
219
+ }
220
+ /**
221
+ * Read-only pool of every discovered WorkBuddy account, with rate-limit
222
+ * cooldown and round-robin failover.
223
+ */
224
+ export declare class WorkBuddyAccountPool {
225
+ private readonly logger;
226
+ private authDirs;
227
+ private cooldownMs;
228
+ private readonly client;
229
+ private readonly refreshMarginMs;
230
+ private accounts;
231
+ private cursor;
232
+ private lastScanAtMs;
233
+ private preferredId;
234
+ private refreshInflight;
235
+ constructor(options?: AccountPoolOptions);
236
+ /**
237
+ * Re-apply configuration that only affects discovery and cooldown policy,
238
+ * without rebuilding the pool. A later `scan()` uses the new auth dirs and
239
+ * cooldown window; existing accounts keep their in-memory state.
240
+ */
241
+ applyConfig(options: {
242
+ authDirs?: readonly string[];
243
+ cooldownMs?: number;
244
+ }): void;
245
+ /** Rescan the auth directories and merge newly discovered accounts. */
246
+ scan(): Promise<WorkBuddyAccount[]>;
247
+ /** All accounts, cooldown state included. */
248
+ list(region?: WorkBuddyRegion): readonly WorkBuddyAccount[];
249
+ /**
250
+ * Accounts currently eligible to serve a request.
251
+ *
252
+ * With a `modelId`, an account is eligible when it is not account-wide cooled
253
+ * AND that model is not cooling on it — so a 429 on `hy4-preview` only keeps
254
+ * that model out while `hy3` on the same account stays usable. Without a
255
+ * model id the legacy account-wide check applies (callers that cannot name a
256
+ * model, e.g. CLI diagnostics).
257
+ */
258
+ private available;
259
+ /**
260
+ * Pick the next usable account for an optional model. Scans on first use,
261
+ * and rescans when every known account is cooling down — a fresh desktop
262
+ * login is the usual way out of an exhausted pool. A preferred
263
+ * (user-selected) account that is healthy is tried first; otherwise the
264
+ * cursor round-robins so consecutive requests spread across accounts and a
265
+ * still-cooling preferred account is skipped.
266
+ */
267
+ acquire(modelId?: string, region?: WorkBuddyRegion): Promise<WorkBuddyAccount | undefined>;
268
+ /** Pin the account the plugin card should prefer; tokens stay out of settings. */
269
+ prefer(accountId: string | undefined): void;
270
+ /** Best-effort refresh of one account after a session-dead upstream answer. */
271
+ refreshAccount(accountId: string): Promise<void>;
272
+ /**
273
+ * Refresh the account's access token when it is within the margin (or already
274
+ * expired), in-flight de-duped per account. A failed refresh keeps the
275
+ * existing token when it has not yet expired, so an unreachable refresh
276
+ * endpoint never takes down a working session.
277
+ */
278
+ private ensureFresh;
279
+ /**
280
+ * Mark an account (or one of its models) rate-limited.
281
+ *
282
+ * With `modelId`, only that model on the account is cooled — the account's
283
+ * other models stay in rotation, matching the upstream's per-model rate
284
+ * limit ("可切换其他模型继续使用"). Without a model id the whole account is
285
+ * cooled, which callers should reserve for limits that truly span every model.
286
+ */
287
+ penalize(accountId: string, resetAtMs?: number, modelId?: string): void;
288
+ /** Clear all cooldowns (account-wide and per-model), e.g. from a reset command. */
289
+ resetCooldowns(): void;
290
+ /** Diagnostics snapshot. Account-wide cooling count (per-model cooling excluded:
291
+ * the account as a whole stays usable when only one model is limited). */
292
+ status(): {
293
+ count: number;
294
+ cooling: number;
295
+ lastScanAtMs: number;
296
+ };
297
+ }
298
+ //#endregion
299
+ //#region src/catalog.d.ts
300
+ /** One model the provider exposes. */
301
+ interface WorkBuddyModelInfo {
302
+ id: string;
303
+ /** Display name; the multiplier is appended for the picker. */
304
+ name: string;
305
+ contextWindow: number;
306
+ maxOutputTokens: number;
307
+ /** Relative credit cost, e.g. 0.79 for `x0.79`. */
308
+ multiplier?: number;
309
+ /** Upstream-declared thinking levels. */
310
+ supportedEfforts?: readonly string[];
311
+ supportsImages: boolean;
312
+ /** Upstream tags: free / limited-free / night-discount. */
313
+ tags?: readonly string[];
314
+ }
315
+ /** Static fallback used before the first live catalog fetch. */
316
+ export declare const FALLBACK_WORKBUDDY_MODELS: readonly WorkBuddyModelInfo[];
317
+ /** Live catalog with a static fallback behind it. */
318
+ export declare class WorkBuddyCatalog {
319
+ private models;
320
+ private listeners;
321
+ /** User's model selection. Empty object = follow the catalog unfiltered. */
322
+ private selection;
323
+ current(): readonly WorkBuddyModelInfo[];
324
+ /**
325
+ * The models DSH should actually offer, after applying the user's selection:
326
+ * disabled models are dropped, an explicit image list overrides the upstream
327
+ * capability flag, and a per-model budget caps the advertised window.
328
+ *
329
+ * An absent `enabledModelIds` means "everything" — a fresh install with no
330
+ * saved selection must not present an empty picker.
331
+ */
332
+ visible(): readonly WorkBuddyModelInfo[];
333
+ /** Replace the catalog and notify the adapter to rebuild its model list. */
334
+ update(models: readonly WorkBuddyModelInfo[]): void;
335
+ /** Restore the static fallback, e.g. when the upstream stops answering. */
336
+ reset(): void;
337
+ /** Replace the user's selection; the adapter rebuilds from `visible()`. */
338
+ applySelection(selection: ModelSelection): void;
339
+ /** The selection currently in force, for the card's save round-trip. */
340
+ currentSelection(): ModelSelection;
341
+ onChange(listener: () => void): () => void;
342
+ find(id: string): WorkBuddyModelInfo | undefined;
343
+ /** Replace the catalog from the live upstream list; keeps the fallback if empty. */
344
+ updateFromUpstream(models: readonly WorkBuddyUpstreamModel[]): void;
345
+ private notify;
346
+ }
347
+ /** The user's model selection, as stored in the settings section. */
348
+ interface ModelSelection {
349
+ /** Absent = every model in the catalog is offered. */
350
+ enabledModelIds?: readonly string[];
351
+ /** Absent = each model follows its upstream image capability. */
352
+ imageModelIds?: readonly string[];
353
+ /** Per-model context-window cap, keyed by model id. */
354
+ contextBudgets?: Readonly<Record<string, number>>;
355
+ }
356
+ //#endregion
357
+ //#region src/shim.d.ts
358
+ interface ShimLogger {
359
+ info?(...args: unknown[]): void;
360
+ warn(...args: unknown[]): void;
361
+ error(...args: unknown[]): void;
362
+ }
363
+ interface WorkBuddyShim {
364
+ ready: Promise<void>;
365
+ baseUrl(): string;
366
+ token(): string;
367
+ close(): Promise<void>;
368
+ }
369
+ interface WorkBuddyShimOptions {
370
+ pool: WorkBuddyAccountPool;
371
+ client: WorkBuddyUpstreamClient;
372
+ catalog: WorkBuddyCatalog;
373
+ logger?: ShimLogger;
374
+ /**
375
+ * Restrict this shim to one gateway. Two shims run side by side — one
376
+ * per region — and each must only ever draw accounts that belong to its
377
+ * own gateway. Absent means "every account" (a single-region deployment).
378
+ */
379
+ region?: WorkBuddyRegion;
380
+ /** Max accounts to try per request before giving up. */
381
+ maxAttempts?: number;
382
+ }
383
+ export declare function createWorkBuddyShim(options: WorkBuddyShimOptions): WorkBuddyShim;
384
+ //#endregion
385
+ //#region src/adapter.d.ts
386
+ /** Provider route this bundle owns. */
387
+ /** Provider route this bundle owns for the domestic (CN) gateway. */
388
+ export declare const WORKBUDDY_POOL_PROVIDER = "workbuddy-xdpool";
389
+ interface WorkBuddyAdapterOptions {
390
+ shim: WorkBuddyShim;
391
+ catalog: WorkBuddyCatalog;
392
+ providerId?: string;
393
+ displayName?: string;
394
+ }
395
+ /** What {@link createWorkBuddyAdapter} hands back. */
396
+ interface WorkBuddyAdapter {
397
+ providerId: string;
398
+ displayName: string;
399
+ adapter: PiAiAdapter;
400
+ /** Rebuild the pi-ai model list from the current catalog. */
401
+ buildModels: () => Model<Api>[];
402
+ /** Rebuild the adapter's provider snapshot; call after a catalog update. */
403
+ invalidate: () => void;
404
+ }
405
+ /**
406
+ * Assemble the adapter. `getModels` re-reads the live catalog, and every
407
+ * model's `baseUrl` is re-resolved per read so the shim's ephemeral port
408
+ * applies from the first snapshot after startup. Call only after `shim.ready`.
409
+ */
410
+ export declare function createWorkBuddyAdapter(options: WorkBuddyAdapterOptions): WorkBuddyAdapter;
411
+ //#endregion
412
+ //#region src/status.d.ts
413
+ /** One account's status row. */
414
+ interface AccountStatus {
415
+ id: string;
416
+ label: string;
417
+ nickname?: string;
418
+ domain: string;
419
+ /** ISO timestamp when the access token expires. */
420
+ expiresAt?: string;
421
+ /** Account-wide cooldown (every model blocked). */
422
+ cooling: boolean;
423
+ cooldownUntil?: string;
424
+ /** Per-model cooldowns active right now (modelId → ISO until); the account
425
+ * itself is not `cooling` while only some models are limited. */
426
+ modelCooldowns?: readonly {
427
+ modelId: string;
428
+ until: string;
429
+ }[];
430
+ rateLimitHits: number;
431
+ /** Read-only aggregated credit summary for the account. */
432
+ credits?: WorkBuddyCredits;
433
+ creditsError?: string;
434
+ sourcePath: string;
435
+ }
436
+ /** Whole-plugin status document. */
437
+ interface WorkBuddyStatus {
438
+ ok: boolean;
439
+ accounts: AccountStatus[];
440
+ activeAccountId?: string;
441
+ cooling: number;
442
+ models: {
443
+ id: string;
444
+ name: string;
445
+ multiplier?: number;
446
+ tags?: readonly string[];
447
+ }[];
448
+ shim: {
449
+ running: boolean;
450
+ baseUrl?: string;
451
+ };
452
+ }
453
+ interface StatusOptions {
454
+ pool: WorkBuddyAccountPool;
455
+ catalog: WorkBuddyCatalog;
456
+ client: WorkBuddyUpstreamClient;
457
+ shim?: {
458
+ running: boolean;
459
+ baseUrl?: string;
460
+ };
461
+ /** Query credits per account. Off for cheap diagnostics runs. */
462
+ includeCredits?: boolean;
463
+ }
464
+ /** Build the status document. Never throws. */
465
+ export declare function buildStatus(options: StatusOptions): Promise<WorkBuddyStatus>;
466
+ /** Format the status document for a terminal. */
467
+ export declare function formatStatus(status: WorkBuddyStatus): string;
468
+ /** Format the per-model credit multipliers. */
469
+ export declare function formatRates(status: WorkBuddyStatus): string;
470
+ //#endregion
471
+ //#region src/status-paths.d.ts
472
+ /**
473
+ * Node-free constants and types shared by the Host and browser halves of the
474
+ * WorkBuddy XD Pool settings card.
475
+ *
476
+ * Pool's runtime state already lives in `src/status.ts` (`buildStatus` /
477
+ * `WorkBuddyStatus`); this module only carves the cross-domain (Host→browser)
478
+ * JSON document into a shape that stays token-free and matches what the
479
+ * browser card renders. Route paths are plugin-owned and mounted on the Host's
480
+ * same-origin web server (see `src/web-status.ts`).
481
+ *
482
+ * @module dsh-workbuddy-xdpool/status-paths
483
+ */
484
+ /** Plugin-owned read-only pool status endpoint (account rows + models + shim). */
485
+ export declare const POOL_STATUS_PATH = "/plugins/dsh-workbuddy-xdpool/status";
486
+ /** Plugin-owned local account rescan endpoint (re-read desktop snapshots). */
487
+ export declare const POOL_RESCAN_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/rescan";
488
+ /** Plugin-owned cooldown reset endpoint (clear all 429 cooldowns). */
489
+ export declare const POOL_RESET_COOLDOWN_PATH = "/plugins/dsh-workbuddy-xdpool/cooldowns/reset";
490
+ /** Plugin-owned daily check-in action endpoint (claim today's reward). */
491
+ export declare const POOL_CHECKIN_PATH = "/plugins/dsh-workbuddy-xdpool/checkin";
492
+ /** Plugin-owned model-selection save endpoint (writes the settings section). */
493
+ export declare const POOL_MODELS_SAVE_PATH = "/plugins/dsh-workbuddy-xdpool/models/save";
494
+ /** One pool account's row, token-free. */
495
+ interface PoolWebAccount {
496
+ id: string;
497
+ label: string;
498
+ nickname?: string;
499
+ domain: string;
500
+ /** ISO timestamp; absent when the credential carries no expiry. */
501
+ expiresAt?: string;
502
+ /** Account-wide cooldown (every model blocked); only after a no-model penalize. */
503
+ cooling: boolean;
504
+ /** ISO timestamp when the account-wide 429 cooldown lifts; only while cooling. */
505
+ cooldownUntil?: string;
506
+ /**
507
+ * Per-model cooldowns currently active. The account is NOT `cooling` while a
508
+ * model is limited — its other models still serve — but each entry tells the
509
+ * card which model is out until when (e.g. `hy4-preview` cooling to 10:14,
510
+ * `hy3` normal).
511
+ */
512
+ modelCooldowns?: ReadonlyArray<{
513
+ modelId: string;
514
+ until: string;
515
+ }>;
516
+ rateLimitHits: number;
517
+ /** ISO timestamp of the last successful use (best-effort pool bookkeeping). */
518
+ lastUsedAt?: string;
519
+ /** Aggregated credit summary for the account, read-only. */
520
+ credits?: PoolWebCredits;
521
+ creditsError?: string;
522
+ /**
523
+ * Today's check-in state for this account, read-only. Present only when the
524
+ * per-account check-in probe succeeded and the program is active. The card
525
+ * renders one claim button per account, so a multi-account pool can collect
526
+ * every account's daily reward without switching accounts by hand.
527
+ */
528
+ checkin?: PoolWebCheckin;
529
+ checkinError?: string;
530
+ }
531
+ /** One credit package (as surfaced by the pool's upstream client), node-free. */
532
+ interface PoolWebCreditPackage {
533
+ packageName: string;
534
+ remain?: number;
535
+ size?: number;
536
+ /** CapacityType 4 — refreshed each cycle and never expires. */
537
+ monthly?: boolean;
538
+ /** Next cycle refresh point, ms. */
539
+ cycleRefreshMs?: number;
540
+ /** One-off expiry, ms. */
541
+ expiresAtMs?: number;
542
+ }
543
+ /** Aggregated credit answer the card renders under one account. */
544
+ interface PoolWebCredits {
545
+ total?: number;
546
+ packages: readonly PoolWebCreditPackage[];
547
+ /** Credits expiring within 3 days. */
548
+ expiringSoon?: number;
549
+ /** When the nearest package expires, ms. */
550
+ nearestExpiryMs?: number;
551
+ }
552
+ /**
553
+ * Daily check-in state the card renders under one account's credits. Mirrors
554
+ * the upstream activity endpoint, minus anything the browser does not need.
555
+ */
556
+ interface PoolWebCheckin {
557
+ /** The activity is running; a claim button is offered only while true. */
558
+ active: boolean;
559
+ /** Already collected today — the button renders as a done state. */
560
+ todayCheckedIn: boolean;
561
+ /** Consecutive days checked in. */
562
+ streakDays: number;
563
+ /** Credits a single day grants. */
564
+ dailyCredit: number;
565
+ /** Credits collected today (0 before claiming). */
566
+ todayCredit: number;
567
+ /** Today is a streak milestone day. */
568
+ isStreakDay: boolean;
569
+ /** The day count the next milestone lands on. */
570
+ nextStreakDay: number;
571
+ /** Bonus credits granted on a milestone day. */
572
+ streakBonusCredit: number;
573
+ }
574
+ /** Result of one claim, so the card can confirm what was collected. */
575
+ interface PoolWebCheckinClaim {
576
+ credit: number;
577
+ streakDays: number;
578
+ isStreakDay: boolean;
579
+ }
580
+ /** One model the pool exposes to DSH, with cost / free tags. */
581
+ interface PoolWebModel {
582
+ id: string;
583
+ name: string;
584
+ /** Relative credit cost, e.g. 0.79 for x0.79. */
585
+ multiplier?: number;
586
+ /** Upstream tags: free / limited-free / night-discount. */
587
+ tags?: readonly string[];
588
+ /** Effective image support after the user's per-model toggle. */
589
+ supportsImages: boolean;
590
+ /** Effective context window after the user's budget cap. */
591
+ contextWindow: number;
592
+ /** The window the upstream advertises, before any cap. */
593
+ nativeContextWindow: number;
594
+ /** Upstream output ceiling, so the card can show both limits. */
595
+ maxOutputTokens: number;
596
+ /** Thinking levels the upstream declares, when it declares any. */
597
+ supportedEfforts?: readonly string[];
598
+ /** Whether this model is currently enabled in the picker. */
599
+ enabled: boolean;
600
+ }
601
+ /** The user's saved model selection, echoed back so the card can diff a draft. */
602
+ interface PoolWebModelSelection {
603
+ /** Absent = every model is enabled. */
604
+ enabledModelIds?: readonly string[];
605
+ /** Absent = each model follows its upstream image capability. */
606
+ imageModelIds?: readonly string[];
607
+ /** Per-model context-window cap, keyed by model id. */
608
+ contextBudgets?: Readonly<Record<string, number>>;
609
+ }
610
+ /** The JSON document the pool card renders. */
611
+ interface PoolWebStatus {
612
+ ok: boolean;
613
+ accounts: readonly PoolWebAccount[];
614
+ /** The next account the pool would use (rotation cursor). */
615
+ activeAccountId?: string;
616
+ cooling: number;
617
+ models: readonly PoolWebModel[];
618
+ /** The saved selection the card diffs its draft against. */
619
+ selection: PoolWebModelSelection;
620
+ /** Which region this document describes. */
621
+ region: PoolRegion;
622
+ /** Every region holding at least one account, in display order. */
623
+ regions: readonly PoolRegion[];
624
+ shim: {
625
+ running: boolean;
626
+ baseUrl?: string;
627
+ };
628
+ }
629
+ /**
630
+ * The two gateways, matching the provider ids the host registers. `cn` is the
631
+ * domestic gateway (`copilot.tencent.com` / `codebuddy.cn`); `global` is the
632
+ * international one (`workbuddy.ai`).
633
+ */
634
+ type PoolRegion = 'cn' | 'global';
635
+ //#endregion
636
+ //#region src/index.d.ts
637
+ /** Stable Cordis plugin name. */
638
+ export declare const name = "llm-workbuddy-xdpool";
639
+ /** The model registry required before the provider can register. */
640
+ export declare const inject: string[];
641
+ /**
642
+ * Settings namespace for the WorkBuddy XD Pool card. Registering a section here
643
+ * is what makes the provider appear on the Models settings page and causes the
644
+ * Host to mount the plugin's client card under Plugin configuration — exactly
645
+ * the mechanism the single-account connector uses.
646
+ */
647
+ export declare const WORKBUDDY_POOL_SETTINGS_NS: SettingsNamespace;
648
+ /** Plugin configuration. */
649
+ export interface Config {
650
+ /** Explicit WorkBuddy desktop auth-file path override. */
651
+ authFile?: string;
652
+ /** Rate-limit cooldown per account, milliseconds. */
653
+ cooldownMs?: number;
654
+ /**
655
+ * Model ids enabled in the picker. Absent means "every model the catalog
656
+ * advertises" — an unconfigured install should never present an empty model
657
+ * list just because the key is missing.
658
+ */
659
+ enabledModelIds?: string[];
660
+ /**
661
+ * Model ids that additionally accept image input. Absent means "follow the
662
+ * upstream capability flag"; an explicit list is authoritative for the models
663
+ * it mentions and leaves the rest to the catalog.
664
+ */
665
+ imageModelIds?: string[];
666
+ /**
667
+ * Per-model context-window override, keyed by model id. The upstream can
668
+ * advertise more than DSH wants to hand a single turn, so the card lets the
669
+ * user cap a model without touching the catalog.
670
+ */
671
+ contextBudgets?: Partial<Record<string, number>>;
672
+ }
673
+ /** Upper bound the card offers as the "default" context window, in tokens. */
674
+ export declare const DEFAULT_CONTEXT_BUDGET = 200000;
675
+ /**
676
+ * Plugin configuration schema.
677
+ *
678
+ * Mirrors the shape the settings section stores. Every field carries a default
679
+ * so a config that never touched the card still folds cleanly: a field whose
680
+ * schema declares no default is read as absent by the settings fold. That is
681
+ * also why `contextBudgets` is a real dictionary (`z.dict`) - an open object
682
+ * schema reads as "an object with no fields" and the fold then throws while
683
+ * the provider row is rendered.
684
+ */
685
+ export declare const Config: z<Config>;
686
+ /** Everything the CLI needs from a live plugin instance. */
687
+ export interface WorkBuddyPoolApi {
688
+ pool: WorkBuddyAccountPool;
689
+ catalog: WorkBuddyCatalog;
690
+ client: WorkBuddyUpstreamClient;
691
+ shim: WorkBuddyShim;
692
+ adapter: WorkBuddyAdapter | undefined;
693
+ rescan(): Promise<number>;
694
+ status(includeCredits?: boolean): Promise<Awaited<ReturnType<typeof buildStatus>>>;
695
+ resetCooldowns(): void;
696
+ }
697
+ /** The live API, or undefined when the plugin has not applied yet. */
698
+ export declare function currentApi(): WorkBuddyPoolApi | undefined;
699
+ /** Test seam: install an API instance without booting cordis. */
700
+ export declare function setApi(next: WorkBuddyPoolApi | undefined): void;
701
+ /** Assemble the runtime objects without registering anything. */
702
+ export declare function createCore(logger?: {
703
+ warn(...args: unknown[]): void;
704
+ }): {
705
+ pool: WorkBuddyAccountPool;
706
+ catalog: WorkBuddyCatalog;
707
+ client: WorkBuddyUpstreamClient;
708
+ };
709
+ /**
710
+ * Start the loopback endpoint, register the `workbuddy-xdpool` provider, and
711
+ * discover accounts. The provider registers only after `shim.ready` resolves,
712
+ * because its models read the shim origin at construction time.
713
+ */
714
+ export declare function apply(ctx: Context, config?: Config): void;
715
+ //#endregion
716
+ export type { AccountStatus, Context, ModelSelection, PoolWebCheckin, PoolWebCheckinClaim, PoolWebModel, PoolWebModelSelection, PoolWebStatus, UpstreamErrorKind, WorkBuddyAccount, WorkBuddyAdapter, WorkBuddyCredential, WorkBuddyModelInfo, WorkBuddyShim, WorkBuddyStatus };