opencode-codex-multi-account 0.2.7 → 0.2.9

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/dist/index.js CHANGED
@@ -1,1860 +1,12 @@
1
1
  // src/index.ts
2
2
  import { tool } from "@opencode-ai/plugin";
3
+ import { migrateFromAuthJson } from "opencode-multi-account-core";
3
4
 
4
- // ../multi-account-core/src/account-manager.ts
5
- import { randomUUID } from "node:crypto";
6
-
7
- // ../multi-account-core/src/claims.ts
8
- import { promises as fs2 } from "node:fs";
9
- import { randomBytes as randomBytes2 } from "node:crypto";
10
- import { dirname as dirname2, join as join3 } from "node:path";
11
-
12
- // ../multi-account-core/src/utils.ts
13
- import { join as join2 } from "node:path";
14
- import { homedir as homedir2 } from "node:os";
15
-
16
- // ../multi-account-core/src/config.ts
17
- import { promises as fs } from "node:fs";
18
- import { randomBytes } from "node:crypto";
19
- import { dirname, join } from "node:path";
20
- import { homedir } from "node:os";
21
- import * as v2 from "valibot";
22
-
23
- // ../multi-account-core/src/types.ts
24
- import * as v from "valibot";
25
- var OAuthCredentialsSchema = v.object({
26
- type: v.literal("oauth"),
27
- refresh: v.string(),
28
- access: v.string(),
29
- expires: v.number()
30
- });
31
- var UsageLimitEntrySchema = v.object({
32
- utilization: v.number(),
33
- resets_at: v.nullable(v.string())
34
- });
35
- var UsageLimitsSchema = v.object({
36
- five_hour: v.optional(v.nullable(UsageLimitEntrySchema), null),
37
- seven_day: v.optional(v.nullable(UsageLimitEntrySchema), null),
38
- seven_day_sonnet: v.optional(v.nullable(UsageLimitEntrySchema), null)
39
- });
40
- var CredentialRefreshPatchSchema = v.object({
41
- accessToken: v.string(),
42
- expiresAt: v.number(),
43
- refreshToken: v.optional(v.string()),
44
- uuid: v.optional(v.string()),
45
- accountId: v.optional(v.string()),
46
- email: v.optional(v.string())
47
- });
48
- var StoredAccountSchema = v.object({
49
- uuid: v.optional(v.string()),
50
- accountId: v.optional(v.string()),
51
- label: v.optional(v.string()),
52
- email: v.optional(v.string()),
53
- planTier: v.optional(v.string(), ""),
54
- refreshToken: v.string(),
55
- accessToken: v.optional(v.string()),
56
- expiresAt: v.optional(v.number()),
57
- addedAt: v.number(),
58
- lastUsed: v.number(),
59
- enabled: v.optional(v.boolean(), true),
60
- rateLimitResetAt: v.optional(v.number()),
61
- cachedUsage: v.optional(UsageLimitsSchema),
62
- cachedUsageAt: v.optional(v.number()),
63
- consecutiveAuthFailures: v.optional(v.number(), 0),
64
- isAuthDisabled: v.optional(v.boolean(), false),
65
- authDisabledReason: v.optional(v.string())
66
- });
67
- var AccountStorageSchema = v.object({
68
- version: v.literal(1),
69
- accounts: v.optional(v.array(StoredAccountSchema), []),
70
- activeAccountUuid: v.optional(v.string())
71
- });
72
- var AccountSelectionStrategySchema = v.picklist(["sticky", "round-robin", "hybrid"]);
73
- var PluginConfigSchema = v.object({
74
- account_selection_strategy: v.optional(AccountSelectionStrategySchema, "sticky"),
75
- cross_process_claims: v.optional(v.boolean(), true),
76
- soft_quota_threshold_percent: v.optional(v.pipe(v.number(), v.minValue(0), v.maxValue(100)), 100),
77
- rate_limit_min_backoff_ms: v.optional(v.pipe(v.number(), v.minValue(0)), 3e4),
78
- default_retry_after_ms: v.optional(v.pipe(v.number(), v.minValue(0)), 6e4),
79
- max_consecutive_auth_failures: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1)), 3),
80
- token_failure_backoff_ms: v.optional(v.pipe(v.number(), v.minValue(0)), 3e4),
81
- proactive_refresh: v.optional(v.boolean(), true),
82
- proactive_refresh_buffer_seconds: v.optional(v.pipe(v.number(), v.minValue(60)), 1800),
83
- proactive_refresh_interval_seconds: v.optional(v.pipe(v.number(), v.minValue(30)), 300),
84
- quiet_mode: v.optional(v.boolean(), false),
85
- debug: v.optional(v.boolean(), false)
86
- });
87
- var TokenRefreshError = class _TokenRefreshError extends Error {
88
- status;
89
- permanent;
90
- constructor(permanent, status) {
91
- super(status === void 0 ? "Token refresh failed" : `Token refresh failed: ${status}`);
92
- this.name = "TokenRefreshError";
93
- this.status = status;
94
- this.permanent = permanent;
95
- Object.setPrototypeOf(this, _TokenRefreshError.prototype);
96
- }
97
- };
98
- function isTokenRefreshError(error) {
99
- if (error instanceof TokenRefreshError) return true;
100
- if (!(error instanceof Error)) return false;
101
- const candidate = error;
102
- return candidate.name === "TokenRefreshError" && typeof candidate.permanent === "boolean" && (candidate.status === void 0 || typeof candidate.status === "number");
103
- }
104
-
105
- // ../multi-account-core/src/config.ts
106
- var DEFAULT_CONFIG_FILENAME = "multiauth-config.json";
107
- var DEFAULT_CONFIG = v2.parse(PluginConfigSchema, {});
108
- var configFilename = DEFAULT_CONFIG_FILENAME;
109
- var cachedConfig = null;
110
- var externalConfigGetter = null;
111
- function getConfigDir() {
112
- return process.env.OPENCODE_CONFIG_DIR || join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "opencode");
113
- }
114
- function getConfigPath() {
115
- return join(getConfigDir(), configFilename);
116
- }
117
- function parseConfig(raw) {
118
- const result = v2.safeParse(PluginConfigSchema, raw);
119
- return result.success ? result.output : DEFAULT_CONFIG;
120
- }
121
- function initCoreConfig(filename) {
122
- configFilename = filename || DEFAULT_CONFIG_FILENAME;
123
- cachedConfig = null;
124
- }
125
- async function loadConfig() {
126
- if (cachedConfig) return cachedConfig;
127
- const path = getConfigPath();
128
- try {
129
- const content = await fs.readFile(path, "utf-8");
130
- cachedConfig = parseConfig(JSON.parse(content));
131
- } catch {
132
- cachedConfig = DEFAULT_CONFIG;
133
- }
134
- return cachedConfig;
135
- }
136
- function getConfig() {
137
- if (cachedConfig) return cachedConfig;
138
- if (externalConfigGetter && externalConfigGetter !== getConfig) {
139
- try {
140
- return parseConfig(externalConfigGetter());
141
- } catch {
142
- return DEFAULT_CONFIG;
143
- }
144
- }
145
- return DEFAULT_CONFIG;
146
- }
147
- function setConfigGetter(getter) {
148
- if (getter === getConfig) {
149
- return;
150
- }
151
- externalConfigGetter = getter;
152
- }
153
- async function updateConfigField(key, value) {
154
- const path = getConfigPath();
155
- let existing = {};
156
- try {
157
- const content2 = await fs.readFile(path, "utf-8");
158
- existing = JSON.parse(content2);
159
- } catch {
160
- }
161
- existing[key] = value;
162
- await fs.mkdir(dirname(path), { recursive: true });
163
- const content = `${JSON.stringify(existing, null, 2)}
164
- `;
165
- const tempPath = `${path}.${randomBytes(8).toString("hex")}.tmp`;
166
- try {
167
- await fs.writeFile(tempPath, content, "utf-8");
168
- await fs.rename(tempPath, path);
169
- } catch (error) {
170
- try {
171
- await fs.unlink(tempPath);
172
- } catch {
173
- }
174
- throw error;
175
- }
176
- cachedConfig = null;
177
- await loadConfig();
178
- }
179
-
180
- // ../multi-account-core/src/utils.ts
181
- function getConfigDir2() {
182
- return process.env.OPENCODE_CONFIG_DIR || join2(process.env.XDG_CONFIG_HOME || join2(homedir2(), ".config"), "opencode");
183
- }
184
- function getErrorCode(error) {
185
- if (typeof error !== "object" || error === null || !("code" in error)) {
186
- return void 0;
187
- }
188
- const code = error.code;
189
- return typeof code === "string" ? code : void 0;
190
- }
191
- function formatWaitTime(ms) {
192
- const totalSeconds = Math.ceil(ms / 1e3);
193
- if (totalSeconds < 60) return `${totalSeconds}s`;
194
- const days = Math.floor(totalSeconds / 86400);
195
- const hours = Math.floor(totalSeconds % 86400 / 3600);
196
- const minutes = Math.floor(totalSeconds % 3600 / 60);
197
- const seconds = totalSeconds % 60;
198
- const parts = [];
199
- if (days > 0) parts.push(`${days}d`);
200
- if (hours > 0) parts.push(`${hours}h`);
201
- if (minutes > 0) parts.push(`${minutes}m`);
202
- if (seconds > 0 && days === 0) parts.push(`${seconds}s`);
203
- return parts.join(" ") || "0s";
204
- }
205
- function getAccountLabel(account) {
206
- if (account.label) return account.label;
207
- if (account.email) return account.email;
208
- if (account.uuid) return `Account (${account.uuid.slice(0, 8)})`;
209
- return `Account ${account.index + 1}`;
210
- }
211
- function sleep(ms) {
212
- return new Promise((resolve) => setTimeout(resolve, ms));
213
- }
214
- async function showToast(client, message, variant) {
215
- if (getConfig().quiet_mode) return;
216
- try {
217
- await client.tui.showToast({ body: { message, variant } });
218
- } catch {
219
- }
220
- }
221
- function debugLog(client, message, extra) {
222
- if (!getConfig().debug) return;
223
- client.app.log({
224
- body: { service: "claude-multiauth", level: "debug", message, extra }
225
- }).catch(() => {
226
- });
227
- }
228
- function createMinimalClient() {
229
- return {
230
- auth: {
231
- set: async () => {
232
- }
233
- },
234
- tui: {
235
- showToast: async () => {
236
- }
237
- },
238
- app: {
239
- log: async () => {
240
- }
241
- }
242
- };
243
- }
244
- function getClearedOAuthBody() {
245
- return {
246
- type: "oauth",
247
- refresh: "",
248
- access: "",
249
- expires: 0
250
- };
251
- }
252
-
253
- // ../multi-account-core/src/claims.ts
254
- var CLAIMS_FILENAME = "multiauth-claims.json";
255
- var CLAIM_EXPIRY_MS = 6e4;
256
- function getClaimsPath() {
257
- return join3(getConfigDir2(), CLAIMS_FILENAME);
258
- }
259
- function isClaimShape(value) {
260
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
261
- const claim = value;
262
- return typeof claim.pid === "number" && Number.isInteger(claim.pid) && claim.pid > 0 && typeof claim.at === "number" && Number.isFinite(claim.at);
263
- }
264
- function parseClaims(raw) {
265
- let parsed;
266
- try {
267
- parsed = JSON.parse(raw);
268
- } catch {
269
- return {};
270
- }
271
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
272
- return {};
273
- }
274
- const claims = {};
275
- for (const [accountId, claim] of Object.entries(parsed)) {
276
- if (isClaimShape(claim)) {
277
- claims[accountId] = claim;
278
- }
279
- }
280
- return claims;
281
- }
282
- function isProcessAlive(pid) {
283
- try {
284
- process.kill(pid, 0);
285
- return true;
286
- } catch {
287
- return false;
288
- }
289
- }
290
- function cleanClaims(claims, now) {
291
- const cleaned = {};
292
- let changed = false;
293
- for (const [accountId, claim] of Object.entries(claims)) {
294
- const expiredByTime = now - claim.at > CLAIM_EXPIRY_MS;
295
- const zombieClaim = !isProcessAlive(claim.pid);
296
- if (expiredByTime || zombieClaim) {
297
- changed = true;
298
- continue;
299
- }
300
- cleaned[accountId] = claim;
301
- }
302
- return { cleaned, changed };
303
- }
304
- async function writeClaimsFile(claims) {
305
- const path = getClaimsPath();
306
- const tempPath = `${path}.${randomBytes2(6).toString("hex")}.tmp`;
307
- await fs2.mkdir(dirname2(path), { recursive: true });
308
- try {
309
- await fs2.writeFile(tempPath, JSON.stringify(claims, null, 2), { encoding: "utf-8", mode: 384 });
310
- await fs2.rename(tempPath, path);
311
- } catch (error) {
312
- try {
313
- await fs2.unlink(tempPath);
314
- } catch {
315
- }
316
- throw error;
317
- }
318
- }
319
- async function readClaims() {
320
- try {
321
- const data = await fs2.readFile(getClaimsPath(), "utf-8");
322
- const parsed = parseClaims(data);
323
- const now = Date.now();
324
- const { cleaned, changed } = cleanClaims(parsed, now);
325
- if (changed) {
326
- try {
327
- await writeClaimsFile(cleaned);
328
- } catch {
329
- }
330
- }
331
- return cleaned;
332
- } catch {
333
- return {};
334
- }
335
- }
336
- async function writeClaim(accountId) {
337
- const now = Date.now();
338
- const claims = await readClaims();
339
- const { cleaned } = cleanClaims(claims, now);
340
- cleaned[accountId] = { pid: process.pid, at: now };
341
- try {
342
- await writeClaimsFile(cleaned);
343
- } catch {
344
- }
345
- }
346
- function isClaimedByOther(claims, accountId) {
347
- if (!accountId) return false;
348
- const claim = claims[accountId];
349
- if (!claim) return false;
350
- if (Date.now() - claim.at > CLAIM_EXPIRY_MS) return false;
351
- if (!isProcessAlive(claim.pid)) return false;
352
- return claim.pid !== process.pid;
353
- }
354
-
355
- // ../multi-account-core/src/account-manager.ts
356
- var STARTUP_REFRESH_CONCURRENCY = 3;
357
- var RECENT_429_COOLDOWN_MS = 3e4;
358
- var HYBRID_SWITCH_MARGIN = 40;
359
- function createAccountManagerForProvider(dependencies) {
360
- const {
361
- providerAuthId,
362
- isTokenExpired: isTokenExpired2,
363
- refreshToken: refreshToken2
364
- } = dependencies;
365
- return class AccountManager2 {
366
- constructor(store) {
367
- this.store = store;
368
- }
369
- cached = [];
370
- activeAccountUuid;
371
- client = null;
372
- runtimeFactory = null;
373
- roundRobinCursor = 0;
374
- last429Map = /* @__PURE__ */ new Map();
375
- static async create(store, currentAuth, client) {
376
- const manager = new AccountManager2(store);
377
- await manager.initialize(currentAuth, client);
378
- return manager;
379
- }
380
- async initialize(currentAuth, client) {
381
- if (client) this.client = client;
382
- const storage = await this.store.load();
383
- if (storage.accounts.length > 0) {
384
- this.cached = storage.accounts.map((account, index) => this.toManagedAccount(account, index));
385
- this.activeAccountUuid = storage.activeAccountUuid;
386
- if (!this.getActiveAccount() && this.cached.length > 0) {
387
- this.activeAccountUuid = this.cached[0].uuid;
388
- }
389
- return;
390
- }
391
- if (currentAuth.refresh) {
392
- const newAccount = this.createNewAccount(currentAuth, Date.now());
393
- await this.store.addAccount(newAccount);
394
- await this.store.setActiveUuid(newAccount.uuid);
395
- this.cached = [this.toManagedAccount(newAccount, 0)];
396
- this.activeAccountUuid = newAccount.uuid;
397
- }
398
- }
399
- async refresh() {
400
- const storage = await this.store.load();
401
- this.cached = storage.accounts.map((account, index) => this.toManagedAccount(account, index));
402
- if (storage.activeAccountUuid) {
403
- this.activeAccountUuid = storage.activeAccountUuid;
404
- }
405
- }
406
- toManagedAccount(storedAccount, index) {
407
- return {
408
- index,
409
- uuid: storedAccount.uuid,
410
- accountId: storedAccount.accountId,
411
- label: storedAccount.label,
412
- email: storedAccount.email,
413
- planTier: storedAccount.planTier,
414
- refreshToken: storedAccount.refreshToken,
415
- accessToken: storedAccount.accessToken,
416
- expiresAt: storedAccount.expiresAt,
417
- addedAt: storedAccount.addedAt,
418
- lastUsed: storedAccount.lastUsed,
419
- enabled: storedAccount.enabled,
420
- rateLimitResetAt: storedAccount.rateLimitResetAt,
421
- cachedUsage: storedAccount.cachedUsage,
422
- cachedUsageAt: storedAccount.cachedUsageAt,
423
- consecutiveAuthFailures: storedAccount.consecutiveAuthFailures,
424
- isAuthDisabled: storedAccount.isAuthDisabled,
425
- authDisabledReason: storedAccount.authDisabledReason,
426
- last429At: storedAccount.uuid ? this.last429Map.get(storedAccount.uuid) : void 0
427
- };
428
- }
429
- createNewAccount(auth, now) {
430
- return {
431
- uuid: randomUUID(),
432
- refreshToken: auth.refresh,
433
- accessToken: auth.access,
434
- expiresAt: auth.expires,
435
- addedAt: now,
436
- lastUsed: now,
437
- enabled: true,
438
- planTier: "",
439
- consecutiveAuthFailures: 0,
440
- isAuthDisabled: false
441
- };
442
- }
443
- getAccountCount() {
444
- return this.getEligibleAccounts().length;
445
- }
446
- getAccounts() {
447
- return [...this.cached];
448
- }
449
- getActiveAccount() {
450
- if (this.activeAccountUuid) {
451
- return this.cached.find((account) => account.uuid === this.activeAccountUuid) ?? null;
452
- }
453
- return this.cached[0] ?? null;
454
- }
455
- setClient(client) {
456
- this.client = client;
457
- }
458
- setRuntimeFactory(factory) {
459
- this.runtimeFactory = factory;
460
- }
461
- getEligibleAccounts() {
462
- return this.cached.filter((account) => account.uuid && account.enabled && !account.isAuthDisabled);
463
- }
464
- exceedsSoftQuota(account) {
465
- const threshold = getConfig().soft_quota_threshold_percent;
466
- if (threshold >= 100) return false;
467
- const usage = account.cachedUsage;
468
- if (!usage) return false;
469
- const tiers = [usage.five_hour, usage.seven_day];
470
- return tiers.some((tier) => tier != null && tier.utilization >= threshold);
471
- }
472
- hasAnyUsableAccount() {
473
- return this.getEligibleAccounts().length > 0;
474
- }
475
- isRateLimited(account) {
476
- if (account.rateLimitResetAt && Date.now() < account.rateLimitResetAt) {
477
- return true;
478
- }
479
- return this.isUsageExhausted(account);
480
- }
481
- isUsageExhausted(account) {
482
- const usage = account.cachedUsage;
483
- if (!usage) return false;
484
- const now = Date.now();
485
- const tiers = [usage.five_hour, usage.seven_day];
486
- return tiers.some(
487
- (tier) => tier != null && tier.utilization >= 100 && tier.resets_at != null && Date.parse(tier.resets_at) > now
488
- );
489
- }
490
- clearExpiredRateLimits() {
491
- const now = Date.now();
492
- for (const account of this.cached) {
493
- if (account.rateLimitResetAt && now >= account.rateLimitResetAt) {
494
- account.rateLimitResetAt = void 0;
495
- }
496
- }
497
- }
498
- getMinWaitTime() {
499
- const eligible = this.getEligibleAccounts();
500
- const available = eligible.filter((account) => !this.isRateLimited(account));
501
- if (available.length > 0) return 0;
502
- const now = Date.now();
503
- const waits = [];
504
- for (const account of eligible) {
505
- if (account.rateLimitResetAt) {
506
- const ms = account.rateLimitResetAt - now;
507
- if (ms > 0) waits.push(ms);
508
- }
509
- const usageResetMs = this.getUsageResetMs(account);
510
- if (usageResetMs !== null && usageResetMs > 0) {
511
- waits.push(usageResetMs);
512
- }
513
- }
514
- return waits.length > 0 ? Math.min(...waits) : 0;
515
- }
516
- getUsageResetMs(account) {
517
- const usage = account.cachedUsage;
518
- if (!usage) return null;
519
- const now = Date.now();
520
- const candidates = [];
521
- const tiers = [usage.five_hour, usage.seven_day];
522
- for (const tier of tiers) {
523
- if (tier != null && tier.utilization >= 100 && tier.resets_at != null) {
524
- const ms = Date.parse(tier.resets_at) - now;
525
- if (ms > 0) candidates.push(ms);
526
- }
527
- }
528
- return candidates.length > 0 ? Math.min(...candidates) : null;
529
- }
530
- async selectAccount() {
531
- await this.refresh();
532
- this.clearExpiredRateLimits();
533
- const eligible = this.getEligibleAccounts();
534
- if (eligible.length === 0) return null;
535
- const config = getConfig();
536
- const claims = config.cross_process_claims ? await readClaims() : {};
537
- const strategy = config.account_selection_strategy;
538
- let selected;
539
- switch (strategy) {
540
- case "round-robin":
541
- selected = this.selectRoundRobin(eligible, claims);
542
- break;
543
- case "hybrid":
544
- selected = this.selectHybrid(eligible, claims);
545
- break;
546
- case "sticky":
547
- default:
548
- selected = this.selectSticky(eligible, claims);
549
- break;
550
- }
551
- if (selected?.uuid) {
552
- this.activeAccountUuid = selected.uuid;
553
- this.store.setActiveUuid(selected.uuid).catch(() => {
554
- });
555
- }
556
- if (config.cross_process_claims && selected?.uuid) {
557
- writeClaim(selected.uuid).catch(() => {
558
- });
559
- }
560
- return selected;
561
- }
562
- isUsable(account) {
563
- return !this.isRateLimited(account) && !this.isInRecentCooldown(account) && !this.exceedsSoftQuota(account);
564
- }
565
- isInRecentCooldown(account) {
566
- if (!account.last429At) return false;
567
- return Date.now() - account.last429At < RECENT_429_COOLDOWN_MS;
568
- }
569
- fallbackNotRateLimited(eligible) {
570
- const account = eligible.find((candidate) => !this.isRateLimited(candidate));
571
- if (account) {
572
- this.activateAccount(account);
573
- return account;
574
- }
575
- return null;
576
- }
577
- selectSticky(eligible, claims) {
578
- const current = this.getActiveAccount();
579
- if (current?.enabled && !current.isAuthDisabled && this.isUsable(current)) {
580
- this.activateAccount(current);
581
- return current;
582
- }
583
- const unclaimed = eligible.find(
584
- (account) => this.isUsable(account) && !isClaimedByOther(claims, account.uuid)
585
- );
586
- if (unclaimed) {
587
- this.activateAccount(unclaimed);
588
- return unclaimed;
589
- }
590
- const available = eligible.find((account) => this.isUsable(account));
591
- if (available) {
592
- this.activateAccount(available);
593
- return available;
594
- }
595
- return this.fallbackNotRateLimited(eligible);
596
- }
597
- selectRoundRobin(eligible, claims) {
598
- for (let i = 0; i < eligible.length; i++) {
599
- const index = (this.roundRobinCursor + i) % eligible.length;
600
- const account = eligible[index];
601
- if (this.isUsable(account) && !isClaimedByOther(claims, account.uuid)) {
602
- this.roundRobinCursor = (index + 1) % eligible.length;
603
- this.activateAccount(account);
604
- return account;
605
- }
606
- }
607
- for (let i = 0; i < eligible.length; i++) {
608
- const index = (this.roundRobinCursor + i) % eligible.length;
609
- const account = eligible[index];
610
- if (this.isUsable(account)) {
611
- this.roundRobinCursor = (index + 1) % eligible.length;
612
- this.activateAccount(account);
613
- return account;
614
- }
615
- }
616
- return this.fallbackNotRateLimited(eligible);
617
- }
618
- selectHybrid(eligible, claims) {
619
- const usable = eligible.filter((account) => this.isUsable(account));
620
- const pool = usable.length > 0 ? usable : eligible.filter((account) => !this.isRateLimited(account));
621
- if (pool.length === 0) return null;
622
- const activeUuid = this.activeAccountUuid;
623
- let best = pool[0];
624
- let bestScore = this.calculateHybridScore(best, best.uuid === activeUuid, claims);
625
- for (let i = 1; i < pool.length; i++) {
626
- const account = pool[i];
627
- const score = this.calculateHybridScore(account, account.uuid === activeUuid, claims);
628
- if (score > bestScore) {
629
- best = account;
630
- bestScore = score;
631
- }
632
- }
633
- const current = pool.find((account) => account.uuid === activeUuid);
634
- if (current && current !== best) {
635
- const currentScore = this.calculateHybridScore(current, true, claims);
636
- const bestWithoutStickiness = this.calculateHybridScore(best, false, claims);
637
- if (bestWithoutStickiness <= currentScore + HYBRID_SWITCH_MARGIN) {
638
- this.activateAccount(current);
639
- return current;
640
- }
641
- }
642
- this.activateAccount(best);
643
- return best;
644
- }
645
- calculateHybridScore(account, isActive, claims) {
646
- const maxUtilization = Math.min(100, Math.max(0, this.getMaxUtilization(account)));
647
- const usageScore = (100 - maxUtilization) / 100 * 450;
648
- const maxFailures = Math.max(1, getConfig().max_consecutive_auth_failures);
649
- const healthScore = Math.max(0, (maxFailures - account.consecutiveAuthFailures) / maxFailures * 250);
650
- const secondsSinceUsed = (Date.now() - account.lastUsed) / 1e3;
651
- const freshnessScore = Math.min(secondsSinceUsed, 900) / 900 * 60;
652
- const stickinessBonus = isActive ? 120 : 0;
653
- const claimPenalty = isClaimedByOther(claims, account.uuid) ? -200 : 0;
654
- return usageScore + healthScore + freshnessScore + stickinessBonus + claimPenalty;
655
- }
656
- getMaxUtilization(account) {
657
- const usage = account.cachedUsage;
658
- if (!usage) return 65;
659
- const tiers = [usage.five_hour, usage.seven_day];
660
- const utilizations = tiers.filter((tier) => tier != null).map((tier) => tier.utilization);
661
- return utilizations.length > 0 ? Math.max(...utilizations) : 65;
662
- }
663
- activateAccount(account) {
664
- this.activeAccountUuid = account.uuid;
665
- account.lastUsed = Date.now();
666
- }
667
- async markRateLimited(uuid, backoffMs) {
668
- const effectiveBackoff = backoffMs ?? getConfig().rate_limit_min_backoff_ms;
669
- this.last429Map.set(uuid, Date.now());
670
- await this.store.mutateAccount(uuid, (account) => {
671
- account.rateLimitResetAt = Date.now() + effectiveBackoff;
672
- });
673
- }
674
- async markRevoked(uuid) {
675
- await this.removeAccountByUuid(uuid);
676
- }
677
- async markSuccess(uuid) {
678
- this.last429Map.delete(uuid);
679
- await this.store.mutateAccount(uuid, (account) => {
680
- account.rateLimitResetAt = void 0;
681
- account.consecutiveAuthFailures = 0;
682
- account.lastUsed = Date.now();
683
- });
684
- }
685
- syncToOpenCode(account) {
686
- if (!this.client || !account.accessToken || !account.expiresAt) return;
687
- this.client.auth.set({
688
- path: { id: providerAuthId },
689
- body: {
690
- type: "oauth",
691
- refresh: account.refreshToken,
692
- access: account.accessToken,
693
- expires: account.expiresAt
694
- }
695
- }).catch(() => {
696
- });
697
- }
698
- async clearOpenCodeAuthIfNoAccountsRemain() {
699
- if (!this.client) return;
700
- const storage = await this.store.load();
701
- if (storage.accounts.length > 0) return;
702
- await this.client.auth.set({
703
- path: { id: providerAuthId },
704
- body: getClearedOAuthBody()
705
- }).catch(() => {
706
- });
707
- }
708
- async removeAccountByUuid(uuid) {
709
- const removed = await this.store.removeAccount(uuid);
710
- if (!removed) return;
711
- this.last429Map.delete(uuid);
712
- this.runtimeFactory?.invalidate(uuid);
713
- await this.refresh();
714
- await this.clearOpenCodeAuthIfNoAccountsRemain();
715
- }
716
- async markAuthFailure(uuid, result) {
717
- if (!result.ok && result.permanent) {
718
- await this.removeAccountByUuid(uuid);
719
- return;
720
- }
721
- await this.store.mutateStorage((storage) => {
722
- const account = storage.accounts.find((entry) => entry.uuid === uuid);
723
- if (!account) return;
724
- account.consecutiveAuthFailures = (account.consecutiveAuthFailures ?? 0) + 1;
725
- const maxFailures = getConfig().max_consecutive_auth_failures;
726
- const usableCount = storage.accounts.filter(
727
- (entry) => entry.enabled && !entry.isAuthDisabled && entry.uuid !== uuid
728
- ).length;
729
- if (account.consecutiveAuthFailures >= maxFailures && usableCount > 0) {
730
- account.isAuthDisabled = true;
731
- account.authDisabledReason = `${maxFailures} consecutive auth failures`;
732
- }
733
- });
734
- }
735
- async applyUsageCache(uuid, usage) {
736
- await this.store.mutateAccount(uuid, (account) => {
737
- const now = Date.now();
738
- const exhaustedTierResetTimes = [usage.five_hour, usage.seven_day].flatMap((tier) => {
739
- if (tier == null || tier.utilization < 100 || tier.resets_at == null) {
740
- return [];
741
- }
742
- return [Date.parse(tier.resets_at)];
743
- }).filter((resetAt) => Number.isFinite(resetAt) && resetAt > now);
744
- account.cachedUsage = usage;
745
- account.cachedUsageAt = Date.now();
746
- account.rateLimitResetAt = exhaustedTierResetTimes.length > 0 ? Math.min(...exhaustedTierResetTimes) : void 0;
747
- });
748
- }
749
- async applyProfileCache(uuid, profile) {
750
- await this.store.mutateAccount(uuid, (account) => {
751
- account.email = profile.email ?? account.email;
752
- account.planTier = profile.planTier;
753
- });
754
- }
755
- async ensureValidToken(uuid, client) {
756
- const credentials = await this.store.readCredentials(uuid);
757
- if (!credentials) return { ok: false, permanent: true };
758
- if (credentials.accessToken && credentials.expiresAt && !isTokenExpired2(credentials)) {
759
- return {
760
- ok: true,
761
- patch: { accessToken: credentials.accessToken, expiresAt: credentials.expiresAt }
762
- };
763
- }
764
- const result = await refreshToken2(credentials.refreshToken, uuid, client);
765
- if (!result.ok) return result;
766
- const updated = await this.store.mutateAccount(uuid, (account) => {
767
- account.accessToken = result.patch.accessToken;
768
- account.expiresAt = result.patch.expiresAt;
769
- if (result.patch.refreshToken) account.refreshToken = result.patch.refreshToken;
770
- if (result.patch.uuid && result.patch.uuid !== uuid) account.uuid = result.patch.uuid;
771
- if (result.patch.accountId) account.accountId = result.patch.accountId;
772
- if (result.patch.email) account.email = result.patch.email;
773
- account.consecutiveAuthFailures = 0;
774
- account.isAuthDisabled = false;
775
- account.authDisabledReason = void 0;
776
- });
777
- if (result.patch.uuid && result.patch.uuid !== uuid && this.activeAccountUuid === uuid) {
778
- this.activeAccountUuid = result.patch.uuid;
779
- this.store.setActiveUuid(result.patch.uuid).catch(() => {
780
- });
781
- }
782
- if (updated && (uuid === this.activeAccountUuid || updated.uuid === this.activeAccountUuid)) {
783
- this.syncToOpenCode(updated);
784
- }
785
- return result;
786
- }
787
- async validateNonActiveTokens(client) {
788
- await this.refresh();
789
- const activeUuid = this.activeAccountUuid;
790
- const eligible = this.cached.filter(
791
- (account) => account.enabled && !account.isAuthDisabled && account.uuid && account.uuid !== activeUuid
792
- );
793
- for (let i = 0; i < eligible.length; i += STARTUP_REFRESH_CONCURRENCY) {
794
- const batch = eligible.slice(i, i + STARTUP_REFRESH_CONCURRENCY);
795
- await Promise.all(
796
- batch.map(async (account) => {
797
- if (!account.uuid || !isTokenExpired2(account)) return;
798
- const result = await this.ensureValidToken(account.uuid, client);
799
- if (!result.ok) {
800
- await this.markAuthFailure(account.uuid, result);
801
- }
802
- })
803
- );
804
- }
805
- }
806
- async removeAccount(index) {
807
- const account = this.cached[index];
808
- if (!account?.uuid) return false;
809
- const removed = await this.store.removeAccount(account.uuid);
810
- if (removed) {
811
- await this.refresh();
812
- }
813
- return removed;
814
- }
815
- async clearAllAccounts() {
816
- await this.store.clear();
817
- this.cached = [];
818
- this.activeAccountUuid = void 0;
819
- }
820
- async addAccount(auth, email) {
821
- if (!auth.refresh) return;
822
- const existingByToken = this.cached.find((account) => account.refreshToken === auth.refresh);
823
- if (existingByToken) return;
824
- if (email) {
825
- const existingByEmail = this.cached.find(
826
- (account) => account.email && account.email === email
827
- );
828
- if (existingByEmail?.uuid) {
829
- await this.replaceAccountCredentials(existingByEmail.uuid, auth);
830
- return;
831
- }
832
- }
833
- const newAccount = this.createNewAccount(auth, Date.now());
834
- if (email) newAccount.email = email;
835
- await this.store.addAccount(newAccount);
836
- this.activeAccountUuid = newAccount.uuid;
837
- await this.store.setActiveUuid(newAccount.uuid);
838
- await this.refresh();
839
- }
840
- async toggleEnabled(uuid) {
841
- await this.store.mutateAccount(uuid, (account) => {
842
- account.enabled = !(account.enabled ?? true);
843
- if (account.enabled) {
844
- account.isAuthDisabled = false;
845
- account.authDisabledReason = void 0;
846
- account.consecutiveAuthFailures = 0;
847
- }
848
- });
849
- }
850
- async replaceAccountCredentials(uuid, auth) {
851
- const updated = await this.store.mutateAccount(uuid, (account) => {
852
- account.refreshToken = auth.refresh;
853
- account.accessToken = auth.access;
854
- account.expiresAt = auth.expires;
855
- account.lastUsed = Date.now();
856
- account.enabled = true;
857
- account.isAuthDisabled = false;
858
- account.authDisabledReason = void 0;
859
- account.consecutiveAuthFailures = 0;
860
- account.rateLimitResetAt = void 0;
861
- });
862
- this.runtimeFactory?.invalidate(uuid);
863
- if (updated && uuid === this.activeAccountUuid) {
864
- this.syncToOpenCode(updated);
865
- }
866
- }
867
- async retryAuth(uuid, client) {
868
- await this.store.mutateAccount(uuid, (account) => {
869
- account.consecutiveAuthFailures = 0;
870
- account.isAuthDisabled = false;
871
- account.authDisabledReason = void 0;
872
- });
873
- this.runtimeFactory?.invalidate(uuid);
874
- const credentials = await this.store.readCredentials(uuid);
875
- if (!credentials) return { ok: false, permanent: true };
876
- const result = await refreshToken2(credentials.refreshToken, uuid, client);
877
- if (result.ok) {
878
- const updated = await this.store.mutateAccount(uuid, (account) => {
879
- account.accessToken = result.patch.accessToken;
880
- account.expiresAt = result.patch.expiresAt;
881
- if (result.patch.refreshToken) account.refreshToken = result.patch.refreshToken;
882
- if (result.patch.uuid) account.uuid = result.patch.uuid;
883
- if (result.patch.accountId) account.accountId = result.patch.accountId;
884
- if (result.patch.email) account.email = result.patch.email;
885
- account.enabled = true;
886
- account.consecutiveAuthFailures = 0;
887
- });
888
- this.runtimeFactory?.invalidate(uuid);
889
- if (result.patch.uuid) {
890
- this.runtimeFactory?.invalidate(result.patch.uuid);
891
- }
892
- const nextUuid = result.patch.uuid ?? uuid;
893
- if (this.activeAccountUuid === uuid && result.patch.uuid && result.patch.uuid !== uuid) {
894
- this.activeAccountUuid = result.patch.uuid;
895
- await this.store.setActiveUuid(result.patch.uuid);
896
- }
897
- if (updated && (uuid === this.activeAccountUuid || nextUuid === this.activeAccountUuid)) {
898
- const freshCredentials = await this.store.readCredentials(nextUuid);
899
- if (freshCredentials) {
900
- this.syncToOpenCode({
901
- refreshToken: freshCredentials.refreshToken,
902
- accessToken: freshCredentials.accessToken,
903
- expiresAt: freshCredentials.expiresAt
904
- });
905
- }
906
- }
907
- } else {
908
- await this.markAuthFailure(uuid, result);
909
- this.runtimeFactory?.invalidate(uuid);
910
- }
911
- return result;
912
- }
913
- };
914
- }
915
-
916
- // ../multi-account-core/src/account-store.ts
917
- import { promises as fs4 } from "node:fs";
918
- import { randomBytes as randomBytes3 } from "node:crypto";
919
- import { dirname as dirname4, join as join5 } from "node:path";
920
- import lockfile from "proper-lockfile";
921
- import * as v4 from "valibot";
922
-
923
- // ../multi-account-core/src/storage.ts
924
- import { promises as fs3 } from "node:fs";
925
- import { dirname as dirname3, join as join4 } from "node:path";
926
- import * as v3 from "valibot";
927
-
928
- // ../multi-account-core/src/constants.ts
929
- var DEFAULT_ACCOUNTS_FILENAME = "multiauth-accounts.json";
930
- var ACCOUNTS_FILENAME = DEFAULT_ACCOUNTS_FILENAME;
931
- function setAccountsFilename(filename) {
932
- if (!filename) {
933
- ACCOUNTS_FILENAME = DEFAULT_ACCOUNTS_FILENAME;
934
- return;
935
- }
936
- ACCOUNTS_FILENAME = filename;
937
- }
938
-
939
- // ../multi-account-core/src/storage.ts
940
- function getStoragePath() {
941
- return join4(getConfigDir2(), ACCOUNTS_FILENAME);
942
- }
943
- async function backupCorruptFile(targetPath, content) {
944
- const backupPath = `${targetPath}.corrupt.${Date.now()}.bak`;
945
- await fs3.mkdir(dirname3(backupPath), { recursive: true });
946
- await fs3.writeFile(backupPath, content, "utf-8");
947
- }
948
- async function readStorageFromDisk(targetPath, backupOnCorrupt) {
949
- let content;
950
- try {
951
- content = await fs3.readFile(targetPath, "utf-8");
952
- } catch (error) {
953
- if (getErrorCode(error) === "ENOENT") {
954
- return null;
955
- }
956
- throw error;
957
- }
958
- let parsed;
959
- try {
960
- parsed = JSON.parse(content);
961
- } catch {
962
- if (backupOnCorrupt) {
963
- try {
964
- await backupCorruptFile(targetPath, content);
965
- } catch {
966
- }
967
- }
968
- return null;
969
- }
970
- const validation = v3.safeParse(AccountStorageSchema, parsed);
971
- if (!validation.success) {
972
- if (backupOnCorrupt) {
973
- try {
974
- await backupCorruptFile(targetPath, content);
975
- } catch {
976
- }
977
- }
978
- return null;
979
- }
980
- return validation.output;
981
- }
982
- function deduplicateAccounts(accounts) {
983
- const deduplicated = [];
984
- const indexByUuid = /* @__PURE__ */ new Map();
985
- for (const account of accounts) {
986
- if (!account.uuid) {
987
- deduplicated.push(account);
988
- continue;
989
- }
990
- const existingIndex = indexByUuid.get(account.uuid);
991
- if (existingIndex === void 0) {
992
- indexByUuid.set(account.uuid, deduplicated.length);
993
- deduplicated.push(account);
994
- continue;
995
- }
996
- const existingAccount = deduplicated[existingIndex];
997
- if (!existingAccount || account.lastUsed >= existingAccount.lastUsed) {
998
- deduplicated[existingIndex] = account;
999
- }
1000
- }
1001
- return deduplicated;
1002
- }
1003
- async function loadAccounts() {
1004
- const storagePath = getStoragePath();
1005
- const storage = await readStorageFromDisk(storagePath, true);
1006
- if (!storage) {
1007
- return null;
1008
- }
1009
- return {
1010
- ...storage,
1011
- accounts: deduplicateAccounts(storage.accounts || [])
1012
- };
1013
- }
1014
-
1015
- // ../multi-account-core/src/account-store.ts
1016
- var FILE_MODE = 384;
1017
- var LOCK_OPTIONS = {
1018
- stale: 1e4,
1019
- retries: { retries: 10, minTimeout: 50, maxTimeout: 2e3, factor: 2 }
1020
- };
1021
- function getStoragePath2() {
1022
- return join5(getConfigDir2(), ACCOUNTS_FILENAME);
1023
- }
1024
- function createEmptyStorage() {
1025
- return { version: 1, accounts: [] };
1026
- }
1027
- function buildTempPath(targetPath) {
1028
- return `${targetPath}.${randomBytes3(8).toString("hex")}.tmp`;
1029
- }
1030
- async function writeAtomicText(targetPath, content) {
1031
- await fs4.mkdir(dirname4(targetPath), { recursive: true });
1032
- const tempPath = buildTempPath(targetPath);
1033
- try {
1034
- await fs4.writeFile(tempPath, content, { encoding: "utf-8", mode: FILE_MODE });
1035
- await fs4.chmod(tempPath, FILE_MODE);
1036
- await fs4.rename(tempPath, targetPath);
1037
- await fs4.chmod(targetPath, FILE_MODE);
1038
- } catch (error) {
1039
- try {
1040
- await fs4.unlink(tempPath);
1041
- } catch {
1042
- }
1043
- throw error;
1044
- }
1045
- }
1046
- async function writeStorageAtomic(targetPath, storage) {
1047
- const validation = v4.safeParse(AccountStorageSchema, storage);
1048
- if (!validation.success) {
1049
- throw new Error("Invalid account storage payload");
1050
- }
1051
- await writeAtomicText(targetPath, `${JSON.stringify(validation.output, null, 2)}
1052
- `);
1053
- }
1054
- async function ensureStorageFileExists(targetPath) {
1055
- await fs4.mkdir(dirname4(targetPath), { recursive: true });
1056
- const emptyContent = `${JSON.stringify(createEmptyStorage(), null, 2)}
1057
- `;
1058
- try {
1059
- await fs4.writeFile(targetPath, emptyContent, { flag: "wx", mode: FILE_MODE });
1060
- } catch (error) {
1061
- if (getErrorCode(error) !== "EEXIST") throw error;
1062
- }
1063
- }
1064
- async function withFileLock(fn) {
1065
- const storagePath = getStoragePath2();
1066
- await ensureStorageFileExists(storagePath);
1067
- let release = null;
1068
- try {
1069
- release = await lockfile.lock(storagePath, LOCK_OPTIONS);
1070
- return await fn(storagePath);
1071
- } finally {
1072
- if (release) {
1073
- try {
1074
- await release();
1075
- } catch {
1076
- }
1077
- }
1078
- }
1079
- }
1080
- var AccountStore = class {
1081
- async load() {
1082
- const storage = await loadAccounts();
1083
- return storage ?? createEmptyStorage();
1084
- }
1085
- async readCredentials(uuid) {
1086
- const storagePath = getStoragePath2();
1087
- const storage = await readStorageFromDisk(storagePath, false);
1088
- if (!storage) return null;
1089
- const account = storage.accounts.find((a) => a.uuid === uuid);
1090
- if (!account) return null;
1091
- return {
1092
- refreshToken: account.refreshToken,
1093
- accessToken: account.accessToken,
1094
- expiresAt: account.expiresAt,
1095
- accountId: account.accountId
1096
- };
1097
- }
1098
- async mutateAccount(uuid, fn) {
1099
- return await withFileLock(async (storagePath) => {
1100
- const current = await readStorageFromDisk(storagePath, false);
1101
- if (!current) return null;
1102
- const account = current.accounts.find((a) => a.uuid === uuid);
1103
- if (!account) return null;
1104
- fn(account);
1105
- await writeStorageAtomic(storagePath, current);
1106
- return { ...account };
1107
- });
1108
- }
1109
- async mutateStorage(fn) {
1110
- await withFileLock(async (storagePath) => {
1111
- const current = await readStorageFromDisk(storagePath, false) ?? createEmptyStorage();
1112
- fn(current);
1113
- await writeStorageAtomic(storagePath, current);
1114
- });
1115
- }
1116
- async addAccount(account) {
1117
- await withFileLock(async (storagePath) => {
1118
- const current = await readStorageFromDisk(storagePath, false) ?? createEmptyStorage();
1119
- const exists = current.accounts.some(
1120
- (a) => a.uuid === account.uuid || a.refreshToken === account.refreshToken
1121
- );
1122
- if (exists) return;
1123
- current.accounts.push(account);
1124
- await writeStorageAtomic(storagePath, current);
1125
- });
1126
- }
1127
- async removeAccount(uuid) {
1128
- return await withFileLock(async (storagePath) => {
1129
- const current = await readStorageFromDisk(storagePath, false);
1130
- if (!current) return false;
1131
- const initialLength = current.accounts.length;
1132
- current.accounts = current.accounts.filter((a) => a.uuid !== uuid);
1133
- if (current.accounts.length === initialLength) return false;
1134
- if (current.activeAccountUuid === uuid) {
1135
- current.activeAccountUuid = current.accounts[0]?.uuid;
1136
- }
1137
- await writeStorageAtomic(storagePath, current);
1138
- return true;
1139
- });
1140
- }
1141
- async setActiveUuid(uuid) {
1142
- await this.mutateStorage((storage) => {
1143
- storage.activeAccountUuid = uuid;
1144
- });
1145
- }
1146
- async clear() {
1147
- await withFileLock(async (storagePath) => {
1148
- await writeStorageAtomic(storagePath, createEmptyStorage());
1149
- });
1150
- }
1151
- };
1152
-
1153
- // ../multi-account-core/src/executor.ts
1154
- var MIN_MAX_RETRIES = 6;
1155
- var RETRIES_PER_ACCOUNT = 3;
1156
- var MAX_SERVER_RETRIES_PER_ATTEMPT = 2;
1157
- var MAX_RESOLVE_ATTEMPTS = 10;
1158
- var SERVER_RETRY_BASE_MS = 1e3;
1159
- var SERVER_RETRY_MAX_MS = 4e3;
1160
- function isAbortError(error) {
1161
- return error instanceof Error && error.name === "AbortError";
1162
- }
1163
- function createExecutorForProvider(providerName, dependencies) {
1164
- const {
1165
- handleRateLimitResponse: handleRateLimitResponse2,
1166
- formatWaitTime: formatWaitTime2,
1167
- sleep: sleep2,
1168
- showToast: showToast2,
1169
- getAccountLabel: getAccountLabel2
1170
- } = dependencies;
1171
- async function executeWithAccountRotation2(manager, runtimeFactory, client, input, init) {
1172
- const maxRetries = Math.max(MIN_MAX_RETRIES, manager.getAccountCount() * RETRIES_PER_ACCOUNT);
1173
- let previousAccountUuid;
1174
- async function retryServerErrors(account, runtime) {
1175
- for (let attempt = 0; attempt < MAX_SERVER_RETRIES_PER_ATTEMPT; attempt++) {
1176
- const backoff = Math.min(SERVER_RETRY_BASE_MS * 2 ** attempt, SERVER_RETRY_MAX_MS);
1177
- const jitteredBackoff = backoff * (0.5 + Math.random() * 0.5);
1178
- await sleep2(jitteredBackoff);
1179
- let retryResponse;
1180
- try {
1181
- retryResponse = await runtime.fetch(input, init);
1182
- } catch (error) {
1183
- if (isAbortError(error)) throw error;
1184
- if (await handleRuntimeFetchFailure(manager, runtimeFactory, client, account, error)) {
1185
- return null;
1186
- }
1187
- void showToast2(client, `${getAccountLabel2(account)} network error \u2014 switching`, "warning");
1188
- return null;
1189
- }
1190
- if (retryResponse.status < 500) return retryResponse;
1191
- }
1192
- return null;
1193
- }
1194
- const dispatchResponseStatus = async (account, accountUuid, runtime, response, allow401Retry, from401RefreshRetry) => {
1195
- if (response.status >= 500) {
1196
- const recovered = await retryServerErrors(account, runtime);
1197
- if (recovered === null) {
1198
- return { type: "retryOuter" };
1199
- }
1200
- response = recovered;
1201
- }
1202
- if (response.status === 401) {
1203
- if (allow401Retry) {
1204
- runtimeFactory.invalidate(accountUuid);
1205
- try {
1206
- const retryRuntime = await runtimeFactory.getRuntime(accountUuid);
1207
- const retryResponse = await retryRuntime.fetch(input, init);
1208
- return dispatchResponseStatus(account, accountUuid, retryRuntime, retryResponse, false, true);
1209
- } catch (error) {
1210
- if (isAbortError(error)) throw error;
1211
- if (await handleRuntimeFetchFailure(manager, runtimeFactory, client, account, error)) {
1212
- return { type: "retryOuter" };
1213
- }
1214
- return { type: "retryOuter" };
1215
- }
1216
- }
1217
- await manager.markAuthFailure(accountUuid, { ok: false, permanent: false });
1218
- await manager.refresh();
1219
- if (!manager.hasAnyUsableAccount()) {
1220
- void showToast2(client, "All accounts have auth failures.", "error");
1221
- throw new Error(
1222
- `All ${providerName} accounts have authentication failures. Re-authenticate with \`opencode auth login\`.`
1223
- );
1224
- }
1225
- void showToast2(client, `${getAccountLabel2(account)} auth failed \u2014 switching to next account.`, "warning");
1226
- return { type: "retryOuter" };
1227
- }
1228
- if (response.status === 403) {
1229
- const revoked = await isRevokedTokenResponse(response);
1230
- if (revoked) {
1231
- await manager.markRevoked(accountUuid);
1232
- await manager.refresh();
1233
- void showToast2(
1234
- client,
1235
- `${getAccountLabel2(account)} disabled: OAuth token revoked.`,
1236
- "error"
1237
- );
1238
- if (!manager.hasAnyUsableAccount()) {
1239
- throw new Error(
1240
- `All ${providerName} accounts have been revoked or disabled. Re-authenticate with \`opencode auth login\`.`
1241
- );
1242
- }
1243
- return { type: "retryOuter" };
1244
- }
1245
- if (from401RefreshRetry) {
1246
- return { type: "handled", response };
1247
- }
1248
- }
1249
- if (response.status === 429) {
1250
- await handleRateLimitResponse2(manager, client, account, response);
1251
- return { type: "handled" };
1252
- }
1253
- return { type: "success", response };
1254
- };
1255
- for (let retries = 1; retries <= maxRetries; retries++) {
1256
- await manager.refresh();
1257
- const account = await resolveAccount(manager, client);
1258
- const accountUuid = account.uuid;
1259
- if (!accountUuid) continue;
1260
- if (previousAccountUuid && accountUuid !== previousAccountUuid && manager.getAccountCount() > 1) {
1261
- void showToast2(client, `Switched to ${getAccountLabel2(account)}`, "info");
1262
- }
1263
- previousAccountUuid = accountUuid;
1264
- let runtime;
1265
- let response;
1266
- try {
1267
- runtime = await runtimeFactory.getRuntime(accountUuid);
1268
- response = await runtime.fetch(input, init);
1269
- } catch (error) {
1270
- if (isAbortError(error)) throw error;
1271
- if (await handleRuntimeFetchFailure(manager, runtimeFactory, client, account, error)) {
1272
- continue;
1273
- }
1274
- void showToast2(client, `${getAccountLabel2(account)} network error \u2014 switching`, "warning");
1275
- continue;
1276
- }
1277
- const transition = await dispatchResponseStatus(account, accountUuid, runtime, response, true, false);
1278
- if (transition.type === "retryOuter" || transition.type === "handled") {
1279
- if (transition.type === "handled" && transition.response) {
1280
- return transition.response;
1281
- }
1282
- continue;
1283
- }
1284
- await manager.markSuccess(accountUuid);
1285
- return transition.response;
1286
- }
1287
- throw new Error(
1288
- `Exhausted ${maxRetries} retries across all accounts. All attempts failed due to auth errors, rate limits, or token issues.`
1289
- );
1290
- }
1291
- async function handleRuntimeFetchFailure(manager, runtimeFactory, client, account, error) {
1292
- if (!isTokenRefreshError(error)) return false;
1293
- if (!account.uuid) return false;
1294
- const accountUuid = account.uuid;
1295
- runtimeFactory.invalidate(accountUuid);
1296
- await manager.markAuthFailure(accountUuid, {
1297
- ok: false,
1298
- permanent: error.permanent
1299
- });
1300
- await manager.refresh();
1301
- if (!manager.hasAnyUsableAccount()) {
1302
- void showToast2(client, "All accounts have auth failures.", "error");
1303
- throw new Error(
1304
- `All ${providerName} accounts have authentication failures. Re-authenticate with \`opencode auth login\`.`
1305
- );
1306
- }
1307
- void showToast2(client, `${getAccountLabel2(account)} auth failed \u2014 switching to next account.`, "warning");
1308
- return true;
1309
- }
1310
- async function resolveAccount(manager, client) {
1311
- let attempts = 0;
1312
- while (true) {
1313
- if (++attempts > MAX_RESOLVE_ATTEMPTS) {
1314
- throw new Error(
1315
- `Failed to resolve an available account after ${MAX_RESOLVE_ATTEMPTS} attempts. All accounts may be rate-limited or disabled.`
1316
- );
1317
- }
1318
- const account = await manager.selectAccount();
1319
- if (account) return account;
1320
- if (!manager.hasAnyUsableAccount()) {
1321
- throw new Error(
1322
- `All ${providerName} accounts are disabled. Re-authenticate with \`opencode auth login\`.`
1323
- );
1324
- }
1325
- const waitMs = manager.getMinWaitTime();
1326
- if (waitMs <= 0) {
1327
- throw new Error(
1328
- `All ${providerName} accounts are rate-limited. Add more accounts with \`opencode auth login\` or wait.`
1329
- );
1330
- }
1331
- await showToast2(
1332
- client,
1333
- `All ${manager.getAccountCount()} account(s) rate-limited. Waiting ${formatWaitTime2(waitMs)}...`,
1334
- "warning"
1335
- );
1336
- await sleep2(waitMs);
1337
- }
1338
- }
1339
- return {
1340
- executeWithAccountRotation: executeWithAccountRotation2
1341
- };
1342
- }
1343
- async function isRevokedTokenResponse(response) {
1344
- try {
1345
- const cloned = response.clone();
1346
- const body = await cloned.text();
1347
- return body.includes("revoked");
1348
- } catch {
1349
- return false;
1350
- }
1351
- }
1352
-
1353
- // ../multi-account-core/src/proactive-refresh.ts
1354
- var INITIAL_DELAY_MS = 5e3;
1355
- function createProactiveRefreshQueueForProvider(dependencies) {
1356
- const {
1357
- providerAuthId,
1358
- getConfig: getConfig2,
1359
- refreshToken: refreshToken2,
1360
- isTokenExpired: isTokenExpired2,
1361
- debugLog: debugLog2
1362
- } = dependencies;
1363
- return class ProactiveRefreshQueue {
1364
- constructor(client, store, onInvalidate) {
1365
- this.client = client;
1366
- this.store = store;
1367
- this.onInvalidate = onInvalidate;
1368
- }
1369
- timeoutHandle = null;
1370
- runToken = 0;
1371
- inFlight = null;
1372
- start() {
1373
- const config = getConfig2();
1374
- if (!config.proactive_refresh) return;
1375
- this.runToken++;
1376
- if (this.timeoutHandle) {
1377
- clearTimeout(this.timeoutHandle);
1378
- this.timeoutHandle = null;
1379
- }
1380
- this.scheduleNext(this.runToken, INITIAL_DELAY_MS);
1381
- debugLog2(this.client, "Proactive refresh started", {
1382
- intervalSeconds: config.proactive_refresh_interval_seconds,
1383
- bufferSeconds: config.proactive_refresh_buffer_seconds
1384
- });
1385
- }
1386
- async stop() {
1387
- this.runToken++;
1388
- if (this.timeoutHandle) {
1389
- clearTimeout(this.timeoutHandle);
1390
- this.timeoutHandle = null;
1391
- }
1392
- if (this.inFlight) {
1393
- await this.inFlight;
1394
- this.inFlight = null;
1395
- }
1396
- debugLog2(this.client, "Proactive refresh stopped");
1397
- }
1398
- scheduleNext(token, delayMs) {
1399
- this.timeoutHandle = setTimeout(() => {
1400
- if (token !== this.runToken) return;
1401
- this.inFlight = this.runCheck(token).finally(() => {
1402
- this.inFlight = null;
1403
- });
1404
- }, delayMs);
1405
- }
1406
- needsProactiveRefresh(account) {
1407
- if (!account.accessToken || !account.expiresAt) return false;
1408
- if (isTokenExpired2(account)) return false;
1409
- const bufferMs = getConfig2().proactive_refresh_buffer_seconds * 1e3;
1410
- return account.expiresAt <= Date.now() + bufferMs;
1411
- }
1412
- async runCheck(token) {
1413
- try {
1414
- const stored = await this.store.load();
1415
- if (token !== this.runToken) return;
1416
- const candidates = stored.accounts.filter(
1417
- (a) => a.enabled !== false && !a.isAuthDisabled && a.uuid && this.needsProactiveRefresh(a)
1418
- );
1419
- if (candidates.length === 0) return;
1420
- debugLog2(this.client, `Proactive refresh: ${candidates.length} account(s) approaching expiry`);
1421
- for (const account of candidates) {
1422
- if (token !== this.runToken) return;
1423
- const credentials = await this.store.readCredentials(account.uuid);
1424
- if (!credentials || !this.needsProactiveRefresh(credentials)) continue;
1425
- const result = await refreshToken2(credentials.refreshToken, account.uuid, this.client);
1426
- if (result.ok) {
1427
- await this.store.mutateAccount(account.uuid, (target) => {
1428
- target.accessToken = result.patch.accessToken;
1429
- target.expiresAt = result.patch.expiresAt;
1430
- if (result.patch.refreshToken) target.refreshToken = result.patch.refreshToken;
1431
- if (result.patch.uuid) target.uuid = result.patch.uuid;
1432
- if (result.patch.email) target.email = result.patch.email;
1433
- if (result.patch.accountId) target.accountId = result.patch.accountId;
1434
- target.consecutiveAuthFailures = 0;
1435
- target.isAuthDisabled = false;
1436
- target.authDisabledReason = void 0;
1437
- });
1438
- this.onInvalidate?.(account.uuid);
1439
- } else {
1440
- await this.persistFailure(account, result.permanent);
1441
- }
1442
- }
1443
- } catch (error) {
1444
- debugLog2(this.client, `Proactive refresh check error: ${error}`);
1445
- } finally {
1446
- if (token === this.runToken) {
1447
- const intervalMs = getConfig2().proactive_refresh_interval_seconds * 1e3;
1448
- this.scheduleNext(token, intervalMs);
1449
- }
1450
- }
1451
- }
1452
- async persistFailure(account, permanent) {
1453
- try {
1454
- const accountUuid = account.uuid;
1455
- if (!accountUuid) return;
1456
- if (permanent) {
1457
- const removed = await this.store.removeAccount(accountUuid);
1458
- if (!removed) return;
1459
- this.onInvalidate?.(accountUuid);
1460
- await this.clearOpenCodeAuthIfNoAccountsRemain();
1461
- return;
1462
- }
1463
- await this.store.mutateStorage((storage) => {
1464
- const target = storage.accounts.find((entry) => entry.uuid === accountUuid);
1465
- if (!target) return;
1466
- target.consecutiveAuthFailures = (target.consecutiveAuthFailures ?? 0) + 1;
1467
- const maxFailures = getConfig2().max_consecutive_auth_failures;
1468
- const usableCount = storage.accounts.filter(
1469
- (entry) => entry.enabled && !entry.isAuthDisabled && entry.uuid !== accountUuid
1470
- ).length;
1471
- if (target.consecutiveAuthFailures >= maxFailures && usableCount > 0) {
1472
- target.isAuthDisabled = true;
1473
- target.authDisabledReason = `${maxFailures} consecutive auth failures (proactive refresh)`;
1474
- }
1475
- });
1476
- } catch {
1477
- debugLog2(this.client, `Failed to persist auth failure for ${account.uuid}`);
1478
- }
1479
- }
1480
- async clearOpenCodeAuthIfNoAccountsRemain() {
1481
- const storage = await this.store.load();
1482
- if (storage.accounts.length > 0) return;
1483
- await this.client.auth.set({
1484
- path: { id: providerAuthId },
1485
- body: getClearedOAuthBody()
1486
- }).catch(() => {
1487
- });
1488
- }
1489
- };
1490
- }
1491
-
1492
- // ../multi-account-core/src/rate-limit.ts
1493
- var USAGE_FETCH_COOLDOWN_MS = 3e4;
1494
- function createRateLimitHandlers(dependencies) {
1495
- const {
1496
- fetchUsage: fetchUsage2,
1497
- getConfig: getConfig2,
1498
- formatWaitTime: formatWaitTime2,
1499
- getAccountLabel: getAccountLabel2,
1500
- showToast: showToast2
1501
- } = dependencies;
1502
- function retryAfterMsFromResponse2(response) {
1503
- const retryAfterMs = response.headers.get("retry-after-ms");
1504
- if (retryAfterMs) {
1505
- const parsed = parseInt(retryAfterMs, 10);
1506
- if (!isNaN(parsed) && parsed > 0) return parsed;
1507
- }
1508
- const retryAfter = response.headers.get("retry-after");
1509
- if (retryAfter) {
1510
- const parsed = parseInt(retryAfter, 10);
1511
- if (!isNaN(parsed) && parsed > 0) return parsed * 1e3;
1512
- }
1513
- return getConfig2().default_retry_after_ms;
1514
- }
1515
- function getResetMsFromUsage2(account) {
1516
- const usage = account.cachedUsage;
1517
- if (!usage) return null;
1518
- const now = Date.now();
1519
- const candidates = [];
1520
- if (usage.five_hour?.resets_at) {
1521
- const ms = Date.parse(usage.five_hour.resets_at) - now;
1522
- if (ms > 0) candidates.push(ms);
1523
- }
1524
- if (usage.seven_day?.resets_at) {
1525
- const ms = Date.parse(usage.seven_day.resets_at) - now;
1526
- if (ms > 0) candidates.push(ms);
1527
- }
1528
- return candidates.length > 0 ? Math.min(...candidates) : null;
1529
- }
1530
- async function fetchUsageLimits2(accessToken, accountId) {
1531
- if (!accessToken) return null;
1532
- try {
1533
- const result = await fetchUsage2(accessToken, accountId);
1534
- return result.ok ? result.data : null;
1535
- } catch {
1536
- return null;
1537
- }
1538
- }
1539
- async function handleRateLimitResponse2(manager, client, account, response) {
1540
- if (!account.uuid) return;
1541
- const resetMs = getResetMsFromUsage2(account) ?? retryAfterMsFromResponse2(response);
1542
- await manager.markRateLimited(account.uuid, resetMs);
1543
- const shouldFetchUsage = account.accessToken && (!account.cachedUsageAt || Date.now() - account.cachedUsageAt > USAGE_FETCH_COOLDOWN_MS);
1544
- if (shouldFetchUsage) {
1545
- const usage = await fetchUsageLimits2(account.accessToken, account.accountId);
1546
- if (usage) {
1547
- await manager.applyUsageCache(account.uuid, usage);
1548
- }
1549
- }
1550
- if (manager.getAccountCount() > 1) {
1551
- void showToast2(
1552
- client,
1553
- `${getAccountLabel2(account)} rate-limited (resets in ${formatWaitTime2(resetMs)}). Switching...`,
1554
- "warning"
1555
- );
1556
- }
1557
- }
1558
- return {
1559
- retryAfterMsFromResponse: retryAfterMsFromResponse2,
1560
- getResetMsFromUsage: getResetMsFromUsage2,
1561
- fetchUsageLimits: fetchUsageLimits2,
1562
- handleRateLimitResponse: handleRateLimitResponse2
1563
- };
1564
- }
1565
-
1566
- // ../multi-account-core/src/auth-migration.ts
1567
- import { promises as fs5 } from "node:fs";
1568
- import { join as join6 } from "node:path";
1569
- var AUTH_JSON_FILENAME = "auth.json";
1570
- function isValidOAuthCredential(value) {
1571
- if (typeof value !== "object" || value === null) return false;
1572
- const candidate = value;
1573
- return candidate.type === "oauth" && typeof candidate.refresh === "string" && candidate.refresh.length > 0;
1574
- }
1575
- function resolveAuthJsonPath() {
1576
- return join6(getConfigDir2(), AUTH_JSON_FILENAME);
1577
- }
1578
- async function readAuthJson() {
1579
- const authPath = resolveAuthJsonPath();
1580
- let content;
1581
- try {
1582
- content = await fs5.readFile(authPath, "utf-8");
1583
- } catch {
1584
- return null;
1585
- }
1586
- try {
1587
- const parsed = JSON.parse(content);
1588
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1589
- return null;
1590
- }
1591
- return parsed;
1592
- } catch {
1593
- return null;
1594
- }
1595
- }
1596
- async function migrateFromAuthJson(providerKey, store) {
1597
- const storage = await store.load();
1598
- const hasExistingAccounts = storage.accounts.length > 0;
1599
- if (hasExistingAccounts) return false;
1600
- const authData = await readAuthJson();
1601
- if (!authData) return false;
1602
- const providerCredential = authData[providerKey];
1603
- if (!isValidOAuthCredential(providerCredential)) return false;
1604
- const now = Date.now();
1605
- const newAccount = {
1606
- uuid: crypto.randomUUID(),
1607
- refreshToken: providerCredential.refresh,
1608
- accessToken: providerCredential.access,
1609
- expiresAt: providerCredential.expires,
1610
- addedAt: now,
1611
- lastUsed: now,
1612
- enabled: true,
1613
- planTier: "",
1614
- consecutiveAuthFailures: 0,
1615
- isAuthDisabled: false
1616
- };
1617
- await store.addAccount(newAccount);
1618
- await store.setActiveUuid(newAccount.uuid);
1619
- return true;
1620
- }
1621
-
1622
- // ../multi-account-core/src/ui/ansi.ts
1623
- var ANSI = {
1624
- hide: "\x1B[?25l",
1625
- show: "\x1B[?25h",
1626
- up: (n = 1) => `\x1B[${n}A`,
1627
- down: (n = 1) => `\x1B[${n}B`,
1628
- clearLine: "\x1B[2K",
1629
- cyan: "\x1B[36m",
1630
- green: "\x1B[32m",
1631
- red: "\x1B[31m",
1632
- yellow: "\x1B[33m",
1633
- dim: "\x1B[2m",
1634
- bold: "\x1B[1m",
1635
- reset: "\x1B[0m"
1636
- };
1637
- function parseKey(data) {
1638
- const s = data.toString();
1639
- if (s === "\x1B[A" || s === "\x1BOA") return "up";
1640
- if (s === "\x1B[B" || s === "\x1BOB") return "down";
1641
- if (s === "\r" || s === "\n") return "enter";
1642
- if (s === "") return "escape";
1643
- if (s === "\x1B") return "escape-start";
1644
- return null;
1645
- }
1646
- function isTTY() {
1647
- return Boolean(process.stdin.isTTY);
1648
- }
1649
-
1650
- // ../multi-account-core/src/ui/select.ts
1651
- var ESCAPE_TIMEOUT_MS = 50;
1652
- var COLOR_MAP = {
1653
- red: ANSI.red,
1654
- green: ANSI.green,
1655
- yellow: ANSI.yellow,
1656
- cyan: ANSI.cyan
1657
- };
1658
- async function select(items, options) {
1659
- if (!isTTY()) {
1660
- throw new Error("Interactive select requires a TTY terminal");
1661
- }
1662
- const enabledItems = items.filter((i) => !i.disabled && !i.separator);
1663
- if (enabledItems.length === 0) {
1664
- throw new Error("All items disabled");
1665
- }
1666
- if (enabledItems.length === 1) {
1667
- return enabledItems[0].value;
1668
- }
1669
- const { message, subtitle } = options;
1670
- const { stdin, stdout } = process;
1671
- let cursor = items.findIndex((i) => !i.disabled && !i.separator);
1672
- if (cursor === -1) cursor = 0;
1673
- let escapeTimeout = null;
1674
- let isCleanedUp = false;
1675
- let isFirstRender = true;
1676
- const getTotalLines = () => {
1677
- const subtitleLines = subtitle ? 3 : 0;
1678
- return 1 + subtitleLines + items.length + 1 + 1;
1679
- };
1680
- const renderItemLabel = (item, isSelected) => {
1681
- const colorCode = item.color ? COLOR_MAP[item.color] ?? "" : "";
1682
- if (item.disabled) {
1683
- return `${ANSI.dim}${item.label} (unavailable)${ANSI.reset}`;
1684
- }
1685
- const hintSuffix = item.hint ? ` ${ANSI.dim}${item.hint}${ANSI.reset}` : "";
1686
- if (isSelected) {
1687
- const label = colorCode ? `${colorCode}${item.label}${ANSI.reset}` : item.label;
1688
- return `${label}${hintSuffix}`;
1689
- }
1690
- const dimLabel = colorCode ? `${ANSI.dim}${colorCode}${item.label}${ANSI.reset}` : `${ANSI.dim}${item.label}${ANSI.reset}`;
1691
- return `${dimLabel}${hintSuffix}`;
1692
- };
1693
- const render = () => {
1694
- const totalLines = getTotalLines();
1695
- if (!isFirstRender) {
1696
- stdout.write(ANSI.up(totalLines) + "\r");
1697
- }
1698
- isFirstRender = false;
1699
- stdout.write(`${ANSI.clearLine}${ANSI.dim}\u250C ${ANSI.reset}${message}
1700
- `);
1701
- if (subtitle) {
1702
- stdout.write(`${ANSI.clearLine}${ANSI.dim}\u2502${ANSI.reset}
1703
- `);
1704
- stdout.write(`${ANSI.clearLine}${ANSI.cyan}\u25C6${ANSI.reset} ${subtitle}
1705
- `);
1706
- stdout.write(`${ANSI.clearLine}
1707
- `);
1708
- }
1709
- for (let i = 0; i < items.length; i++) {
1710
- const item = items[i];
1711
- if (!item) continue;
1712
- if (item.separator) {
1713
- stdout.write(`${ANSI.clearLine}${ANSI.dim}\u2502${ANSI.reset}
1714
- `);
1715
- continue;
1716
- }
1717
- const isSelected = i === cursor;
1718
- const labelText = renderItemLabel(item, isSelected);
1719
- const bullet = isSelected ? `${ANSI.green}\u25CF${ANSI.reset}` : `${ANSI.dim}\u25CB${ANSI.reset}`;
1720
- stdout.write(`${ANSI.clearLine}${ANSI.cyan}\u2502${ANSI.reset} ${bullet} ${labelText}
1721
- `);
1722
- }
1723
- stdout.write(`${ANSI.clearLine}${ANSI.cyan}\u2502${ANSI.reset} ${ANSI.dim}\u2191/\u2193 to select \u2022 Enter: confirm${ANSI.reset}
1724
- `);
1725
- stdout.write(`${ANSI.clearLine}${ANSI.cyan}\u2514${ANSI.reset}
1726
- `);
1727
- };
1728
- return new Promise((resolve) => {
1729
- const wasRaw = stdin.isRaw ?? false;
1730
- const cleanup = () => {
1731
- if (isCleanedUp) return;
1732
- isCleanedUp = true;
1733
- if (escapeTimeout) {
1734
- clearTimeout(escapeTimeout);
1735
- escapeTimeout = null;
1736
- }
1737
- try {
1738
- stdin.removeListener("data", onKey);
1739
- stdin.setRawMode(wasRaw);
1740
- stdin.pause();
1741
- stdout.write(ANSI.show);
1742
- } catch {
1743
- }
1744
- process.removeListener("SIGINT", onSignal);
1745
- process.removeListener("SIGTERM", onSignal);
1746
- };
1747
- const onSignal = () => {
1748
- cleanup();
1749
- resolve(null);
1750
- };
1751
- const finishWithValue = (value) => {
1752
- cleanup();
1753
- resolve(value);
1754
- };
1755
- const findNextSelectable = (from, direction) => {
1756
- if (items.length === 0) return from;
1757
- let next = from;
1758
- do {
1759
- next = (next + direction + items.length) % items.length;
1760
- } while (items[next]?.disabled || items[next]?.separator);
1761
- return next;
1762
- };
1763
- const onKey = (data) => {
1764
- if (escapeTimeout) {
1765
- clearTimeout(escapeTimeout);
1766
- escapeTimeout = null;
1767
- }
1768
- const action = parseKey(data);
1769
- switch (action) {
1770
- case "up":
1771
- cursor = findNextSelectable(cursor, -1);
1772
- render();
1773
- return;
1774
- case "down":
1775
- cursor = findNextSelectable(cursor, 1);
1776
- render();
1777
- return;
1778
- case "enter":
1779
- finishWithValue(items[cursor]?.value ?? null);
1780
- return;
1781
- case "escape":
1782
- finishWithValue(null);
1783
- return;
1784
- case "escape-start":
1785
- escapeTimeout = setTimeout(() => {
1786
- finishWithValue(null);
1787
- }, ESCAPE_TIMEOUT_MS);
1788
- return;
1789
- default:
1790
- return;
1791
- }
1792
- };
1793
- process.once("SIGINT", onSignal);
1794
- process.once("SIGTERM", onSignal);
1795
- try {
1796
- stdin.setRawMode(true);
1797
- } catch {
1798
- cleanup();
1799
- resolve(null);
1800
- return;
1801
- }
1802
- stdin.resume();
1803
- stdout.write(ANSI.hide);
1804
- render();
1805
- stdin.on("data", onKey);
1806
- });
1807
- }
1808
-
1809
- // ../multi-account-core/src/ui/confirm.ts
1810
- async function confirm(message, defaultYes = false) {
1811
- const items = defaultYes ? [
1812
- { label: "Yes", value: true },
1813
- { label: "No", value: false }
1814
- ] : [
1815
- { label: "No", value: false },
1816
- { label: "Yes", value: true }
1817
- ];
1818
- const result = await select(items, { message });
1819
- return result ?? false;
1820
- }
1821
-
1822
- // ../multi-account-core/src/adapters/openai.ts
1823
- var ISSUER = "https://auth.openai.com";
1824
- var openAIOAuthAdapter = {
1825
- id: "openai",
1826
- authProviderId: "openai",
1827
- modelDisplayName: "ChatGPT",
1828
- statusToolName: "chatgpt_multiauth_status",
1829
- authMethodLabel: "ChatGPT Plus/Pro (Multi-Auth)",
1830
- serviceLogName: "chatgpt-multiauth",
1831
- oauthClientId: "app_EMoamEEZ73f0CkXaXp7hrann",
1832
- tokenEndpoint: `${ISSUER}/oauth/token`,
1833
- usageEndpoint: "",
1834
- profileEndpoint: "",
1835
- oauthBetaHeader: "",
1836
- requestBetaHeader: "",
1837
- cliUserAgent: "opencode/1.1.53",
1838
- cliVersion: "",
1839
- billingSalt: "",
1840
- toolPrefix: "mcp_",
1841
- accountStorageFilename: "openai-multi-account-accounts.json",
1842
- transform: {
1843
- rewriteOpenCodeBranding: false,
1844
- addToolPrefix: false,
1845
- stripToolPrefixInResponse: false,
1846
- enableMessagesBetaQuery: false
1847
- },
1848
- planLabels: {
1849
- pro: "ChatGPT Pro",
1850
- plus: "ChatGPT Plus",
1851
- go: "ChatGPT Go",
1852
- free: "Free"
1853
- },
1854
- supported: true
1855
- };
5
+ // src/account-manager.ts
6
+ import { createAccountManagerForProvider } from "opencode-multi-account-core";
1856
7
 
