oc-chatgpt-multi-auth 4.9.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/LICENSE +37 -0
- package/README.md +507 -0
- package/assets/opencode-logo-ornate-dark.svg +18 -0
- package/assets/readme-hero.svg +31 -0
- package/config/README.md +110 -0
- package/config/minimal-opencode.json +13 -0
- package/config/opencode-legacy.json +572 -0
- package/config/opencode-modern.json +240 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +971 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/accounts.d.ts +120 -0
- package/dist/lib/accounts.d.ts.map +1 -0
- package/dist/lib/accounts.js +579 -0
- package/dist/lib/accounts.js.map +1 -0
- package/dist/lib/auth/auth.d.ts +51 -0
- package/dist/lib/auth/auth.d.ts.map +1 -0
- package/dist/lib/auth/auth.js +180 -0
- package/dist/lib/auth/auth.js.map +1 -0
- package/dist/lib/auth/browser.d.ts +17 -0
- package/dist/lib/auth/browser.d.ts.map +1 -0
- package/dist/lib/auth/browser.js +83 -0
- package/dist/lib/auth/browser.js.map +1 -0
- package/dist/lib/auth/server.d.ts +10 -0
- package/dist/lib/auth/server.d.ts.map +1 -0
- package/dist/lib/auth/server.js +85 -0
- package/dist/lib/auth/server.js.map +1 -0
- package/dist/lib/auto-update-checker.d.ts +10 -0
- package/dist/lib/auto-update-checker.d.ts.map +1 -0
- package/dist/lib/auto-update-checker.js +129 -0
- package/dist/lib/auto-update-checker.js.map +1 -0
- package/dist/lib/cli.d.ts +9 -0
- package/dist/lib/cli.d.ts.map +1 -0
- package/dist/lib/cli.js +50 -0
- package/dist/lib/cli.js.map +1 -0
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +102 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/constants.d.ts +74 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +74 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/context-overflow.d.ts +27 -0
- package/dist/lib/context-overflow.d.ts.map +1 -0
- package/dist/lib/context-overflow.js +124 -0
- package/dist/lib/context-overflow.js.map +1 -0
- package/dist/lib/index.d.ts +13 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +13 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/logger.d.ts +22 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +175 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/oauth-success.html +712 -0
- package/dist/lib/prompts/codex-opencode-bridge.d.ts +19 -0
- package/dist/lib/prompts/codex-opencode-bridge.d.ts.map +1 -0
- package/dist/lib/prompts/codex-opencode-bridge.js +152 -0
- package/dist/lib/prompts/codex-opencode-bridge.js.map +1 -0
- package/dist/lib/prompts/codex.d.ts +32 -0
- package/dist/lib/prompts/codex.d.ts.map +1 -0
- package/dist/lib/prompts/codex.js +262 -0
- package/dist/lib/prompts/codex.js.map +1 -0
- package/dist/lib/prompts/opencode-codex.d.ts +21 -0
- package/dist/lib/prompts/opencode-codex.d.ts.map +1 -0
- package/dist/lib/prompts/opencode-codex.js +91 -0
- package/dist/lib/prompts/opencode-codex.js.map +1 -0
- package/dist/lib/recovery/constants.d.ts +12 -0
- package/dist/lib/recovery/constants.d.ts.map +1 -0
- package/dist/lib/recovery/constants.js +25 -0
- package/dist/lib/recovery/constants.js.map +1 -0
- package/dist/lib/recovery/index.d.ts +12 -0
- package/dist/lib/recovery/index.d.ts.map +1 -0
- package/dist/lib/recovery/index.js +12 -0
- package/dist/lib/recovery/index.js.map +1 -0
- package/dist/lib/recovery/storage.d.ts +24 -0
- package/dist/lib/recovery/storage.d.ts.map +1 -0
- package/dist/lib/recovery/storage.js +354 -0
- package/dist/lib/recovery/storage.js.map +1 -0
- package/dist/lib/recovery/types.d.ts +116 -0
- package/dist/lib/recovery/types.d.ts.map +1 -0
- package/dist/lib/recovery/types.js +7 -0
- package/dist/lib/recovery/types.js.map +1 -0
- package/dist/lib/recovery.d.ts +31 -0
- package/dist/lib/recovery.d.ts.map +1 -0
- package/dist/lib/recovery.js +308 -0
- package/dist/lib/recovery.js.map +1 -0
- package/dist/lib/refresh-queue.d.ts +100 -0
- package/dist/lib/refresh-queue.d.ts.map +1 -0
- package/dist/lib/refresh-queue.js +196 -0
- package/dist/lib/refresh-queue.js.map +1 -0
- package/dist/lib/request/fetch-helpers.d.ts +81 -0
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -0
- package/dist/lib/request/fetch-helpers.js +325 -0
- package/dist/lib/request/fetch-helpers.js.map +1 -0
- package/dist/lib/request/helpers/input-utils.d.ts +7 -0
- package/dist/lib/request/helpers/input-utils.d.ts.map +1 -0
- package/dist/lib/request/helpers/input-utils.js +213 -0
- package/dist/lib/request/helpers/input-utils.js.map +1 -0
- package/dist/lib/request/helpers/model-map.d.ts +28 -0
- package/dist/lib/request/helpers/model-map.d.ts.map +1 -0
- package/dist/lib/request/helpers/model-map.js +109 -0
- package/dist/lib/request/helpers/model-map.js.map +1 -0
- package/dist/lib/request/rate-limit-backoff.d.ts +17 -0
- package/dist/lib/request/rate-limit-backoff.d.ts.map +1 -0
- package/dist/lib/request/rate-limit-backoff.js +74 -0
- package/dist/lib/request/rate-limit-backoff.js.map +1 -0
- package/dist/lib/request/request-transformer.d.ts +93 -0
- package/dist/lib/request/request-transformer.d.ts.map +1 -0
- package/dist/lib/request/request-transformer.js +405 -0
- package/dist/lib/request/request-transformer.js.map +1 -0
- package/dist/lib/request/response-handler.d.ts +14 -0
- package/dist/lib/request/response-handler.d.ts.map +1 -0
- package/dist/lib/request/response-handler.js +90 -0
- package/dist/lib/request/response-handler.js.map +1 -0
- package/dist/lib/rotation.d.ts +121 -0
- package/dist/lib/rotation.d.ts.map +1 -0
- package/dist/lib/rotation.js +248 -0
- package/dist/lib/rotation.js.map +1 -0
- package/dist/lib/storage.d.ts +91 -0
- package/dist/lib/storage.d.ts.map +1 -0
- package/dist/lib/storage.js +323 -0
- package/dist/lib/storage.js.map +1 -0
- package/dist/lib/types.d.ts +185 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/package.json +86 -0
- package/scripts/copy-oauth-success.js +37 -0
- package/scripts/install-opencode-codex-auth.js +193 -0
- package/scripts/test-all-models.sh +260 -0
- package/scripts/validate-model-map.sh +97 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rotation Strategy Module
|
|
3
|
+
*
|
|
4
|
+
* Implements health-based account selection with token bucket rate limiting.
|
|
5
|
+
* Ported from antigravity-auth rotation logic for optimal account rotation
|
|
6
|
+
* when rate limits are encountered.
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_HEALTH_SCORE_CONFIG = {
|
|
9
|
+
successDelta: 1,
|
|
10
|
+
rateLimitDelta: -10,
|
|
11
|
+
failureDelta: -20,
|
|
12
|
+
maxScore: 100,
|
|
13
|
+
minScore: 0,
|
|
14
|
+
passiveRecoveryPerHour: 2,
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Tracks health scores for accounts to prioritize healthy accounts.
|
|
18
|
+
* Accounts with higher health scores are preferred for selection.
|
|
19
|
+
*/
|
|
20
|
+
export class HealthScoreTracker {
|
|
21
|
+
entries = new Map();
|
|
22
|
+
config;
|
|
23
|
+
constructor(config = {}) {
|
|
24
|
+
this.config = { ...DEFAULT_HEALTH_SCORE_CONFIG, ...config };
|
|
25
|
+
}
|
|
26
|
+
getKey(accountIndex, quotaKey) {
|
|
27
|
+
return quotaKey ? `${accountIndex}:${quotaKey}` : `${accountIndex}`;
|
|
28
|
+
}
|
|
29
|
+
applyPassiveRecovery(entry) {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
const hoursSinceUpdate = (now - entry.lastUpdated) / (1000 * 60 * 60);
|
|
32
|
+
const recovery = hoursSinceUpdate * this.config.passiveRecoveryPerHour;
|
|
33
|
+
return Math.min(entry.score + recovery, this.config.maxScore);
|
|
34
|
+
}
|
|
35
|
+
getScore(accountIndex, quotaKey) {
|
|
36
|
+
const key = this.getKey(accountIndex, quotaKey);
|
|
37
|
+
const entry = this.entries.get(key);
|
|
38
|
+
if (!entry)
|
|
39
|
+
return this.config.maxScore;
|
|
40
|
+
return this.applyPassiveRecovery(entry);
|
|
41
|
+
}
|
|
42
|
+
getConsecutiveFailures(accountIndex, quotaKey) {
|
|
43
|
+
const key = this.getKey(accountIndex, quotaKey);
|
|
44
|
+
const entry = this.entries.get(key);
|
|
45
|
+
return entry?.consecutiveFailures ?? 0;
|
|
46
|
+
}
|
|
47
|
+
recordSuccess(accountIndex, quotaKey) {
|
|
48
|
+
const key = this.getKey(accountIndex, quotaKey);
|
|
49
|
+
const entry = this.entries.get(key);
|
|
50
|
+
const baseScore = entry ? this.applyPassiveRecovery(entry) : this.config.maxScore;
|
|
51
|
+
const newScore = Math.min(baseScore + this.config.successDelta, this.config.maxScore);
|
|
52
|
+
this.entries.set(key, {
|
|
53
|
+
score: newScore,
|
|
54
|
+
lastUpdated: Date.now(),
|
|
55
|
+
consecutiveFailures: 0,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
recordRateLimit(accountIndex, quotaKey) {
|
|
59
|
+
const key = this.getKey(accountIndex, quotaKey);
|
|
60
|
+
const entry = this.entries.get(key);
|
|
61
|
+
const baseScore = entry ? this.applyPassiveRecovery(entry) : this.config.maxScore;
|
|
62
|
+
const newScore = Math.max(baseScore + this.config.rateLimitDelta, this.config.minScore);
|
|
63
|
+
this.entries.set(key, {
|
|
64
|
+
score: newScore,
|
|
65
|
+
lastUpdated: Date.now(),
|
|
66
|
+
consecutiveFailures: (entry?.consecutiveFailures ?? 0) + 1,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
recordFailure(accountIndex, quotaKey) {
|
|
70
|
+
const key = this.getKey(accountIndex, quotaKey);
|
|
71
|
+
const entry = this.entries.get(key);
|
|
72
|
+
const baseScore = entry ? this.applyPassiveRecovery(entry) : this.config.maxScore;
|
|
73
|
+
const newScore = Math.max(baseScore + this.config.failureDelta, this.config.minScore);
|
|
74
|
+
this.entries.set(key, {
|
|
75
|
+
score: newScore,
|
|
76
|
+
lastUpdated: Date.now(),
|
|
77
|
+
consecutiveFailures: (entry?.consecutiveFailures ?? 0) + 1,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
reset(accountIndex, quotaKey) {
|
|
81
|
+
const key = this.getKey(accountIndex, quotaKey);
|
|
82
|
+
this.entries.delete(key);
|
|
83
|
+
}
|
|
84
|
+
clear() {
|
|
85
|
+
this.entries.clear();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export const DEFAULT_TOKEN_BUCKET_CONFIG = {
|
|
89
|
+
maxTokens: 50,
|
|
90
|
+
tokensPerMinute: 6,
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Client-side token bucket for rate limiting requests per account.
|
|
94
|
+
* Prevents sending requests to accounts that are likely to be rate-limited.
|
|
95
|
+
*/
|
|
96
|
+
export class TokenBucketTracker {
|
|
97
|
+
buckets = new Map();
|
|
98
|
+
config;
|
|
99
|
+
constructor(config = {}) {
|
|
100
|
+
this.config = { ...DEFAULT_TOKEN_BUCKET_CONFIG, ...config };
|
|
101
|
+
}
|
|
102
|
+
getKey(accountIndex, quotaKey) {
|
|
103
|
+
return quotaKey ? `${accountIndex}:${quotaKey}` : `${accountIndex}`;
|
|
104
|
+
}
|
|
105
|
+
refillTokens(entry) {
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
const minutesSinceRefill = (now - entry.lastRefill) / (1000 * 60);
|
|
108
|
+
const tokensToAdd = minutesSinceRefill * this.config.tokensPerMinute;
|
|
109
|
+
return Math.min(entry.tokens + tokensToAdd, this.config.maxTokens);
|
|
110
|
+
}
|
|
111
|
+
getTokens(accountIndex, quotaKey) {
|
|
112
|
+
const key = this.getKey(accountIndex, quotaKey);
|
|
113
|
+
const entry = this.buckets.get(key);
|
|
114
|
+
if (!entry)
|
|
115
|
+
return this.config.maxTokens;
|
|
116
|
+
return this.refillTokens(entry);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Attempt to consume a token. Returns true if successful, false if bucket is empty.
|
|
120
|
+
*/
|
|
121
|
+
tryConsume(accountIndex, quotaKey) {
|
|
122
|
+
const key = this.getKey(accountIndex, quotaKey);
|
|
123
|
+
const entry = this.buckets.get(key);
|
|
124
|
+
const currentTokens = entry ? this.refillTokens(entry) : this.config.maxTokens;
|
|
125
|
+
if (currentTokens < 1) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
this.buckets.set(key, {
|
|
129
|
+
tokens: currentTokens - 1,
|
|
130
|
+
lastRefill: Date.now(),
|
|
131
|
+
});
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Drain tokens on rate limit to prevent immediate retries.
|
|
136
|
+
*/
|
|
137
|
+
drain(accountIndex, quotaKey, drainAmount = 10) {
|
|
138
|
+
const key = this.getKey(accountIndex, quotaKey);
|
|
139
|
+
const entry = this.buckets.get(key);
|
|
140
|
+
const currentTokens = entry ? this.refillTokens(entry) : this.config.maxTokens;
|
|
141
|
+
this.buckets.set(key, {
|
|
142
|
+
tokens: Math.max(0, currentTokens - drainAmount),
|
|
143
|
+
lastRefill: Date.now(),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
reset(accountIndex, quotaKey) {
|
|
147
|
+
const key = this.getKey(accountIndex, quotaKey);
|
|
148
|
+
this.buckets.delete(key);
|
|
149
|
+
}
|
|
150
|
+
clear() {
|
|
151
|
+
this.buckets.clear();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export const DEFAULT_HYBRID_SELECTION_CONFIG = {
|
|
155
|
+
healthWeight: 2,
|
|
156
|
+
tokenWeight: 5,
|
|
157
|
+
freshnessWeight: 0.1,
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Selects the best account using a hybrid scoring strategy.
|
|
161
|
+
*
|
|
162
|
+
* Score = (health * healthWeight) + (tokens * tokenWeight) + (freshness * freshnessWeight)
|
|
163
|
+
*
|
|
164
|
+
* Where:
|
|
165
|
+
* - health: Account health score (0-100)
|
|
166
|
+
* - tokens: Available tokens in bucket (0-maxTokens)
|
|
167
|
+
* - freshness: Hours since last used (higher = more fresh for rotation)
|
|
168
|
+
*/
|
|
169
|
+
export function selectHybridAccount(accounts, healthTracker, tokenTracker, quotaKey, config = {}) {
|
|
170
|
+
const cfg = { ...DEFAULT_HYBRID_SELECTION_CONFIG, ...config };
|
|
171
|
+
const available = accounts.filter((a) => a.isAvailable);
|
|
172
|
+
if (available.length === 0)
|
|
173
|
+
return null;
|
|
174
|
+
if (available.length === 1)
|
|
175
|
+
return available[0];
|
|
176
|
+
const now = Date.now();
|
|
177
|
+
let bestAccount = null;
|
|
178
|
+
let bestScore = -Infinity;
|
|
179
|
+
for (const account of available) {
|
|
180
|
+
const health = healthTracker.getScore(account.index, quotaKey);
|
|
181
|
+
const tokens = tokenTracker.getTokens(account.index, quotaKey);
|
|
182
|
+
const hoursSinceUsed = (now - account.lastUsed) / (1000 * 60 * 60);
|
|
183
|
+
const score = health * cfg.healthWeight +
|
|
184
|
+
tokens * cfg.tokenWeight +
|
|
185
|
+
hoursSinceUsed * cfg.freshnessWeight;
|
|
186
|
+
if (score > bestScore) {
|
|
187
|
+
bestScore = score;
|
|
188
|
+
bestAccount = account;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return bestAccount;
|
|
192
|
+
}
|
|
193
|
+
// ============================================================================
|
|
194
|
+
// Utility Functions
|
|
195
|
+
// ============================================================================
|
|
196
|
+
/**
|
|
197
|
+
* Adds random jitter to a delay value.
|
|
198
|
+
* @param baseMs - Base delay in milliseconds
|
|
199
|
+
* @param jitterFactor - Jitter factor (0-1), default 0.1 (10%)
|
|
200
|
+
* @returns Delay with jitter applied
|
|
201
|
+
*/
|
|
202
|
+
export function addJitter(baseMs, jitterFactor = 0.1) {
|
|
203
|
+
const jitter = baseMs * jitterFactor * (Math.random() * 2 - 1);
|
|
204
|
+
return Math.max(0, Math.floor(baseMs + jitter));
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Returns a random delay within a range.
|
|
208
|
+
* @param minMs - Minimum delay in milliseconds
|
|
209
|
+
* @param maxMs - Maximum delay in milliseconds
|
|
210
|
+
* @returns Random delay within range
|
|
211
|
+
*/
|
|
212
|
+
export function randomDelay(minMs, maxMs) {
|
|
213
|
+
return Math.floor(minMs + Math.random() * (maxMs - minMs));
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Calculates exponential backoff with jitter.
|
|
217
|
+
* @param attempt - Attempt number (1-based)
|
|
218
|
+
* @param baseMs - Base delay in milliseconds
|
|
219
|
+
* @param maxMs - Maximum delay in milliseconds
|
|
220
|
+
* @param jitterFactor - Jitter factor (0-1)
|
|
221
|
+
* @returns Backoff delay with jitter
|
|
222
|
+
*/
|
|
223
|
+
export function exponentialBackoff(attempt, baseMs = 1000, maxMs = 60000, jitterFactor = 0.1) {
|
|
224
|
+
const delay = Math.min(baseMs * Math.pow(2, attempt - 1), maxMs);
|
|
225
|
+
return addJitter(delay, jitterFactor);
|
|
226
|
+
}
|
|
227
|
+
// ============================================================================
|
|
228
|
+
// Singleton Instances
|
|
229
|
+
// ============================================================================
|
|
230
|
+
let healthTrackerInstance = null;
|
|
231
|
+
let tokenTrackerInstance = null;
|
|
232
|
+
export function getHealthTracker(config) {
|
|
233
|
+
if (!healthTrackerInstance) {
|
|
234
|
+
healthTrackerInstance = new HealthScoreTracker(config);
|
|
235
|
+
}
|
|
236
|
+
return healthTrackerInstance;
|
|
237
|
+
}
|
|
238
|
+
export function getTokenTracker(config) {
|
|
239
|
+
if (!tokenTrackerInstance) {
|
|
240
|
+
tokenTrackerInstance = new TokenBucketTracker(config);
|
|
241
|
+
}
|
|
242
|
+
return tokenTrackerInstance;
|
|
243
|
+
}
|
|
244
|
+
export function resetTrackers() {
|
|
245
|
+
healthTrackerInstance?.clear();
|
|
246
|
+
tokenTrackerInstance?.clear();
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=rotation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rotation.js","sourceRoot":"","sources":["../../lib/rotation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { type ModelFamily } from "./prompts/codex.js";
|
|
2
|
+
export type CooldownReason = "auth-failure" | "network-error";
|
|
3
|
+
export interface RateLimitStateV3 {
|
|
4
|
+
[key: string]: number | undefined;
|
|
5
|
+
}
|
|
6
|
+
export interface AccountMetadataV1 {
|
|
7
|
+
accountId?: string;
|
|
8
|
+
email?: string;
|
|
9
|
+
refreshToken: string;
|
|
10
|
+
addedAt: number;
|
|
11
|
+
lastUsed: number;
|
|
12
|
+
lastSwitchReason?: "rate-limit" | "initial" | "rotation";
|
|
13
|
+
rateLimitResetTime?: number;
|
|
14
|
+
coolingDownUntil?: number;
|
|
15
|
+
cooldownReason?: CooldownReason;
|
|
16
|
+
}
|
|
17
|
+
export interface AccountStorageV1 {
|
|
18
|
+
version: 1;
|
|
19
|
+
accounts: AccountMetadataV1[];
|
|
20
|
+
activeIndex: number;
|
|
21
|
+
}
|
|
22
|
+
export interface AccountMetadataV3 {
|
|
23
|
+
accountId?: string;
|
|
24
|
+
email?: string;
|
|
25
|
+
refreshToken: string;
|
|
26
|
+
addedAt: number;
|
|
27
|
+
lastUsed: number;
|
|
28
|
+
lastSwitchReason?: "rate-limit" | "initial" | "rotation";
|
|
29
|
+
rateLimitResetTimes?: RateLimitStateV3;
|
|
30
|
+
coolingDownUntil?: number;
|
|
31
|
+
cooldownReason?: CooldownReason;
|
|
32
|
+
}
|
|
33
|
+
export interface AccountStorageV3 {
|
|
34
|
+
version: 3;
|
|
35
|
+
accounts: AccountMetadataV3[];
|
|
36
|
+
activeIndex: number;
|
|
37
|
+
activeIndexByFamily?: Partial<Record<ModelFamily, number>>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Returns the file path for the account storage JSON file.
|
|
41
|
+
* @returns Absolute path to ~/.opencode/openai-codex-accounts.json
|
|
42
|
+
*/
|
|
43
|
+
export declare function getStoragePath(): string;
|
|
44
|
+
/**
|
|
45
|
+
* Removes duplicate accounts, keeping the most recently used entry for each unique key.
|
|
46
|
+
* Deduplication is based on accountId or refreshToken.
|
|
47
|
+
* @param accounts - Array of accounts to deduplicate
|
|
48
|
+
* @returns New array with duplicates removed
|
|
49
|
+
*/
|
|
50
|
+
export declare function deduplicateAccounts<T extends {
|
|
51
|
+
accountId?: string;
|
|
52
|
+
refreshToken: string;
|
|
53
|
+
lastUsed?: number;
|
|
54
|
+
addedAt?: number;
|
|
55
|
+
}>(accounts: T[]): T[];
|
|
56
|
+
/**
|
|
57
|
+
* Removes duplicate accounts by email, keeping the most recently used entry.
|
|
58
|
+
* Accounts without email are always preserved.
|
|
59
|
+
* @param accounts - Array of accounts to deduplicate
|
|
60
|
+
* @returns New array with email duplicates removed
|
|
61
|
+
*/
|
|
62
|
+
export declare function deduplicateAccountsByEmail<T extends {
|
|
63
|
+
email?: string;
|
|
64
|
+
lastUsed?: number;
|
|
65
|
+
addedAt?: number;
|
|
66
|
+
}>(accounts: T[]): T[];
|
|
67
|
+
/**
|
|
68
|
+
* Normalizes and validates account storage data, migrating from v1 to v3 if needed.
|
|
69
|
+
* Handles deduplication, index clamping, and per-family active index mapping.
|
|
70
|
+
* @param data - Raw storage data (unknown format)
|
|
71
|
+
* @returns Normalized AccountStorageV3 or null if invalid
|
|
72
|
+
*/
|
|
73
|
+
export declare function normalizeAccountStorage(data: unknown): AccountStorageV3 | null;
|
|
74
|
+
/**
|
|
75
|
+
* Loads OAuth accounts from disk storage.
|
|
76
|
+
* Automatically migrates v1 storage to v3 format if needed.
|
|
77
|
+
* @returns AccountStorageV3 if file exists and is valid, null otherwise
|
|
78
|
+
*/
|
|
79
|
+
export declare function loadAccounts(): Promise<AccountStorageV3 | null>;
|
|
80
|
+
/**
|
|
81
|
+
* Persists account storage to disk.
|
|
82
|
+
* Creates the .opencode directory if it doesn't exist.
|
|
83
|
+
* @param storage - Account storage data to save
|
|
84
|
+
*/
|
|
85
|
+
export declare function saveAccounts(storage: AccountStorageV3): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Deletes the account storage file from disk.
|
|
88
|
+
* Silently ignores if file doesn't exist.
|
|
89
|
+
*/
|
|
90
|
+
export declare function clearAccounts(): Promise<void>;
|
|
91
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../lib/storage.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAetE,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,eAAe,CAAC;AAE9D,MAAM,WAAW,gBAAgB;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,YAAY,GAAG,SAAS,GAAG,UAAU,CAAC;IACzD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,YAAY,GAAG,SAAS,GAAG,UAAU,CAAC;IACzD,mBAAmB,CAAC,EAAE,gBAAgB,CAAC;IACvC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;CAC5D;AAgBD;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAuDD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,EAC7H,QAAQ,EAAE,CAAC,EAAE,GACZ,CAAC,EAAE,CAEL;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,SAAS;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,EAC1G,QAAQ,EAAE,CAAC,EAAE,GACZ,CAAC,EAAE,CAoDL;AAiED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAyF9E;AAED;;;;GAIG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA2BrE;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAO3E;AAED;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAYnD"}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { createLogger } from "./logger.js";
|
|
5
|
+
import { MODEL_FAMILIES } from "./prompts/codex.js";
|
|
6
|
+
const log = createLogger("storage");
|
|
7
|
+
let storageMutex = Promise.resolve();
|
|
8
|
+
function withStorageLock(fn) {
|
|
9
|
+
const previousMutex = storageMutex;
|
|
10
|
+
let releaseLock;
|
|
11
|
+
storageMutex = new Promise((resolve) => {
|
|
12
|
+
releaseLock = resolve;
|
|
13
|
+
});
|
|
14
|
+
return previousMutex.then(fn).finally(() => releaseLock());
|
|
15
|
+
}
|
|
16
|
+
function getConfigDir() {
|
|
17
|
+
return join(homedir(), ".opencode");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Returns the file path for the account storage JSON file.
|
|
21
|
+
* @returns Absolute path to ~/.opencode/openai-codex-accounts.json
|
|
22
|
+
*/
|
|
23
|
+
export function getStoragePath() {
|
|
24
|
+
return join(getConfigDir(), "openai-codex-accounts.json");
|
|
25
|
+
}
|
|
26
|
+
function nowMs() {
|
|
27
|
+
return Date.now();
|
|
28
|
+
}
|
|
29
|
+
function selectNewestAccount(current, candidate) {
|
|
30
|
+
if (!current)
|
|
31
|
+
return candidate;
|
|
32
|
+
const currentLastUsed = current.lastUsed || 0;
|
|
33
|
+
const candidateLastUsed = candidate.lastUsed || 0;
|
|
34
|
+
if (candidateLastUsed > currentLastUsed)
|
|
35
|
+
return candidate;
|
|
36
|
+
if (candidateLastUsed < currentLastUsed)
|
|
37
|
+
return current;
|
|
38
|
+
const currentAddedAt = current.addedAt || 0;
|
|
39
|
+
const candidateAddedAt = candidate.addedAt || 0;
|
|
40
|
+
return candidateAddedAt >= currentAddedAt ? candidate : current;
|
|
41
|
+
}
|
|
42
|
+
function deduplicateAccountsByKey(accounts) {
|
|
43
|
+
const keyToIndex = new Map();
|
|
44
|
+
const indicesToKeep = new Set();
|
|
45
|
+
for (let i = 0; i < accounts.length; i += 1) {
|
|
46
|
+
const account = accounts[i];
|
|
47
|
+
if (!account)
|
|
48
|
+
continue;
|
|
49
|
+
const key = account.accountId || account.refreshToken;
|
|
50
|
+
if (!key)
|
|
51
|
+
continue;
|
|
52
|
+
const existingIndex = keyToIndex.get(key);
|
|
53
|
+
if (existingIndex === undefined) {
|
|
54
|
+
keyToIndex.set(key, i);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const existing = accounts[existingIndex];
|
|
58
|
+
const newest = selectNewestAccount(existing, account);
|
|
59
|
+
keyToIndex.set(key, newest === account ? i : existingIndex);
|
|
60
|
+
}
|
|
61
|
+
for (const idx of keyToIndex.values()) {
|
|
62
|
+
indicesToKeep.add(idx);
|
|
63
|
+
}
|
|
64
|
+
const result = [];
|
|
65
|
+
for (let i = 0; i < accounts.length; i += 1) {
|
|
66
|
+
if (indicesToKeep.has(i)) {
|
|
67
|
+
const account = accounts[i];
|
|
68
|
+
if (account)
|
|
69
|
+
result.push(account);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Removes duplicate accounts, keeping the most recently used entry for each unique key.
|
|
76
|
+
* Deduplication is based on accountId or refreshToken.
|
|
77
|
+
* @param accounts - Array of accounts to deduplicate
|
|
78
|
+
* @returns New array with duplicates removed
|
|
79
|
+
*/
|
|
80
|
+
export function deduplicateAccounts(accounts) {
|
|
81
|
+
return deduplicateAccountsByKey(accounts);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Removes duplicate accounts by email, keeping the most recently used entry.
|
|
85
|
+
* Accounts without email are always preserved.
|
|
86
|
+
* @param accounts - Array of accounts to deduplicate
|
|
87
|
+
* @returns New array with email duplicates removed
|
|
88
|
+
*/
|
|
89
|
+
export function deduplicateAccountsByEmail(accounts) {
|
|
90
|
+
const emailToNewestIndex = new Map();
|
|
91
|
+
const indicesToKeep = new Set();
|
|
92
|
+
for (let i = 0; i < accounts.length; i += 1) {
|
|
93
|
+
const account = accounts[i];
|
|
94
|
+
if (!account)
|
|
95
|
+
continue;
|
|
96
|
+
const email = account.email?.trim();
|
|
97
|
+
if (!email) {
|
|
98
|
+
indicesToKeep.add(i);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const existingIndex = emailToNewestIndex.get(email);
|
|
102
|
+
if (existingIndex === undefined) {
|
|
103
|
+
emailToNewestIndex.set(email, i);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const existing = accounts[existingIndex];
|
|
107
|
+
if (!existing) {
|
|
108
|
+
emailToNewestIndex.set(email, i);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const existingLastUsed = existing.lastUsed || 0;
|
|
112
|
+
const candidateLastUsed = account.lastUsed || 0;
|
|
113
|
+
const existingAddedAt = existing.addedAt || 0;
|
|
114
|
+
const candidateAddedAt = account.addedAt || 0;
|
|
115
|
+
const isNewer = candidateLastUsed > existingLastUsed ||
|
|
116
|
+
(candidateLastUsed === existingLastUsed && candidateAddedAt > existingAddedAt);
|
|
117
|
+
if (isNewer) {
|
|
118
|
+
emailToNewestIndex.set(email, i);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const idx of emailToNewestIndex.values()) {
|
|
122
|
+
indicesToKeep.add(idx);
|
|
123
|
+
}
|
|
124
|
+
const result = [];
|
|
125
|
+
for (let i = 0; i < accounts.length; i += 1) {
|
|
126
|
+
if (indicesToKeep.has(i)) {
|
|
127
|
+
const account = accounts[i];
|
|
128
|
+
if (account)
|
|
129
|
+
result.push(account);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
function isRecord(value) {
|
|
135
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
136
|
+
}
|
|
137
|
+
function clampIndex(index, length) {
|
|
138
|
+
if (length <= 0)
|
|
139
|
+
return 0;
|
|
140
|
+
return Math.max(0, Math.min(index, length - 1));
|
|
141
|
+
}
|
|
142
|
+
function toAccountKey(account) {
|
|
143
|
+
return account.accountId || account.refreshToken;
|
|
144
|
+
}
|
|
145
|
+
function extractActiveKey(accounts, activeIndex) {
|
|
146
|
+
const candidate = accounts[activeIndex];
|
|
147
|
+
if (!isRecord(candidate))
|
|
148
|
+
return undefined;
|
|
149
|
+
const accountId = typeof candidate.accountId === "string" && candidate.accountId.trim()
|
|
150
|
+
? candidate.accountId
|
|
151
|
+
: undefined;
|
|
152
|
+
const refreshToken = typeof candidate.refreshToken === "string" && candidate.refreshToken.trim()
|
|
153
|
+
? candidate.refreshToken
|
|
154
|
+
: undefined;
|
|
155
|
+
return accountId || refreshToken;
|
|
156
|
+
}
|
|
157
|
+
function migrateV1ToV3(v1) {
|
|
158
|
+
const now = nowMs();
|
|
159
|
+
return {
|
|
160
|
+
version: 3,
|
|
161
|
+
accounts: v1.accounts.map((account) => {
|
|
162
|
+
const rateLimitResetTimes = {};
|
|
163
|
+
if (typeof account.rateLimitResetTime === "number" && account.rateLimitResetTime > now) {
|
|
164
|
+
for (const family of MODEL_FAMILIES) {
|
|
165
|
+
rateLimitResetTimes[family] = account.rateLimitResetTime;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
accountId: account.accountId,
|
|
170
|
+
email: account.email,
|
|
171
|
+
refreshToken: account.refreshToken,
|
|
172
|
+
addedAt: account.addedAt,
|
|
173
|
+
lastUsed: account.lastUsed,
|
|
174
|
+
lastSwitchReason: account.lastSwitchReason,
|
|
175
|
+
rateLimitResetTimes: Object.keys(rateLimitResetTimes).length > 0 ? rateLimitResetTimes : undefined,
|
|
176
|
+
coolingDownUntil: account.coolingDownUntil,
|
|
177
|
+
cooldownReason: account.cooldownReason,
|
|
178
|
+
};
|
|
179
|
+
}),
|
|
180
|
+
activeIndex: v1.activeIndex,
|
|
181
|
+
activeIndexByFamily: {
|
|
182
|
+
"gpt-5.2-codex": v1.activeIndex,
|
|
183
|
+
"codex-max": v1.activeIndex,
|
|
184
|
+
codex: v1.activeIndex,
|
|
185
|
+
"gpt-5.2": v1.activeIndex,
|
|
186
|
+
"gpt-5.1": v1.activeIndex,
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Normalizes and validates account storage data, migrating from v1 to v3 if needed.
|
|
192
|
+
* Handles deduplication, index clamping, and per-family active index mapping.
|
|
193
|
+
* @param data - Raw storage data (unknown format)
|
|
194
|
+
* @returns Normalized AccountStorageV3 or null if invalid
|
|
195
|
+
*/
|
|
196
|
+
export function normalizeAccountStorage(data) {
|
|
197
|
+
if (!isRecord(data)) {
|
|
198
|
+
log.warn("Invalid storage format, ignoring");
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
if (data.version !== 1 && data.version !== 3) {
|
|
202
|
+
log.warn("Unknown storage version, ignoring", {
|
|
203
|
+
version: data.version,
|
|
204
|
+
});
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const rawAccounts = data.accounts;
|
|
208
|
+
if (!Array.isArray(rawAccounts)) {
|
|
209
|
+
log.warn("Invalid storage format, ignoring");
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
const activeIndexValue = typeof data.activeIndex === "number" && Number.isFinite(data.activeIndex)
|
|
213
|
+
? data.activeIndex
|
|
214
|
+
: 0;
|
|
215
|
+
const rawActiveIndex = clampIndex(activeIndexValue, rawAccounts.length);
|
|
216
|
+
const activeKey = extractActiveKey(rawAccounts, rawActiveIndex);
|
|
217
|
+
const fromVersion = data.version;
|
|
218
|
+
const baseStorage = fromVersion === 1
|
|
219
|
+
? migrateV1ToV3(data)
|
|
220
|
+
: data;
|
|
221
|
+
const validAccounts = rawAccounts.filter((account) => isRecord(account) && typeof account.refreshToken === "string" && !!account.refreshToken.trim());
|
|
222
|
+
const deduplicatedAccounts = deduplicateAccountsByEmail(deduplicateAccountsByKey(validAccounts));
|
|
223
|
+
const activeIndex = (() => {
|
|
224
|
+
if (deduplicatedAccounts.length === 0)
|
|
225
|
+
return 0;
|
|
226
|
+
if (activeKey) {
|
|
227
|
+
const mappedIndex = deduplicatedAccounts.findIndex((account) => toAccountKey(account) === activeKey);
|
|
228
|
+
if (mappedIndex >= 0)
|
|
229
|
+
return mappedIndex;
|
|
230
|
+
}
|
|
231
|
+
return clampIndex(rawActiveIndex, deduplicatedAccounts.length);
|
|
232
|
+
})();
|
|
233
|
+
const activeIndexByFamily = {};
|
|
234
|
+
const rawFamilyIndices = isRecord(baseStorage.activeIndexByFamily)
|
|
235
|
+
? baseStorage.activeIndexByFamily
|
|
236
|
+
: {};
|
|
237
|
+
for (const family of MODEL_FAMILIES) {
|
|
238
|
+
const rawIndexValue = rawFamilyIndices[family];
|
|
239
|
+
const rawIndex = typeof rawIndexValue === "number" && Number.isFinite(rawIndexValue)
|
|
240
|
+
? rawIndexValue
|
|
241
|
+
: rawActiveIndex;
|
|
242
|
+
const clampedRawIndex = clampIndex(rawIndex, rawAccounts.length);
|
|
243
|
+
const familyKey = extractActiveKey(rawAccounts, clampedRawIndex);
|
|
244
|
+
let mappedIndex = clampIndex(rawIndex, deduplicatedAccounts.length);
|
|
245
|
+
if (familyKey && deduplicatedAccounts.length > 0) {
|
|
246
|
+
const idx = deduplicatedAccounts.findIndex((account) => toAccountKey(account) === familyKey);
|
|
247
|
+
if (idx >= 0) {
|
|
248
|
+
mappedIndex = idx;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
activeIndexByFamily[family] = mappedIndex;
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
version: 3,
|
|
255
|
+
accounts: deduplicatedAccounts,
|
|
256
|
+
activeIndex,
|
|
257
|
+
activeIndexByFamily,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Loads OAuth accounts from disk storage.
|
|
262
|
+
* Automatically migrates v1 storage to v3 format if needed.
|
|
263
|
+
* @returns AccountStorageV3 if file exists and is valid, null otherwise
|
|
264
|
+
*/
|
|
265
|
+
export async function loadAccounts() {
|
|
266
|
+
try {
|
|
267
|
+
const path = getStoragePath();
|
|
268
|
+
const content = await fs.readFile(path, "utf-8");
|
|
269
|
+
const data = JSON.parse(content);
|
|
270
|
+
const normalized = normalizeAccountStorage(data);
|
|
271
|
+
const storedVersion = isRecord(data) ? data.version : undefined;
|
|
272
|
+
if (normalized && storedVersion !== normalized.version) {
|
|
273
|
+
log.info("Migrating account storage to v3", { from: storedVersion, to: normalized.version });
|
|
274
|
+
try {
|
|
275
|
+
await saveAccounts(normalized);
|
|
276
|
+
}
|
|
277
|
+
catch (saveError) {
|
|
278
|
+
log.warn("Failed to persist migrated storage", { error: String(saveError) });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return normalized;
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
const code = error.code;
|
|
285
|
+
if (code === "ENOENT") {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
log.error("Failed to load account storage", { error: String(error) });
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Persists account storage to disk.
|
|
294
|
+
* Creates the .opencode directory if it doesn't exist.
|
|
295
|
+
* @param storage - Account storage data to save
|
|
296
|
+
*/
|
|
297
|
+
export async function saveAccounts(storage) {
|
|
298
|
+
return withStorageLock(async () => {
|
|
299
|
+
const path = getStoragePath();
|
|
300
|
+
await fs.mkdir(dirname(path), { recursive: true });
|
|
301
|
+
const content = JSON.stringify(storage, null, 2);
|
|
302
|
+
await fs.writeFile(path, content, "utf-8");
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Deletes the account storage file from disk.
|
|
307
|
+
* Silently ignores if file doesn't exist.
|
|
308
|
+
*/
|
|
309
|
+
export async function clearAccounts() {
|
|
310
|
+
return withStorageLock(async () => {
|
|
311
|
+
try {
|
|
312
|
+
const path = getStoragePath();
|
|
313
|
+
await fs.unlink(path);
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
const code = error.code;
|
|
317
|
+
if (code !== "ENOENT") {
|
|
318
|
+
log.error("Failed to clear account storage", { error: String(error) });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
//# sourceMappingURL=storage.js.map
|