1857
8
  // src/constants.ts
9
+ import { openAIOAuthAdapter } from "opencode-multi-account-core";
1858
10
  var OPENAI_OAUTH_ADAPTER = openAIOAuthAdapter;
1859
11
  var OPENAI_CLIENT_ID = OPENAI_OAUTH_ADAPTER.oauthClientId;
1860
12
  var OPENAI_TOKEN_ENDPOINT = OPENAI_OAUTH_ADAPTER.tokenEndpoint;
@@ -1867,95 +19,95 @@ var OAUTH_PORT = 1455;
1867
19
  var OPENAI_BETA_HEADER = OPENAI_OAUTH_ADAPTER.requestBetaHeader;
1868
20
  var OPENAI_CLI_USER_AGENT = OPENAI_OAUTH_ADAPTER.cliUserAgent;
1869
21
  var TOOL_PREFIX = OPENAI_OAUTH_ADAPTER.toolPrefix;
1870
- var ACCOUNTS_FILENAME2 = OPENAI_OAUTH_ADAPTER.accountStorageFilename;
22
+ var ACCOUNTS_FILENAME = OPENAI_OAUTH_ADAPTER.accountStorageFilename;
1871
23
  var PLAN_LABELS = OPENAI_OAUTH_ADAPTER.planLabels;
1872
24
  var TOKEN_EXPIRY_BUFFER_MS = 6e4;
1873
25
  var TOKEN_REFRESH_TIMEOUT_MS = 3e4;
1874
26
 
1875
27
  // src/oauth.ts
1876
- import * as v6 from "valibot";
28
+ import * as v2 from "valibot";
1877
29
 
1878
30
  // src/types.ts
1879
- import * as v5 from "valibot";
1880
- var OAuthCredentialsSchema2 = v5.object({
1881
- type: v5.literal("oauth"),
1882
- refresh: v5.string(),
1883
- access: v5.string(),
1884
- expires: v5.number()
31
+ import * as v from "valibot";
32
+ var OAuthCredentialsSchema = v.object({
33
+ type: v.literal("oauth"),
34
+ refresh: v.string(),
35
+ access: v.string(),
36
+ expires: v.number()
1885
37
  });
1886
- var UsageLimitEntrySchema2 = v5.object({
1887
- utilization: v5.number(),
1888
- resets_at: v5.nullable(v5.string())
38
+ var UsageLimitEntrySchema = v.object({
39
+ utilization: v.number(),
40
+ resets_at: v.nullable(v.string())
1889
41
  });
1890
- var UsageLimitsSchema2 = v5.object({
1891
- five_hour: v5.optional(v5.nullable(UsageLimitEntrySchema2), null),
1892
- seven_day: v5.optional(v5.nullable(UsageLimitEntrySchema2), null),
1893
- seven_day_sonnet: v5.optional(v5.nullable(UsageLimitEntrySchema2), null)
42
+ var UsageLimitsSchema = v.object({
43
+ five_hour: v.optional(v.nullable(UsageLimitEntrySchema), null),
44
+ seven_day: v.optional(v.nullable(UsageLimitEntrySchema), null),
45
+ seven_day_sonnet: v.optional(v.nullable(UsageLimitEntrySchema), null)
1894
46
  });
1895
- var CredentialRefreshPatchSchema2 = v5.object({
1896
- accessToken: v5.string(),
1897
- expiresAt: v5.number(),
1898
- refreshToken: v5.optional(v5.string()),
1899
- uuid: v5.optional(v5.string()),
1900
- accountId: v5.optional(v5.string()),
1901
- email: v5.optional(v5.string())
47
+ var CredentialRefreshPatchSchema = v.object({
48
+ accessToken: v.string(),
49
+ expiresAt: v.number(),
50
+ refreshToken: v.optional(v.string()),
51
+ uuid: v.optional(v.string()),
52
+ accountId: v.optional(v.string()),
53
+ email: v.optional(v.string())
1902
54
  });
1903
- var StoredAccountSchema2 = v5.object({
1904
- uuid: v5.optional(v5.string()),
1905
- accountId: v5.optional(v5.string()),
1906
- label: v5.optional(v5.string()),
1907
- email: v5.optional(v5.string()),
1908
- planTier: v5.optional(v5.string(), ""),
1909
- refreshToken: v5.string(),
1910
- accessToken: v5.optional(v5.string()),
1911
- expiresAt: v5.optional(v5.number()),
1912
- addedAt: v5.number(),
1913
- lastUsed: v5.number(),
1914
- enabled: v5.optional(v5.boolean(), true),
1915
- rateLimitResetAt: v5.optional(v5.number()),
1916
- cachedUsage: v5.optional(UsageLimitsSchema2),
1917
- cachedUsageAt: v5.optional(v5.number()),
1918
- consecutiveAuthFailures: v5.optional(v5.number(), 0),
1919
- isAuthDisabled: v5.optional(v5.boolean(), false),
1920
- authDisabledReason: v5.optional(v5.string())
55
+ var StoredAccountSchema = v.object({
56
+ uuid: v.optional(v.string()),
57
+ accountId: v.optional(v.string()),
58
+ label: v.optional(v.string()),
59
+ email: v.optional(v.string()),
60
+ planTier: v.optional(v.string(), ""),
61
+ refreshToken: v.string(),
62
+ accessToken: v.optional(v.string()),
63
+ expiresAt: v.optional(v.number()),
64
+ addedAt: v.number(),
65
+ lastUsed: v.number(),
66
+ enabled: v.optional(v.boolean(), true),
67
+ rateLimitResetAt: v.optional(v.number()),
68
+ cachedUsage: v.optional(UsageLimitsSchema),
69
+ cachedUsageAt: v.optional(v.number()),
70
+ consecutiveAuthFailures: v.optional(v.number(), 0),
71
+ isAuthDisabled: v.optional(v.boolean(), false),
72
+ authDisabledReason: v.optional(v.string())
1921
73
  });
1922
- var AccountStorageSchema2 = v5.object({
1923
- version: v5.literal(1),
1924
- accounts: v5.optional(v5.array(StoredAccountSchema2), []),
1925
- activeAccountUuid: v5.optional(v5.string())
74
+ var AccountStorageSchema = v.object({
75
+ version: v.literal(1),
76
+ accounts: v.optional(v.array(StoredAccountSchema), []),
77
+ activeAccountUuid: v.optional(v.string())
1926
78
  });
1927
- var TokenResponseSchema = v5.object({
1928
- id_token: v5.optional(v5.string()),
1929
- access_token: v5.string(),
1930
- refresh_token: v5.optional(v5.string()),
1931
- expires_in: v5.number()
79
+ var TokenResponseSchema = v.object({
80
+ id_token: v.optional(v.string()),
81
+ access_token: v.string(),
82
+ refresh_token: v.optional(v.string()),
83
+ expires_in: v.number()
1932
84
  });
1933
- var AccountSelectionStrategySchema2 = v5.picklist(["sticky", "round-robin", "hybrid"]);
1934
- var PluginConfigSchema2 = v5.object({
85
+ var AccountSelectionStrategySchema = v.picklist(["sticky", "round-robin", "hybrid"]);
86
+ var PluginConfigSchema = v.object({
1935
87
  /** sticky: same account until failure, round-robin: rotate every request, hybrid: health+usage scoring */
1936
- account_selection_strategy: v5.optional(AccountSelectionStrategySchema2, "sticky"),
88
+ account_selection_strategy: v.optional(AccountSelectionStrategySchema, "sticky"),
1937
89
  /** Use cross-process claim file to distribute parallel sessions across accounts */
1938
- cross_process_claims: v5.optional(v5.boolean(), true),
90
+ cross_process_claims: v.optional(v.boolean(), true),
1939
91
  /** Skip account when any usage tier utilization >= this % (100 = disabled) */
1940
- soft_quota_threshold_percent: v5.optional(v5.pipe(v5.number(), v5.minValue(0), v5.maxValue(100)), 100),
92
+ soft_quota_threshold_percent: v.optional(v.pipe(v.number(), v.minValue(0), v.maxValue(100)), 100),
1941
93
  /** Minimum backoff after rate limit (ms) */
1942
- rate_limit_min_backoff_ms: v5.optional(v5.pipe(v5.number(), v5.minValue(0)), 3e4),
94
+ rate_limit_min_backoff_ms: v.optional(v.pipe(v.number(), v.minValue(0)), 3e4),
1943
95
  /** Default retry-after when header is missing (ms) */
1944
- default_retry_after_ms: v5.optional(v5.pipe(v5.number(), v5.minValue(0)), 6e4),
96
+ default_retry_after_ms: v.optional(v.pipe(v.number(), v.minValue(0)), 6e4),
1945
97
  /** Consecutive auth failures before disabling account */
1946
- max_consecutive_auth_failures: v5.optional(v5.pipe(v5.number(), v5.integer(), v5.minValue(1)), 3),
98
+ max_consecutive_auth_failures: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1)), 3),
1947
99
  /** Backoff after token refresh failure (ms) */
1948
- token_failure_backoff_ms: v5.optional(v5.pipe(v5.number(), v5.minValue(0)), 3e4),
100
+ token_failure_backoff_ms: v.optional(v.pipe(v.number(), v.minValue(0)), 3e4),
1949
101
  /** Enable proactive background token refresh */
1950
- proactive_refresh: v5.optional(v5.boolean(), true),
102
+ proactive_refresh: v.optional(v.boolean(), true),
1951
103
  /** Seconds before expiry to trigger proactive refresh (default 30 min) */
1952
- proactive_refresh_buffer_seconds: v5.optional(v5.pipe(v5.number(), v5.minValue(60)), 1800),
104
+ proactive_refresh_buffer_seconds: v.optional(v.pipe(v.number(), v.minValue(60)), 1800),
1953
105
  /** Interval between background refresh checks in seconds (default 5 min) */
1954
- proactive_refresh_interval_seconds: v5.optional(v5.pipe(v5.number(), v5.minValue(30)), 300),
106
+ proactive_refresh_interval_seconds: v.optional(v.pipe(v.number(), v.minValue(30)), 300),
1955
107
  /** Suppress toast notifications */
1956
- quiet_mode: v5.optional(v5.boolean(), false),
108
+ quiet_mode: v.optional(v.boolean(), false),
1957
109
  /** Enable debug logging */
1958
- debug: v5.optional(v5.boolean(), false)
110
+ debug: v.optional(v.boolean(), false)
1959
111
  });
1960
112
 
1961
113
  // src/oauth.ts
@@ -2034,7 +186,7 @@ function tokenEndpoint() {
2034
186
  return `${OAUTH_ISSUER}/oauth/token`;
2035
187
  }
2036
188
  function parseTokenResponse(json) {
2037
- return v6.parse(TokenResponseSchema, json);
189
+ return v2.parse(TokenResponseSchema, json);
2038
190
  }
2039
191
  async function postTokenForm(body) {
2040
192
  const response = await fetch(tokenEndpoint(), {
@@ -2233,7 +385,7 @@ function buildAuthorizeUrl(redirectUri, pkce, state) {
2233
385
  }
2234
386
 
2235
387
  // src/token.ts
2236
- import * as v7 from "valibot";
388
+ import * as v3 from "valibot";
2237
389
  var PERMANENT_FAILURE_STATUSES = /* @__PURE__ */ new Set([400, 401, 403]);
2238
390
  var refreshMutexByAccountId = /* @__PURE__ */ new Map();
2239
391
  function isTokenExpired(account) {
@@ -2272,7 +424,7 @@ async function refreshToken(currentRefreshToken, accountId, client) {
2272
424
  });
2273
425
  return { ok: false, permanent: isPermanent, status: response.status };
2274
426
  }
2275
- const json = v7.parse(TokenResponseSchema, await response.json());
427
+ const json = v3.parse(TokenResponseSchema, await response.json());
2276
428
  const patch = {
2277
429
  accessToken: json.access_token,
2278
430
  expiresAt: startTime + json.expires_in * 1e3,
@@ -2307,37 +459,61 @@ var AccountManager = createAccountManagerForProvider({
2307
459
  refreshToken
2308
460
  });
2309
461
 
462
+ // src/executor.ts
463
+ import { createExecutorForProvider } from "opencode-multi-account-core";
464
+
465
+ // src/rate-limit.ts
466
+ import { createRateLimitHandlers } from "opencode-multi-account-core";
467
+
2310
468
  // src/config.ts
469
+ import {
470
+ getConfig,
471
+ initCoreConfig,
472
+ loadConfig,
473
+ resetConfigCache,
474
+ updateConfigField
475
+ } from "opencode-multi-account-core";
2311
476
  initCoreConfig("codex-multiauth.json");
2312
477
 
2313
478
  // src/utils.ts
479
+ import { setConfigGetter } from "opencode-multi-account-core";
480
+ import {
481
+ createMinimalClient,
482
+ debugLog,
483
+ formatWaitTime,
484
+ getAccountLabel,
485
+ getConfigDir,
486
+ getErrorCode,
487
+ showToast,
488
+ sleep
489
+ } from "opencode-multi-account-core";
2314
490
  setConfigGetter(getConfig);
2315
491
 
2316
492
  // src/usage.ts
2317
- import * as v8 from "valibot";
493
+ import * as v4 from "valibot";
2318
494
  var OPENAI_AUTH_CLAIM = "https://api.openai.com/auth";
2319
495
  var OPENAI_PROFILE_CLAIM = "https://api.openai.com/profile";
2320
- var OpenAIAuthClaimSchema = v8.object({
2321
- chatgpt_plan_type: v8.optional(v8.string()),
2322
- chatgpt_account_id: v8.optional(v8.string()),
2323
- chatgpt_user_id: v8.optional(v8.string())
496
+ var OpenAIAuthClaimSchema = v4.object({
497
+ chatgpt_plan_type: v4.optional(v4.string()),
498
+ chatgpt_account_id: v4.optional(v4.string()),
499
+ chatgpt_user_id: v4.optional(v4.string())
2324
500
  });
2325
- var OpenAIProfileClaimSchema = v8.object({
2326
- email: v8.optional(v8.string())
501
+ var OpenAIProfileClaimSchema = v4.object({
502
+ email: v4.optional(v4.string())
2327
503
  });
2328
- var WhamRateLimitWindowSchema = v8.object({
2329
- used_percent: v8.number(),
2330
- reset_after_seconds: v8.number()
504
+ var WhamRateLimitWindowSchema = v4.object({
505
+ used_percent: v4.number(),
506
+ reset_after_seconds: v4.number()
2331
507
  });
2332
- var WhamUsageResponseSchema = v8.object({
2333
- plan_type: v8.optional(v8.nullable(v8.string())),
2334
- rate_limit: v8.optional(v8.nullable(v8.object({
2335
- primary_window: v8.optional(v8.nullable(WhamRateLimitWindowSchema)),
2336
- secondary_window: v8.optional(v8.nullable(WhamRateLimitWindowSchema))
508
+ var WhamUsageResponseSchema = v4.object({
509
+ plan_type: v4.optional(v4.nullable(v4.string())),
510
+ rate_limit: v4.optional(v4.nullable(v4.object({
511
+ primary_window: v4.optional(v4.nullable(WhamRateLimitWindowSchema)),
512
+ secondary_window: v4.optional(v4.nullable(WhamRateLimitWindowSchema))
2337
513
  }))),
2338
- credits: v8.optional(v8.nullable(v8.object({
2339
- balance: v8.optional(v8.nullable(v8.string())),
2340
- unlimited: v8.optional(v8.nullable(v8.boolean()))
514
+ credits: v4.optional(v4.nullable(v4.object({
515
+ balance: v4.optional(v4.nullable(v4.string())),
516
+ unlimited: v4.optional(v4.nullable(v4.boolean()))
2341
517
  })))
2342
518
  });
2343
519
  function secondsToISOResetTime(resetAfterSeconds) {
@@ -2359,7 +535,7 @@ async function fetchUsage(accessToken, accountId) {
2359
535
  if (!response.ok) {
2360
536
  return { ok: false, reason: `HTTP ${response.status} ${response.statusText}` };
2361
537
  }
2362
- const parsed = v8.safeParse(WhamUsageResponseSchema, await response.json());
538
+ const parsed = v4.safeParse(WhamUsageResponseSchema, await response.json());
2363
539
  if (!parsed.success) {
2364
540
  return { ok: false, reason: `Invalid response: ${parsed.issues[0]?.message ?? "unknown"}` };
2365
541
  }
@@ -2377,7 +553,7 @@ async function fetchUsage(accessToken, accountId) {
2377
553
  } : null,
2378
554
  seven_day_sonnet: null
2379
555
  };
2380
- const validated = v8.safeParse(UsageLimitsSchema2, usage);
556
+ const validated = v4.safeParse(UsageLimitsSchema, usage);
2381
557
  if (!validated.success) {
2382
558
  return { ok: false, reason: `Mapping error: ${validated.issues[0]?.message ?? "unknown"}` };
2383
559
  }
@@ -2399,10 +575,10 @@ function fetchProfile(accessToken) {
2399
575
  }
2400
576
  const record = claims;
2401
577
  const profileClaim = record[OPENAI_PROFILE_CLAIM];
2402
- const profileParsed = v8.safeParse(OpenAIProfileClaimSchema, profileClaim ?? {});
578
+ const profileParsed = v4.safeParse(OpenAIProfileClaimSchema, profileClaim ?? {});
2403
579
  const email = profileParsed.success ? profileParsed.output.email : void 0;
2404
580
  const authClaim = record[OPENAI_AUTH_CLAIM];
2405
- const authParsed = v8.safeParse(OpenAIAuthClaimSchema, authClaim ?? {});
581
+ const authParsed = v4.safeParse(OpenAIAuthClaimSchema, authClaim ?? {});
2406
582
  const planType = authParsed.success ? authParsed.output.chatgpt_plan_type ?? "" : "";
2407
583
  const planTier = derivePlanTier(planType);
2408
584
  return {
@@ -2422,7 +598,7 @@ function formatTimeRemaining(resetAt) {
2422
598
  }
2423
599
  function getUsageSummary(account) {
2424
600
  if (!account.cachedUsage) return "no usage data";
2425
- const parsed = v8.safeParse(UsageLimitsSchema2, account.cachedUsage);
601
+ const parsed = v4.safeParse(UsageLimitsSchema, account.cachedUsage);
2426
602
  if (!parsed.success) return "no usage data";
2427
603
  const parts = [];
2428
604
  const { five_hour, seven_day } = parsed.output;
@@ -2470,7 +646,22 @@ var { executeWithAccountRotation } = createExecutorForProvider("Codex", {
2470
646
  });
2471
647
 
2472
648
  // src/auth-handler.ts
2473
- import * as v9 from "valibot";
649
+ import * as v5 from "valibot";
650
+
651
+ // src/ui/ansi.ts
652
+ import {
653
+ ANSI,
654
+ isTTY,
655
+ parseKey
656
+ } from "opencode-multi-account-core";
657
+
658
+ // src/ui/select.ts
659
+ import {
660
+ select
661
+ } from "opencode-multi-account-core";
662
+
663
+ // src/ui/confirm.ts
664
+ import { confirm } from "opencode-multi-account-core";
2474
665
 
2475
666
  // src/ui/auth-menu.ts
2476
667
  function formatRelativeTime(timestamp) {
@@ -2684,15 +875,21 @@ function printQuotaError(account, error) {
2684
875
  }
2685
876
 
2686
877
  // src/account-store.ts
2687
- setAccountsFilename(ACCOUNTS_FILENAME2);
878
+ import {
879
+ AccountStore,
880
+ setAccountsFilename
881
+ } from "opencode-multi-account-core";
882
+ setAccountsFilename(ACCOUNTS_FILENAME);
2688
883
 
2689
884
  // src/auth-handler.ts
2690
- import { randomUUID as randomUUID2 } from "node:crypto";
2691
- var DeviceUserCodeResponseSchema = v9.object({
2692
- device_code: v9.string(),
2693
- user_code: v9.string(),
2694
- expires_in: v9.number(),
2695
- interval: v9.optional(v9.number())
885
+ import { randomUUID } from "crypto";
886
+ import { createInterface } from "readline";
887
+ import { exec } from "child_process";
888
+ var DeviceUserCodeResponseSchema = v5.object({
889
+ device_code: v5.string(),
890
+ user_code: v5.string(),
891
+ expires_in: v5.number(),
892
+ interval: v5.optional(v5.number())
2696
893
  });
2697
894
  function makeFailedFlowResult(message) {
2698
895
  return {
@@ -2732,7 +929,7 @@ async function pollDeviceAuthToken(startResult) {
2732
929
  })
2733
930
  });
2734
931
  if (response.ok) {
2735
- return v9.parse(TokenResponseSchema, await response.json());
932
+ return v5.parse(TokenResponseSchema, await response.json());
2736
933
  }
2737
934
  const payload = await response.json().catch(() => ({}));
2738
935
  const error = typeof payload.error === "string" ? payload.error : "";
@@ -2785,7 +982,7 @@ async function startDeviceAuth() {
2785
982
  if (!response.ok) {
2786
983
  throw new Error(`Failed to start device authorization: ${response.status}`);
2787
984
  }
2788
- const startResult = v9.parse(DeviceUserCodeResponseSchema, await response.json());
985
+ const startResult = v5.parse(DeviceUserCodeResponseSchema, await response.json());
2789
986
  return {
2790
987
  url: `${OAUTH_ISSUER}/codex/device`,
2791
988
  instructions: `Enter code: ${startResult.user_code}`,
@@ -2869,7 +1066,7 @@ async function persistFallback(auth, accountId) {
2869
1066
  const store = new AccountStore();
2870
1067
  const now = Date.now();
2871
1068
  const account = {
2872
- uuid: randomUUID2(),
1069
+ uuid: randomUUID(),
2873
1070
  accountId,
2874
1071
  refreshToken: auth.refresh,
2875
1072
  accessToken: auth.access,
@@ -3060,6 +1257,7 @@ Retrying authentication for ${label}...
3060
1257
  }
3061
1258
 
3062
1259
  // src/proactive-refresh.ts
1260
+ import { createProactiveRefreshQueueForProvider } from "opencode-multi-account-core";
3063
1261
  var ProactiveRefreshQueue = createProactiveRefreshQueueForProvider({
3064
1262
  providerAuthId: "openai",
3065
1263
  getConfig,
@@ -3131,11 +1329,14 @@ function transformRequestUrl(input) {
3131
1329
  }
3132
1330
 
3133
1331
  // src/runtime-factory.ts
1332
+ import { TokenRefreshError } from "opencode-multi-account-core";
3134
1333
  var AccountRuntimeFactory = class {
3135
1334
  constructor(store, client) {
3136
1335
  this.store = store;
3137
1336
  this.client = client;
3138
1337
  }
1338
+ store;
1339
+ client;
3139
1340
  runtimes = /* @__PURE__ */ new Map();
3140
1341
  initLocks = /* @__PURE__ */ new Map();
3141
1342
  async getRuntime(uuid) {
@@ -3341,3 +1542,4 @@ var CodexMultiAuthPlugin = async (ctx) => {
3341
1542
  export {
3342
1543
  CodexMultiAuthPlugin
3343
1544
  };
1545
+ //# sourceMappingURL=index.js.map