@nick3/copilot-api 1.4.6 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{account-DhQb2A6q.js → account-CipKmikF.js} +2 -2
- package/dist/account-CipKmikF.js.map +1 -0
- package/dist/{accounts-manager-B8Y2Y4ab.js → accounts-manager-DhwGrhHF.js} +40 -43
- package/dist/accounts-manager-DhwGrhHF.js.map +1 -0
- package/dist/{accounts-registry-c7rs5Ed9.js → accounts-registry-CQYvRe65.js} +3 -3
- package/dist/{accounts-registry-c7rs5Ed9.js.map → accounts-registry-CQYvRe65.js.map} +1 -1
- package/dist/admin/assets/{index-BFvCJZIK.js → index-B-G_GrPI.js} +7 -7
- package/dist/admin/index.html +1 -1
- package/dist/{auth-Bt0FGr8C.js → auth-BtUQnU2m.js} +6 -6
- package/dist/{auth-Bt0FGr8C.js.map → auth-BtUQnU2m.js.map} +1 -1
- package/dist/{check-usage-By-sJOch.js → check-usage-DiomC9Dl.js} +6 -5
- package/dist/check-usage-DiomC9Dl.js.map +1 -0
- package/dist/{debug-hQJWwXtC.js → debug-BzR5ZQUk.js} +3 -3
- package/dist/{debug-hQJWwXtC.js.map → debug-BzR5ZQUk.js.map} +1 -1
- package/dist/{get-copilot-token-Q4FXtOE9.js → get-copilot-token-DN1P6qll.js} +3 -3
- package/dist/{get-copilot-token-Q4FXtOE9.js.map → get-copilot-token-DN1P6qll.js.map} +1 -1
- package/dist/main.js +6 -7
- package/dist/main.js.map +1 -1
- package/dist/{paths-DoT4SZ8f.js → paths-Cvzy-eLX.js} +2 -2
- package/dist/{paths-DoT4SZ8f.js.map → paths-Cvzy-eLX.js.map} +1 -1
- package/dist/{poll-access-token-Cp_DfHGH.js → poll-access-token-BrRUFB1F.js} +3 -3
- package/dist/{poll-access-token-Cp_DfHGH.js.map → poll-access-token-BrRUFB1F.js.map} +1 -1
- package/dist/{server-Cln-GCLU.js → server-D9M_wFyO.js} +86 -105
- package/dist/server-D9M_wFyO.js.map +1 -0
- package/dist/{start-DH-QnZxL.js → start-D3yEfZnW.js} +15 -17
- package/dist/{start-DH-QnZxL.js.map → start-D3yEfZnW.js.map} +1 -1
- package/dist/{utils-SV0FyqXk.js → utils-53JWve7i.js} +52 -19
- package/dist/utils-53JWve7i.js.map +1 -0
- package/package.json +3 -1
- package/dist/account-DhQb2A6q.js.map +0 -1
- package/dist/accounts-manager-B8Y2Y4ab.js.map +0 -1
- package/dist/check-usage-By-sJOch.js.map +0 -1
- package/dist/server-Cln-GCLU.js.map +0 -1
- package/dist/utils-SV0FyqXk.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-CipKmikF.js","names":["ACCOUNT_TYPE_VALUES: ReadonlyArray<AccountType>"],"sources":["../src/lib/types/account.ts"],"sourcesContent":["import type { ModelsResponse } from \"~/services/copilot/get-models\"\n\n/**\n * Account type for GitHub Copilot subscription.\n */\nexport type AccountType = \"individual\" | \"business\" | \"enterprise\"\n\nexport const ACCOUNT_TYPE_VALUES: ReadonlyArray<AccountType> = [\n \"individual\",\n \"business\",\n \"enterprise\",\n]\n\nexport function isAccountType(value: unknown): value is AccountType {\n return (\n typeof value === \"string\"\n && (ACCOUNT_TYPE_VALUES as ReadonlyArray<string>).includes(value)\n )\n}\n\nexport function parseAccountType(value: unknown): AccountType {\n if (!isAccountType(value)) {\n throw new Error(\n `Invalid account type: ${String(value)}. Valid values: ${ACCOUNT_TYPE_VALUES.join(\n \", \",\n )}`,\n )\n }\n return value\n}\n\n/**\n * Metadata for a registered account, stored in the registry file.\n */\nexport interface AccountMeta {\n /** GitHub login (username) */\n id: string\n /** Account subscription type */\n accountType: AccountType\n /** Timestamp when the account was added */\n addedAt: number\n}\n\n/**\n * Registry file structure for storing account metadata.\n */\nexport interface AccountRegistry {\n /** Schema version for future migrations */\n version: 1\n /** Ordered list of accounts (order = priority) */\n accounts: Array<AccountMeta>\n}\n\n/**\n * Runtime state for an account, including tokens and quota information.\n */\nexport interface AccountRuntime extends AccountMeta {\n /** GitHub personal access token */\n githubToken: string\n /** Copilot API token (obtained from GitHub) */\n copilotToken?: string\n /** Account-specific Copilot API base URL returned by GitHub */\n copilotApiUrl?: string\n /** VS Code version for API headers */\n vsCodeVersion?: string\n /** Cached available models for this account */\n models?: ModelsResponse\n /** Timestamp of last models fetch */\n lastModelsFetch?: number\n /** Whether models refresh is in progress */\n isRefreshingModels?: boolean\n /** Promise for an in-flight models refresh */\n modelsRefreshPromise?: Promise<void>\n /** Total premium interactions quota entitlement */\n premiumEntitlement?: number\n /** Remaining premium interactions quota */\n premiumRemaining?: number\n /** Reserved premium interaction units for in-flight requests */\n premiumReserved?: number\n /** Internal reservation map for idempotent release */\n premiumReservations?: Map<symbol, number>\n /** Whether this account has unlimited quota */\n unlimited?: boolean\n /** Whether this account allows overage billing (enterprise feature) */\n overagePermitted?: boolean\n /** Timestamp of last quota fetch */\n lastQuotaFetch?: number\n /** Token refresh timer reference */\n refreshTimer?: ReturnType<typeof setInterval>\n /** Whether this account has failed (e.g., 401 error) */\n failed?: boolean\n /** Failure reason if failed */\n failureReason?: string\n /** Whether quota refresh is in progress (prevents concurrent refreshes) */\n isRefreshingQuota?: boolean\n /** Promise for an in-flight quota refresh (allows concurrent callers to await the same refresh) */\n quotaRefreshPromise?: Promise<void>\n}\n\n/**\n * Context required for making API calls on behalf of an account.\n * This is a subset of AccountRuntime used by service functions.\n */\nexport interface AccountContext {\n /** GitHub personal access token */\n githubToken: string\n /** Copilot API token */\n copilotToken?: string\n /** Account-specific Copilot API base URL */\n copilotApiUrl?: string\n /** Account subscription type */\n accountType: AccountType\n /** VS Code version for API headers */\n vsCodeVersion?: string\n}\n"],"mappings":";AAOA,MAAaA,sBAAkD;CAC7D;CACA;CACA;CACD;AAED,SAAgB,cAAc,OAAsC;AAClE,QACE,OAAO,UAAU,YACb,oBAA8C,SAAS,MAAM;;AAIrE,SAAgB,iBAAiB,OAA6B;AAC5D,KAAI,CAAC,cAAc,MAAM,CACvB,OAAM,IAAI,MACR,yBAAyB,OAAO,MAAM,CAAC,kBAAkB,oBAAoB,KAC3E,KACD,GACF;AAEH,QAAO"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { PATHS } from "./paths-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { getCopilotToken } from "./get-copilot-token-
|
|
1
|
+
import { t as PATHS } from "./paths-Cvzy-eLX.js";
|
|
2
|
+
import { a as loadAccountToken, i as listAccountsFromRegistry, l as saveAccountToken, n as hasLegacyToken, o as readLegacyToken, r as hasRegistry, t as addAccountToRegistry } from "./accounts-registry-CQYvRe65.js";
|
|
3
|
+
import { d as getModels, f as getGitHubUser, m as HTTPError, p as getCopilotUsage } from "./utils-53JWve7i.js";
|
|
4
|
+
import { t as getCopilotToken } from "./get-copilot-token-DN1P6qll.js";
|
|
5
5
|
import consola from "consola";
|
|
6
6
|
import fs from "node:fs";
|
|
7
7
|
|
|
@@ -52,6 +52,7 @@ const defaultConfig = {
|
|
|
52
52
|
},
|
|
53
53
|
allowOriginalModelNamesForAliases: false,
|
|
54
54
|
useFunctionApplyPatch: true,
|
|
55
|
+
forceAgent: false,
|
|
55
56
|
compactUseSmallModel: true,
|
|
56
57
|
messageStartInputTokensFallback: false,
|
|
57
58
|
modelRefreshIntervalHours: 24,
|
|
@@ -104,11 +105,13 @@ function mergeDefaultConfig(config) {
|
|
|
104
105
|
const defaultExtraPrompts = defaultConfig.extraPrompts ?? {};
|
|
105
106
|
const modelReasoningEfforts = config.modelReasoningEfforts ?? {};
|
|
106
107
|
const defaultModelReasoningEfforts = defaultConfig.modelReasoningEfforts ?? {};
|
|
108
|
+
const hasForceAgent = typeof config.forceAgent === "boolean";
|
|
109
|
+
const defaultForceAgent = defaultConfig.forceAgent ?? false;
|
|
107
110
|
const missingExtraPromptModels = Object.keys(defaultExtraPrompts).filter((model) => !Object.hasOwn(extraPrompts, model));
|
|
108
111
|
const missingReasoningEffortModels = Object.keys(defaultModelReasoningEfforts).filter((model) => !Object.hasOwn(modelReasoningEfforts, model));
|
|
109
112
|
const hasExtraPromptChanges = missingExtraPromptModels.length > 0;
|
|
110
113
|
const hasReasoningEffortChanges = missingReasoningEffortModels.length > 0;
|
|
111
|
-
if (!hasExtraPromptChanges && !hasReasoningEffortChanges) return {
|
|
114
|
+
if (!hasExtraPromptChanges && !hasReasoningEffortChanges && !!hasForceAgent) return {
|
|
112
115
|
mergedConfig: config,
|
|
113
116
|
changed: false
|
|
114
117
|
};
|
|
@@ -122,15 +125,15 @@ function mergeDefaultConfig(config) {
|
|
|
122
125
|
modelReasoningEfforts: {
|
|
123
126
|
...defaultModelReasoningEfforts,
|
|
124
127
|
...modelReasoningEfforts
|
|
125
|
-
}
|
|
128
|
+
},
|
|
129
|
+
forceAgent: hasForceAgent ? config.forceAgent : defaultForceAgent
|
|
126
130
|
},
|
|
127
131
|
changed: true
|
|
128
132
|
};
|
|
129
133
|
}
|
|
130
134
|
function mergeDefaultAuth(config) {
|
|
131
135
|
const authConfig = isPlainObject(config.auth) ? config.auth : void 0;
|
|
132
|
-
const
|
|
133
|
-
const nextAuth = { apiKeys: normalizeAuthApiKeys(rawApiKeys) };
|
|
136
|
+
const nextAuth = { apiKeys: normalizeAuthApiKeys(Array.isArray(authConfig?.apiKeys) ? authConfig.apiKeys : void 0) };
|
|
134
137
|
if (authConfig && JSON.stringify(authConfig) === JSON.stringify(nextAuth)) return {
|
|
135
138
|
mergedConfig: config,
|
|
136
139
|
changed: false
|
|
@@ -194,8 +197,7 @@ function applyConfigMerges(config, mergeFns) {
|
|
|
194
197
|
});
|
|
195
198
|
}
|
|
196
199
|
function mergeConfigWithDefaults() {
|
|
197
|
-
const
|
|
198
|
-
const { mergedConfig, changed } = applyConfigMerges(config, [
|
|
200
|
+
const { mergedConfig, changed } = applyConfigMerges(readConfigFromDisk(), [
|
|
199
201
|
mergeDefaultAuth,
|
|
200
202
|
mergeDefaultConfig,
|
|
201
203
|
mergeDefaultAccountAffinity,
|
|
@@ -284,8 +286,7 @@ function isOriginalModelNameAllowedForTarget(modelId) {
|
|
|
284
286
|
return !getAliasTargetSet().has(normalized);
|
|
285
287
|
}
|
|
286
288
|
function getPreferredAliasForTarget(modelId) {
|
|
287
|
-
|
|
288
|
-
return getAliasKeysForTarget(modelId, aliases)[0] ?? null;
|
|
289
|
+
return getAliasKeysForTarget(modelId, getModelAliases())[0] ?? null;
|
|
289
290
|
}
|
|
290
291
|
function getAliasKeysForTarget(target, aliases) {
|
|
291
292
|
const normalizedTarget = target.toLowerCase();
|
|
@@ -321,8 +322,7 @@ function isAccountAffinityEnabled() {
|
|
|
321
322
|
return getConfig().accountAffinity ?? true;
|
|
322
323
|
}
|
|
323
324
|
function getModelRefreshIntervalHours() {
|
|
324
|
-
|
|
325
|
-
return normalizeModelRefreshIntervalHours(config.modelRefreshIntervalHours) ?? defaultConfig.modelRefreshIntervalHours ?? 24;
|
|
325
|
+
return normalizeModelRefreshIntervalHours(getConfig().modelRefreshIntervalHours) ?? defaultConfig.modelRefreshIntervalHours ?? 24;
|
|
326
326
|
}
|
|
327
327
|
function getModelRefreshIntervalMs() {
|
|
328
328
|
const hours = getModelRefreshIntervalHours();
|
|
@@ -388,6 +388,9 @@ function getAnthropicApiKey() {
|
|
|
388
388
|
function isResponsesApiWebSearchEnabled() {
|
|
389
389
|
return getConfig().useResponsesApiWebSearch ?? true;
|
|
390
390
|
}
|
|
391
|
+
function getClaudeTokenMultiplier() {
|
|
392
|
+
return getConfig().claudeTokenMultiplier ?? 1.15;
|
|
393
|
+
}
|
|
391
394
|
|
|
392
395
|
//#endregion
|
|
393
396
|
//#region src/lib/account-affinity.ts
|
|
@@ -444,18 +447,11 @@ var AccountAffinityCache = class {
|
|
|
444
447
|
}
|
|
445
448
|
};
|
|
446
449
|
/**
|
|
447
|
-
* Extract the
|
|
448
|
-
*
|
|
450
|
+
* Extract the affinity key from the request context.
|
|
451
|
+
* Uses the upstream request ID which is deterministic for the same user message.
|
|
449
452
|
*/
|
|
450
453
|
function extractAffinityKey(context) {
|
|
451
|
-
|
|
452
|
-
context.promptCacheKey,
|
|
453
|
-
context.sessionId,
|
|
454
|
-
context.safetyIdentifier
|
|
455
|
-
]) {
|
|
456
|
-
const normalized = candidate?.trim();
|
|
457
|
-
if (normalized) return normalized;
|
|
458
|
-
}
|
|
454
|
+
return context.requestId?.trim() || void 0;
|
|
459
455
|
}
|
|
460
456
|
/**
|
|
461
457
|
* Build the full cache key by combining the affinity key with the model ID.
|
|
@@ -491,6 +487,7 @@ const isSameAuthSnapshot = (a, b) => {
|
|
|
491
487
|
const toAccountContextFromSnapshot = (account, snapshot, copilotToken) => ({
|
|
492
488
|
githubToken: snapshot.githubToken,
|
|
493
489
|
copilotToken,
|
|
490
|
+
...account.copilotApiUrl !== void 0 ? { copilotApiUrl: account.copilotApiUrl } : {},
|
|
494
491
|
accountType: snapshot.accountType,
|
|
495
492
|
vsCodeVersion: account.vsCodeVersion
|
|
496
493
|
});
|
|
@@ -517,12 +514,14 @@ const applyTokenRefreshFailureIfCurrent = (account, snapshot, error) => {
|
|
|
517
514
|
account.failureReason = String(error);
|
|
518
515
|
return true;
|
|
519
516
|
};
|
|
520
|
-
const applyQuotaRefreshSuccessIfCurrent = (account, snapshot,
|
|
517
|
+
const applyQuotaRefreshSuccessIfCurrent = (account, snapshot, result) => {
|
|
521
518
|
if (!isAuthSnapshotCurrent(account, snapshot)) return false;
|
|
519
|
+
const { premium, copilotApiUrl } = result;
|
|
522
520
|
account.premiumEntitlement = premium.entitlement;
|
|
523
521
|
account.premiumRemaining = premium.remaining;
|
|
524
522
|
account.unlimited = premium.unlimited;
|
|
525
523
|
account.overagePermitted = premium.overage_permitted;
|
|
524
|
+
if (copilotApiUrl) account.copilotApiUrl = copilotApiUrl;
|
|
526
525
|
account.lastQuotaFetch = Date.now();
|
|
527
526
|
account.failed = false;
|
|
528
527
|
account.failureReason = void 0;
|
|
@@ -651,8 +650,7 @@ var AccountsManager = class {
|
|
|
651
650
|
async runTokenRefreshTick(account, snapshot, refreshInSeconds) {
|
|
652
651
|
if (!this.shouldContinueTokenRefresh(account, snapshot)) return;
|
|
653
652
|
try {
|
|
654
|
-
const
|
|
655
|
-
const { token, refresh_in } = await getCopilotToken(ctx);
|
|
653
|
+
const { token, refresh_in } = await getCopilotToken(toAccountContextFromSnapshot(account, snapshot));
|
|
656
654
|
if (!this.shouldContinueTokenRefresh(account, snapshot)) return;
|
|
657
655
|
if (!applyTokenRefreshSuccessIfCurrent(account, snapshot, token)) return;
|
|
658
656
|
consola.debug(`Refreshed token for account ${account.id}`);
|
|
@@ -676,15 +674,12 @@ var AccountsManager = class {
|
|
|
676
674
|
async initializeAccount(account) {
|
|
677
675
|
const snapshot = takeAuthSnapshot(account);
|
|
678
676
|
try {
|
|
679
|
-
const
|
|
680
|
-
const { token, refresh_in } = await getCopilotToken(tokenCtx);
|
|
677
|
+
const { token, refresh_in } = await getCopilotToken(toAccountContextFromSnapshot(account, snapshot));
|
|
681
678
|
if (!applyCopilotTokenIfCurrent(account, snapshot, token)) return;
|
|
679
|
+
await this.refreshQuota(account);
|
|
682
680
|
this.startTokenRefresh(account, refresh_in);
|
|
683
|
-
|
|
684
|
-
const models = await getModels(modelsCtx);
|
|
685
|
-
if (!applyModelsIfCurrent(account, snapshot, models)) return;
|
|
681
|
+
if (!applyModelsIfCurrent(account, snapshot, await getModels(toAccountContextFromSnapshot(account, snapshot, token)))) return;
|
|
686
682
|
account.lastModelsFetch = Date.now();
|
|
687
|
-
await this.refreshQuota(account);
|
|
688
683
|
consola.debug(`Account ${account.id} initialized`);
|
|
689
684
|
} catch (error) {
|
|
690
685
|
if (!isAuthSnapshotCurrent(account, snapshot)) return;
|
|
@@ -769,8 +764,7 @@ var AccountsManager = class {
|
|
|
769
764
|
}
|
|
770
765
|
const snapshot = takeAuthSnapshot(account);
|
|
771
766
|
if (account.modelsRefreshPromise) {
|
|
772
|
-
|
|
773
|
-
if (isSameAuthSnapshot(existingSnapshot, snapshot)) {
|
|
767
|
+
if (isSameAuthSnapshot(this.modelsRefreshSnapshotByAccount.get(account), snapshot)) {
|
|
774
768
|
await account.modelsRefreshPromise;
|
|
775
769
|
return;
|
|
776
770
|
}
|
|
@@ -779,8 +773,7 @@ var AccountsManager = class {
|
|
|
779
773
|
const ctx = toAccountContextFromSnapshot(account, snapshot, account.copilotToken);
|
|
780
774
|
const promise = (async () => {
|
|
781
775
|
try {
|
|
782
|
-
|
|
783
|
-
if (applyModelsIfCurrent(account, snapshot, models)) account.lastModelsFetch = Date.now();
|
|
776
|
+
if (applyModelsIfCurrent(account, snapshot, await getModels(ctx))) account.lastModelsFetch = Date.now();
|
|
784
777
|
} catch (error) {
|
|
785
778
|
if (error instanceof HTTPError && error.response.status === 401) {
|
|
786
779
|
applyUnauthorizedIfCurrent(account, snapshot, "Unauthorized (401)");
|
|
@@ -810,8 +803,7 @@ var AccountsManager = class {
|
|
|
810
803
|
async refreshQuota(account) {
|
|
811
804
|
const snapshot = takeAuthSnapshot(account);
|
|
812
805
|
if (account.quotaRefreshPromise) {
|
|
813
|
-
|
|
814
|
-
if (isSameAuthSnapshot(existingSnapshot, snapshot)) {
|
|
806
|
+
if (isSameAuthSnapshot(this.quotaRefreshSnapshotByAccount.get(account), snapshot)) {
|
|
815
807
|
await account.quotaRefreshPromise;
|
|
816
808
|
return;
|
|
817
809
|
}
|
|
@@ -820,8 +812,12 @@ var AccountsManager = class {
|
|
|
820
812
|
const ctx = toAccountContextFromSnapshot(account, snapshot);
|
|
821
813
|
const promise = (async () => {
|
|
822
814
|
try {
|
|
823
|
-
const
|
|
824
|
-
|
|
815
|
+
const usage = await getCopilotUsage(ctx);
|
|
816
|
+
const premium = usage.quota_snapshots.premium_interactions;
|
|
817
|
+
applyQuotaRefreshSuccessIfCurrent(account, snapshot, {
|
|
818
|
+
premium,
|
|
819
|
+
copilotApiUrl: usage.endpoints.api
|
|
820
|
+
});
|
|
825
821
|
} catch (error) {
|
|
826
822
|
if (error instanceof HTTPError && error.response.status === 401) {
|
|
827
823
|
applyUnauthorizedIfCurrent(account, snapshot, "Unauthorized (401)");
|
|
@@ -1180,6 +1176,7 @@ var AccountsManager = class {
|
|
|
1180
1176
|
return {
|
|
1181
1177
|
githubToken: account.githubToken,
|
|
1182
1178
|
copilotToken: account.copilotToken,
|
|
1179
|
+
...account.copilotApiUrl !== void 0 ? { copilotApiUrl: account.copilotApiUrl } : {},
|
|
1183
1180
|
accountType: account.accountType,
|
|
1184
1181
|
vsCodeVersion: account.vsCodeVersion
|
|
1185
1182
|
};
|
|
@@ -1359,5 +1356,5 @@ var AccountsManager = class {
|
|
|
1359
1356
|
const accountsManager = new AccountsManager();
|
|
1360
1357
|
|
|
1361
1358
|
//#endregion
|
|
1362
|
-
export {
|
|
1363
|
-
//# sourceMappingURL=accounts-manager-
|
|
1359
|
+
export { isMessagesApiEnabled as _, getClaudeTokenMultiplier as a, mergeConfigWithDefaults as b, getModelAliases as c, getProviderConfig as d, getReasoningEffortForModel as f, isMessageStartInputTokensFallbackEnabled as g, isForceAgentEnabled as h, getAnthropicApiKey as i, getModelAliasesInfo as l, isAccountAffinityEnabled as m, PROVIDER_TYPE_ANTHROPIC as n, getConfig as o, getSmallModel as p, getAliasTargetSet as r, getExtraPromptForModel as s, accountsManager as t, getModelRefreshIntervalMs as u, isResponsesApiContextManagementModel as v, shouldCompactUseSmallModel as x, isResponsesApiWebSearchEnabled as y };
|
|
1360
|
+
//# sourceMappingURL=accounts-manager-DhwGrhHF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accounts-manager-DhwGrhHF.js","names":["defaultConfig: AppConfig","cachedConfig: AppConfig | null","normalizedTarget","normalized: ModelAliasInfoMap","normalized: ModelAliasMap","runtime: AccountRuntime","accounts: Array<AccountRuntime>","overageFallback:\n | {\n account: AccountRuntime\n model: Model\n endpoint: string\n costUnits: number\n }\n | undefined","statuses: Array<{\n id: string\n entitlement?: number\n remaining?: number\n unlimited?: boolean\n overagePermitted?: boolean\n failed?: boolean\n failureReason?: string\n }>","allAccounts: Array<AccountRuntime>","added: Array<string>","removed: Array<string>","updated: Array<string>","changes: Array<string>"],"sources":["../src/lib/config.ts","../src/lib/account-affinity.ts","../src/lib/accounts-manager-auth.ts","../src/lib/accounts-manager-quota.ts","../src/lib/accounts-manager.ts"],"sourcesContent":["import consola from \"consola\"\nimport fs from \"node:fs\"\n\nimport { PATHS } from \"./paths\"\n\nexport interface AppConfig {\n auth?: {\n apiKeys?: Array<string>\n }\n providers?: Record<string, ProviderConfig>\n extraPrompts?: Record<string, string>\n smallModel?: string\n accountAffinity?: boolean\n /** @deprecated */\n apiKey?: string\n responsesApiContextManagementModels?: Array<string>\n modelReasoningEfforts?: Record<\n string,\n \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\"\n >\n modelAliases?: Record<string, { target: string; allowOriginal?: boolean }>\n allowOriginalModelNamesForAliases?: boolean\n useFunctionApplyPatch?: boolean\n forceAgent?: boolean\n compactUseSmallModel?: boolean\n messageStartInputTokensFallback?: boolean\n modelRefreshIntervalHours?: number\n useMessagesApi?: boolean\n anthropicApiKey?: string\n useResponsesApiWebSearch?: boolean\n claudeTokenMultiplier?: number\n}\n\nexport interface ModelConfig {\n temperature?: number\n topP?: number\n topK?: number\n}\n\nexport const PROVIDER_TYPE_ANTHROPIC = \"anthropic\" as const\n\nexport type ProviderType = typeof PROVIDER_TYPE_ANTHROPIC\n\nexport interface ProviderConfig {\n type?: string\n enabled?: boolean\n baseUrl?: string\n apiKey?: string\n models?: Record<string, ModelConfig>\n adjustInputTokens?: boolean\n}\n\nexport interface ResolvedProviderConfig {\n name: string\n type: ProviderType\n baseUrl: string\n apiKey: string\n models?: Record<string, ModelConfig>\n adjustInputTokens?: boolean\n}\n\nconst gpt5ExplorationPrompt = `## Exploration and reading files\n- **Think first.** Before any tool call, decide ALL files/resources you will need.\n- **Batch everything.** If you need multiple files (even from different places), read them together.\n- **multi_tool_use.parallel** Use multi_tool_use.parallel to parallelize tool calls and only this.\n- **Only make sequential calls if you truly cannot know the next file without seeing a result first.**\n- **Workflow:** (a) plan all needed reads → (b) issue one parallel batch → (c) analyze results → (d) repeat if new, unpredictable reads arise.`\n\nconst gpt5CommentaryPrompt = `# Working with the user\n\nYou interact with the user through a terminal. You have 2 ways of communicating with the users: \n- Share intermediary updates in \\`commentary\\` channel. \n- After you have completed all your work, send a message to the \\`final\\` channel. \n\n## Intermediary updates\n\n- Intermediary updates go to the \\`commentary\\` channel.\n- User updates are short updates while you are working, they are NOT final answers.\n- You use 1-2 sentence user updates to communicate progress and new information to the user as you are doing work.\n- Do not begin responses with conversational interjections or meta commentary. Avoid openers such as acknowledgements (“Done —”, “Got it”, “Great question, ”) or framing phrases.\n- You provide user updates frequently, every 20s.\n- Before exploring or doing substantial work, you start with a user update acknowledging the request and explaining your first step. You should include your understanding of the user request and explain what you will do. Avoid commenting on the request or using starters such as \"Got it -\" or \"Understood -\" etc.\n- When exploring, e.g. searching, reading files, you provide user updates as you go, every 20s, explaining what context you are gathering and what you've learned. Vary your sentence structure when providing these updates to avoid sounding repetitive - in particular, don't start each sentence the same way.\n- After you have sufficient context, and the work is substantial, you provide a longer plan (this is the only user update that may be longer than 2 sentences and can contain formatting).\n- Before performing file edits of any kind, you provide updates explaining what edits you are making.\n- As you are thinking, you very frequently provide updates even if not taking any actions, informing the user of your progress. You interrupt your thinking and send multiple updates in a row if thinking for more than 100 words.\n- Tone of your updates MUST match your personality.`\n\nconst defaultConfig: AppConfig = {\n auth: {\n apiKeys: [],\n },\n providers: {},\n extraPrompts: {\n \"gpt-5-mini\": gpt5ExplorationPrompt,\n \"gpt-5.3-codex\": gpt5CommentaryPrompt,\n \"gpt-5.4-mini\": gpt5CommentaryPrompt,\n \"gpt-5.4\": gpt5CommentaryPrompt,\n },\n smallModel: \"gpt-5-mini\",\n accountAffinity: true,\n responsesApiContextManagementModels: [],\n modelReasoningEfforts: {\n \"gpt-5-mini\": \"low\",\n \"gpt-5.3-codex\": \"xhigh\",\n \"gpt-5.4-mini\": \"xhigh\",\n \"gpt-5.4\": \"xhigh\",\n },\n allowOriginalModelNamesForAliases: false,\n useFunctionApplyPatch: true,\n forceAgent: false,\n compactUseSmallModel: true,\n messageStartInputTokensFallback: false,\n modelRefreshIntervalHours: 24,\n useMessagesApi: true,\n useResponsesApiWebSearch: true,\n}\n\nlet cachedConfig: AppConfig | null = null\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\nfunction normalizeAuthApiKeys(value: unknown): Array<string> {\n if (!Array.isArray(value)) return []\n\n return [\n ...new Set(\n value\n .filter((item): item is string => typeof item === \"string\")\n .map((item) => item.trim())\n .filter((item) => item.length > 0),\n ),\n ]\n}\n\nfunction normalizeModelRefreshIntervalHours(\n value: unknown,\n): number | undefined {\n if (typeof value !== \"number\") return undefined\n if (!Number.isFinite(value)) return undefined\n if (value < 0) return undefined\n return value\n}\n\nfunction ensureConfigFile(): void {\n try {\n fs.accessSync(PATHS.CONFIG_PATH, fs.constants.R_OK)\n return\n } catch {\n // Fall through to try creating the default config file.\n }\n\n try {\n fs.mkdirSync(PATHS.APP_DIR, { recursive: true })\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(defaultConfig, null, 2)}\\n`,\n \"utf8\",\n )\n try {\n fs.chmodSync(PATHS.CONFIG_PATH, 0o600)\n } catch {\n // Ignore chmod errors (e.g. unsupported filesystem).\n }\n } catch {\n // Best-effort only: if we can't create the file, reads will fall back to defaults.\n }\n}\n\nfunction readConfigFromDisk(): AppConfig {\n ensureConfigFile()\n try {\n const raw = fs.readFileSync(PATHS.CONFIG_PATH, \"utf8\")\n if (!raw.trim()) {\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(defaultConfig, null, 2)}\\n`,\n \"utf8\",\n )\n return defaultConfig\n }\n return JSON.parse(raw) as AppConfig\n } catch (error) {\n consola.error(\"Failed to read config file, using default config\", error)\n return defaultConfig\n }\n}\n\nfunction mergeDefaultConfig(config: AppConfig): {\n mergedConfig: AppConfig\n changed: boolean\n} {\n const extraPrompts = config.extraPrompts ?? {}\n const defaultExtraPrompts = defaultConfig.extraPrompts ?? {}\n const modelReasoningEfforts = config.modelReasoningEfforts ?? {}\n const defaultModelReasoningEfforts = defaultConfig.modelReasoningEfforts ?? {}\n const hasForceAgent = typeof config.forceAgent === \"boolean\"\n const defaultForceAgent = defaultConfig.forceAgent ?? false\n\n const missingExtraPromptModels = Object.keys(defaultExtraPrompts).filter(\n (model) => !Object.hasOwn(extraPrompts, model),\n )\n\n const missingReasoningEffortModels = Object.keys(\n defaultModelReasoningEfforts,\n ).filter((model) => !Object.hasOwn(modelReasoningEfforts, model))\n\n const hasExtraPromptChanges = missingExtraPromptModels.length > 0\n const hasReasoningEffortChanges = missingReasoningEffortModels.length > 0\n const hasForceAgentChanges = !hasForceAgent\n\n if (\n !hasExtraPromptChanges\n && !hasReasoningEffortChanges\n && !hasForceAgentChanges\n ) {\n return { mergedConfig: config, changed: false }\n }\n\n return {\n mergedConfig: {\n ...config,\n extraPrompts: {\n ...defaultExtraPrompts,\n ...extraPrompts,\n },\n modelReasoningEfforts: {\n ...defaultModelReasoningEfforts,\n ...modelReasoningEfforts,\n },\n forceAgent: hasForceAgent ? config.forceAgent : defaultForceAgent,\n },\n changed: true,\n }\n}\n\nfunction mergeDefaultAuth(config: AppConfig): {\n mergedConfig: AppConfig\n changed: boolean\n} {\n const authConfig = isPlainObject(config.auth) ? config.auth : undefined\n const rawApiKeys =\n Array.isArray(authConfig?.apiKeys) ? authConfig.apiKeys : undefined\n const normalizedApiKeys = normalizeAuthApiKeys(rawApiKeys)\n const nextAuth = { apiKeys: normalizedApiKeys }\n\n if (authConfig && JSON.stringify(authConfig) === JSON.stringify(nextAuth)) {\n return { mergedConfig: config, changed: false }\n }\n\n return {\n mergedConfig: {\n ...config,\n auth: nextAuth,\n },\n changed: true,\n }\n}\n\nfunction mergeDefaultAccountAffinity(config: AppConfig): {\n mergedConfig: AppConfig\n changed: boolean\n} {\n // Migration: map old freeModelLoadBalancing to accountAffinity\n const raw = config as Record<string, unknown>\n const hasOld = typeof raw.freeModelLoadBalancing === \"boolean\"\n const hasNew = typeof config.accountAffinity === \"boolean\"\n\n if (hasOld) {\n const next = { ...config } as Record<string, unknown>\n if (!hasNew) {\n next.accountAffinity = raw.freeModelLoadBalancing\n }\n delete next.freeModelLoadBalancing\n return { mergedConfig: next as AppConfig, changed: true }\n }\n\n if (hasNew) {\n return { mergedConfig: config, changed: false }\n }\n\n return {\n mergedConfig: {\n ...config,\n accountAffinity: defaultConfig.accountAffinity ?? true,\n },\n changed: true,\n }\n}\n\nfunction mergeDefaultModelRefreshInterval(config: AppConfig): {\n mergedConfig: AppConfig\n changed: boolean\n} {\n const normalized = normalizeModelRefreshIntervalHours(\n config.modelRefreshIntervalHours,\n )\n\n if (normalized !== undefined) {\n return { mergedConfig: config, changed: false }\n }\n\n return {\n mergedConfig: {\n ...config,\n modelRefreshIntervalHours: defaultConfig.modelRefreshIntervalHours ?? 24,\n },\n changed: true,\n }\n}\n\ntype ConfigMergeResult = {\n mergedConfig: AppConfig\n changed: boolean\n}\n\ntype ConfigMergeFn = (config: AppConfig) => ConfigMergeResult\n\nfunction applyConfigMerges(\n config: AppConfig,\n mergeFns: ReadonlyArray<ConfigMergeFn>,\n): ConfigMergeResult {\n return mergeFns.reduce<ConfigMergeResult>(\n (acc, mergeFn) => {\n const result = mergeFn(acc.mergedConfig)\n return {\n mergedConfig: result.mergedConfig,\n changed: acc.changed || result.changed,\n }\n },\n { mergedConfig: config, changed: false },\n )\n}\n\nexport function mergeConfigWithDefaults(): AppConfig {\n const config = readConfigFromDisk()\n\n const { mergedConfig, changed } = applyConfigMerges(config, [\n mergeDefaultAuth,\n mergeDefaultConfig,\n mergeDefaultAccountAffinity,\n mergeDefaultModelRefreshInterval,\n ])\n\n if (changed) {\n try {\n fs.writeFileSync(\n PATHS.CONFIG_PATH,\n `${JSON.stringify(mergedConfig, null, 2)}\\n`,\n \"utf8\",\n )\n } catch (writeError) {\n consola.warn(\"Failed to write merged config defaults\", writeError)\n }\n }\n\n cachedConfig = mergedConfig\n return mergedConfig\n}\n\nexport function getConfig(): AppConfig {\n cachedConfig ??= readConfigFromDisk()\n return cachedConfig\n}\n\ntype ModelAliasSpec = {\n target: string\n allowOriginal?: boolean\n}\n\ntype ModelAliasMap = Record<string, string>\n\ntype ModelAliasInfoMap = Record<string, ModelAliasSpec>\n\ntype ModelAliasRawMap = Record<string, unknown>\n\nfunction normalizeAliasKey(value: string): string | null {\n const trimmed = value.trim().toLowerCase()\n return trimmed.length > 0 ? trimmed : null\n}\n\nfunction normalizeAliasTarget(value: string): string | null {\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nfunction normalizeAliasSpec(value: unknown): ModelAliasSpec | null {\n if (typeof value === \"string\") {\n const normalizedTarget = normalizeAliasTarget(value)\n return normalizedTarget ? { target: normalizedTarget } : null\n }\n if (!value || typeof value !== \"object\") {\n return null\n }\n\n const targetValue = (value as { target?: unknown }).target\n if (typeof targetValue !== \"string\") {\n return null\n }\n\n const normalizedTarget = normalizeAliasTarget(targetValue)\n if (!normalizedTarget) {\n return null\n }\n\n const allowOriginalValue = (value as { allowOriginal?: unknown })\n .allowOriginal\n const allowOriginal =\n typeof allowOriginalValue === \"boolean\" ? allowOriginalValue : undefined\n return { target: normalizedTarget, allowOriginal }\n}\n\nexport function getModelAliasesInfo(): ModelAliasInfoMap {\n const config = getConfig()\n const raw = (config.modelAliases ?? {}) as ModelAliasRawMap\n const normalized: ModelAliasInfoMap = {}\n\n for (const [alias, rawSpec] of Object.entries(raw)) {\n const normalizedAlias = normalizeAliasKey(alias)\n const normalizedSpec = normalizeAliasSpec(rawSpec)\n if (!normalizedAlias || !normalizedSpec) {\n continue\n }\n if (!Object.hasOwn(normalized, normalizedAlias)) {\n normalized[normalizedAlias] = normalizedSpec\n }\n }\n\n return normalized\n}\n\nexport function getModelAliases(): ModelAliasMap {\n const info = getModelAliasesInfo()\n const normalized: ModelAliasMap = {}\n\n for (const [alias, spec] of Object.entries(info)) {\n normalized[alias] = spec.target\n }\n\n return normalized\n}\n\nexport function resolveModelAlias(modelId: string): string {\n const normalized = normalizeAliasKey(modelId)\n if (!normalized) return modelId\n const aliases = getModelAliases()\n return aliases[normalized] ?? modelId\n}\n\nexport function isOriginalModelNameAllowedForAliases(): boolean {\n const config = getConfig()\n return config.allowOriginalModelNamesForAliases ?? false\n}\n\nexport function getAliasTargetSet(): Set<string> {\n const aliases = getModelAliasesInfo()\n const allowOriginalDefault = isOriginalModelNameAllowedForAliases()\n const targetAllowMap = new Map<string, boolean>()\n\n for (const { target, allowOriginal } of Object.values(aliases)) {\n const normalizedTarget = target.toLowerCase()\n const effectiveAllow = allowOriginal ?? allowOriginalDefault\n const currentAllow = targetAllowMap.get(normalizedTarget)\n if (currentAllow === true) {\n continue\n }\n if (effectiveAllow) {\n targetAllowMap.set(normalizedTarget, true)\n } else if (currentAllow === undefined) {\n targetAllowMap.set(normalizedTarget, false)\n }\n }\n\n const blockedTargets = new Set<string>()\n for (const [target, allowed] of targetAllowMap.entries()) {\n if (!allowed) {\n blockedTargets.add(target)\n }\n }\n\n return blockedTargets\n}\n\nexport function isOriginalModelNameAllowedForTarget(modelId: string): boolean {\n const normalized = normalizeAliasKey(modelId)\n if (!normalized) return true\n const blockedTargets = getAliasTargetSet()\n return !blockedTargets.has(normalized)\n}\n\nexport function getPreferredAliasForTarget(modelId: string): string | null {\n const aliases = getModelAliases()\n const aliasKeys = getAliasKeysForTarget(modelId, aliases)\n return aliasKeys[0] ?? null\n}\n\nfunction getAliasKeysForTarget(\n target: string,\n aliases: ModelAliasMap,\n): Array<string> {\n const normalizedTarget = target.toLowerCase()\n return Object.entries(aliases)\n .filter(([, model]) => model.toLowerCase() === normalizedTarget)\n .map(([alias]) => alias)\n .sort()\n}\n\nfunction getAliasFallbackValue<T extends string>(\n record: Record<string, T> | undefined,\n modelId: string,\n aliases: ModelAliasMap,\n): T | undefined {\n if (!record) return undefined\n\n const aliasKeys = getAliasKeysForTarget(modelId, aliases)\n if (aliasKeys.length === 0) return undefined\n\n const recordByAlias = new Map<string, T>()\n for (const [key, value] of Object.entries(record)) {\n const normalized = normalizeAliasKey(key)\n if (normalized) {\n recordByAlias.set(normalized, value)\n }\n }\n\n for (const alias of aliasKeys) {\n const value = recordByAlias.get(alias)\n if (value !== undefined) {\n return value\n }\n }\n\n return undefined\n}\n\nexport function getExtraPromptForModel(model: string): string {\n const config = getConfig()\n const direct = config.extraPrompts?.[model]\n if (direct !== undefined) return direct\n\n const aliases = getModelAliases()\n const fallback = getAliasFallbackValue(config.extraPrompts, model, aliases)\n return fallback ?? \"\"\n}\n\nexport function getSmallModel(): string {\n const config = getConfig()\n const model = config.smallModel ?? \"gpt-5-mini\"\n if (isOriginalModelNameAllowedForTarget(model)) {\n return model\n }\n\n return getPreferredAliasForTarget(model) ?? model\n}\n\nexport function isAccountAffinityEnabled(): boolean {\n const config = getConfig()\n return config.accountAffinity ?? true\n}\n\nexport function getModelRefreshIntervalHours(): number {\n const config = getConfig()\n const normalized = normalizeModelRefreshIntervalHours(\n config.modelRefreshIntervalHours,\n )\n return normalized ?? defaultConfig.modelRefreshIntervalHours ?? 24\n}\n\nexport function getModelRefreshIntervalMs(): number {\n const hours = getModelRefreshIntervalHours()\n if (!Number.isFinite(hours) || hours <= 0) return 0\n return hours * 60 * 60 * 1000\n}\n\nexport function isMessageStartInputTokensFallbackEnabled(): boolean {\n const config = getConfig()\n return config.messageStartInputTokensFallback ?? false\n}\n\nexport function shouldCompactUseSmallModel(): boolean {\n const config = getConfig()\n return config.compactUseSmallModel ?? true\n}\n\nexport function getResponsesApiContextManagementModels(): Array<string> {\n const config = getConfig()\n return (\n config.responsesApiContextManagementModels\n ?? defaultConfig.responsesApiContextManagementModels\n ?? []\n )\n}\n\nexport function isResponsesApiContextManagementModel(model: string): boolean {\n return getResponsesApiContextManagementModels().includes(model)\n}\n\nexport function getReasoningEffortForModel(\n model: string,\n): \"none\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" {\n const config = getConfig()\n const direct = config.modelReasoningEfforts?.[model]\n if (direct !== undefined) return direct\n\n const aliases = getModelAliases()\n const fallback = getAliasFallbackValue(\n config.modelReasoningEfforts,\n model,\n aliases,\n )\n return fallback ?? \"high\"\n}\n\nexport function isForceAgentEnabled(): boolean {\n const config = getConfig()\n return config.forceAgent ?? false\n}\n\nexport function normalizeProviderBaseUrl(url: string): string {\n return url.trim().replace(/\\/+$/u, \"\")\n}\n\nexport function getProviderConfig(name: string): ResolvedProviderConfig | null {\n const providerName = name.trim()\n if (!providerName) {\n return null\n }\n\n const config = getConfig()\n const provider = config.providers?.[providerName]\n if (!provider) {\n return null\n }\n\n if (provider.enabled === false) {\n return null\n }\n\n const type = provider.type ?? PROVIDER_TYPE_ANTHROPIC\n if (type !== PROVIDER_TYPE_ANTHROPIC) {\n consola.warn(\n `Provider ${providerName} is ignored because only anthropic type is supported`,\n )\n return null\n }\n\n const baseUrl = normalizeProviderBaseUrl(provider.baseUrl ?? \"\")\n const apiKey = (provider.apiKey ?? \"\").trim()\n if (!baseUrl || !apiKey) {\n consola.warn(\n `Provider ${providerName} is enabled but missing baseUrl or apiKey`,\n )\n return null\n }\n\n return {\n name: providerName,\n type: PROVIDER_TYPE_ANTHROPIC,\n baseUrl,\n apiKey,\n models: provider.models,\n adjustInputTokens: provider.adjustInputTokens,\n }\n}\n\nexport function listEnabledProviders(): Array<string> {\n const config = getConfig()\n const providerNames = Object.keys(config.providers ?? {})\n return providerNames.filter((name) => getProviderConfig(name) !== null)\n}\n\nexport function isMessagesApiEnabled(): boolean {\n const config = getConfig()\n return config.useMessagesApi ?? true\n}\n\nexport function getAnthropicApiKey(): string | undefined {\n const config = getConfig()\n return config.anthropicApiKey ?? process.env.ANTHROPIC_API_KEY ?? undefined\n}\n\nexport function isResponsesApiWebSearchEnabled(): boolean {\n const config = getConfig()\n return config.useResponsesApiWebSearch ?? true\n}\n\nexport function getClaudeTokenMultiplier(): number {\n const config = getConfig()\n return config.claudeTokenMultiplier ?? 1.15\n}\n","import type { AccountRuntime } from \"~/lib/types/account\"\n\nexport interface AffinityContext {\n requestId?: string\n}\n\ninterface AffinityCacheEntry {\n accountId: string\n expiresAt: number\n}\n\nconst DEFAULT_MAX_ENTRIES = 10_000\nconst DEFAULT_TTL_MS = 60 * 60 * 1000 // 1 hour\n\n/**\n * In-memory LRU cache with TTL for account affinity mappings.\n *\n * Uses Map insertion order for LRU eviction: accessed/updated entries are\n * deleted and re-inserted so they move to the \"newest\" end.\n */\nexport class AccountAffinityCache {\n private readonly cache = new Map<string, AffinityCacheEntry>()\n private readonly maxEntries: number\n private readonly ttlMs: number\n\n constructor(maxEntries = DEFAULT_MAX_ENTRIES, ttlMs = DEFAULT_TTL_MS) {\n this.maxEntries = maxEntries\n this.ttlMs = ttlMs\n }\n\n /** Look up the preferred account ID for a cache key. Returns undefined if not found or expired. */\n get(key: string): string | undefined {\n const entry = this.cache.get(key)\n if (!entry) {\n return undefined\n }\n\n if (Date.now() >= entry.expiresAt) {\n this.cache.delete(key)\n return undefined\n }\n\n return entry.accountId\n }\n\n /** Record a successful account mapping. Refreshes TTL and moves the entry to the newest position. */\n set(key: string, accountId: string): void {\n // Delete first so re-insertion moves it to the newest position (LRU).\n this.cache.delete(key)\n\n // Evict oldest entries if at capacity.\n while (this.cache.size >= this.maxEntries) {\n const oldest = this.cache.keys().next()\n if (oldest.done) break\n this.cache.delete(oldest.value)\n }\n\n this.cache.set(key, {\n accountId,\n expiresAt: Date.now() + this.ttlMs,\n })\n }\n\n /** Remove a specific entry. */\n delete(key: string): boolean {\n return this.cache.delete(key)\n }\n\n /** Remove all entries. */\n clear(): void {\n this.cache.clear()\n }\n\n /** Current number of entries (including potentially expired ones). */\n get size(): number {\n return this.cache.size\n }\n}\n\n/**\n * Extract the affinity key from the request context.\n * Uses the upstream request ID which is deterministic for the same user message.\n */\nexport function extractAffinityKey(\n context: AffinityContext,\n): string | undefined {\n return context.requestId?.trim() || undefined\n}\n\n/**\n * Build the full cache key by combining the affinity key with the model ID.\n * This prevents cross-model pollution (same session requesting different models\n * can be routed to different accounts).\n */\nexport function buildAffinityCacheKey(\n affinityKey: string,\n modelId: string,\n): string {\n return `${affinityKey}:${modelId}`\n}\n\n/**\n * Check whether an account is a valid affinity candidate.\n * An account is valid if it is not failed and is present in the provided\n * runtime list.\n */\nexport function isAffinityAccountUsable(\n accountId: string,\n accounts: ReadonlyArray<AccountRuntime>,\n): AccountRuntime | undefined {\n const account = accounts.find((a) => a.id === accountId)\n if (!account) return undefined\n if (account.failed) return undefined\n return account\n}\n","import consola from \"consola\"\n\nimport type {\n AccountContext,\n AccountRuntime,\n AccountType,\n} from \"~/lib/types/account\"\nimport type { QuotaDetail } from \"~/services/github/get-copilot-usage\"\n\nexport type AuthSnapshot = Readonly<{\n githubToken: string\n accountType: AccountType\n}>\n\nexport const takeAuthSnapshot = (account: AccountRuntime): AuthSnapshot => ({\n githubToken: account.githubToken,\n accountType: account.accountType,\n})\n\nexport const isAuthSnapshotCurrent = (\n account: AccountRuntime,\n snapshot: AuthSnapshot,\n): boolean =>\n account.githubToken === snapshot.githubToken\n && account.accountType === snapshot.accountType\n\nexport const isSameAuthSnapshot = (\n a: AuthSnapshot | undefined,\n b: AuthSnapshot,\n): boolean => {\n if (!a) return false\n return a.githubToken === b.githubToken && a.accountType === b.accountType\n}\n\nexport const toAccountContextFromSnapshot = (\n account: AccountRuntime,\n snapshot: AuthSnapshot,\n copilotToken?: string,\n): AccountContext => ({\n githubToken: snapshot.githubToken,\n copilotToken,\n ...(account.copilotApiUrl !== undefined ?\n { copilotApiUrl: account.copilotApiUrl }\n : {}),\n accountType: snapshot.accountType,\n vsCodeVersion: account.vsCodeVersion,\n})\n\nexport const applyCopilotTokenIfCurrent = (\n account: AccountRuntime,\n snapshot: AuthSnapshot,\n copilotToken: string,\n): boolean => {\n if (!isAuthSnapshotCurrent(account, snapshot)) {\n return false\n }\n\n account.copilotToken = copilotToken\n return true\n}\n\nexport const applyModelsIfCurrent = (\n account: AccountRuntime,\n snapshot: AuthSnapshot,\n models: AccountRuntime[\"models\"],\n): boolean => {\n if (!isAuthSnapshotCurrent(account, snapshot)) {\n return false\n }\n\n account.models = models\n return true\n}\n\nexport const applyTokenRefreshSuccessIfCurrent = (\n account: AccountRuntime,\n snapshot: AuthSnapshot,\n token: string,\n): boolean => {\n if (!isAuthSnapshotCurrent(account, snapshot)) {\n return false\n }\n\n account.copilotToken = token\n account.failed = false\n account.failureReason = undefined\n return true\n}\n\nexport const applyTokenRefreshFailureIfCurrent = (\n account: AccountRuntime,\n snapshot: AuthSnapshot,\n error: unknown,\n): boolean => {\n if (!isAuthSnapshotCurrent(account, snapshot)) {\n return false\n }\n\n account.failed = true\n account.failureReason = String(error)\n return true\n}\n\nexport const applyQuotaRefreshSuccessIfCurrent = (\n account: AccountRuntime,\n snapshot: AuthSnapshot,\n result: {\n premium: QuotaDetail\n copilotApiUrl?: string\n },\n): boolean => {\n if (!isAuthSnapshotCurrent(account, snapshot)) {\n return false\n }\n\n const { premium, copilotApiUrl } = result\n\n account.premiumEntitlement = premium.entitlement\n account.premiumRemaining = premium.remaining\n account.unlimited = premium.unlimited\n account.overagePermitted = premium.overage_permitted\n if (copilotApiUrl) {\n account.copilotApiUrl = copilotApiUrl\n }\n account.lastQuotaFetch = Date.now()\n account.failed = false\n account.failureReason = undefined\n return true\n}\n\nexport const setAccountFailedState = (\n account: AccountRuntime,\n reason: string,\n): void => {\n account.failed = true\n account.failureReason = reason\n consola.warn(`Account ${account.id} marked as failed: ${reason}`)\n}\n\nexport const applyUnauthorizedIfCurrent = (\n account: AccountRuntime,\n snapshot: AuthSnapshot,\n reason: string,\n): boolean => {\n if (!isAuthSnapshotCurrent(account, snapshot)) {\n return false\n }\n\n setAccountFailedState(account, reason)\n return true\n}\n","import type { AccountRuntime } from \"~/lib/types/account\"\nimport type { Model } from \"~/services/copilot/get-models\"\n\nexport type QuotaReservation = Readonly<{ id: symbol }>\n\nexport const getCostUnits = (model: Model): number => {\n // Per user decision: missing billing => treat as free (costUnits = 0)\n const billing = model.billing\n if (!billing) {\n return 0\n }\n\n if (billing.is_premium !== true) {\n return 0\n }\n\n const multiplier = billing.multiplier\n if (\n typeof multiplier !== \"number\"\n || !Number.isFinite(multiplier)\n || multiplier <= 0\n ) {\n return 1\n }\n\n return multiplier\n}\n\nexport const getEffectivePremiumRemaining = (\n account: AccountRuntime,\n): number | undefined => {\n if (account.premiumRemaining === undefined) {\n return undefined\n }\n\n const reserved = account.premiumReserved ?? 0\n return account.premiumRemaining - reserved\n}\n\nexport const reservePremiumUnits = (\n account: AccountRuntime,\n units: number,\n): QuotaReservation | undefined => {\n if (units <= 0) {\n return undefined\n }\n\n const id = Symbol(\"quotaReservation\")\n\n if (!account.premiumReservations) {\n account.premiumReservations = new Map()\n }\n\n account.premiumReservations.set(id, units)\n account.premiumReserved = (account.premiumReserved ?? 0) + units\n\n return { id }\n}\n\nexport const releasePremiumReservation = (\n account: AccountRuntime,\n reservation?: QuotaReservation,\n): void => {\n if (!reservation) {\n return\n }\n\n const reservations = account.premiumReservations\n if (!reservations) {\n return\n }\n\n const reservedUnits = reservations.get(reservation.id)\n if (reservedUnits === undefined) {\n return\n }\n\n reservations.delete(reservation.id)\n\n const nextReserved = (account.premiumReserved ?? 0) - reservedUnits\n account.premiumReserved = Math.max(0, nextReserved)\n\n if (reservations.size === 0) {\n account.premiumReservations = undefined\n }\n}\n","/* eslint-disable max-lines */\nimport consola from \"consola\"\nimport fs from \"node:fs\"\n\nimport type {\n AccountContext,\n AccountRuntime,\n AccountType,\n} from \"~/lib/types/account\"\n\nimport {\n AccountAffinityCache,\n buildAffinityCacheKey,\n extractAffinityKey,\n isAffinityAccountUsable,\n type AffinityContext,\n} from \"~/lib/account-affinity\"\nimport { resolveModelAlias } from \"~/lib/config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { getModels, type Model } from \"~/services/copilot/get-models\"\nimport { getCopilotToken } from \"~/services/github/get-copilot-token\"\nimport { getCopilotUsage } from \"~/services/github/get-copilot-usage\"\nimport { getGitHubUser } from \"~/services/github/get-user\"\n\nimport {\n applyCopilotTokenIfCurrent,\n applyModelsIfCurrent,\n applyQuotaRefreshSuccessIfCurrent,\n applyTokenRefreshFailureIfCurrent,\n applyTokenRefreshSuccessIfCurrent,\n applyUnauthorizedIfCurrent,\n isAuthSnapshotCurrent,\n isSameAuthSnapshot,\n setAccountFailedState,\n takeAuthSnapshot,\n toAccountContextFromSnapshot,\n type AuthSnapshot,\n} from \"./accounts-manager-auth\"\nimport {\n getCostUnits,\n getEffectivePremiumRemaining,\n releasePremiumReservation,\n reservePremiumUnits,\n type QuotaReservation,\n} from \"./accounts-manager-quota\"\nimport {\n hasLegacyToken,\n hasRegistry,\n listAccountsFromRegistry,\n loadAccountToken,\n readLegacyToken,\n saveAccountToken,\n addAccountToRegistry,\n} from \"./accounts-registry\"\nimport { PATHS } from \"./paths\"\n\n/** Quota cache TTL in milliseconds (45 seconds) for pre-request selection. */\nconst QUOTA_CACHE_TTL = 45 * 1000\n\n/** Debounce delay for registry reload in milliseconds */\nconst RELOAD_DEBOUNCE_MS = 500\n\n/** Registry watcher restart initial delay in milliseconds */\nconst WATCHER_RESTART_INITIAL_DELAY_MS = 1000\n/** Registry watcher restart max delay in milliseconds */\nconst WATCHER_RESTART_MAX_DELAY_MS = 60 * 1000\n\nexport interface AccountRequestCandidate {\n modelId: string\n endpoint: string\n}\n\nexport type { QuotaReservation } from \"./accounts-manager-quota\"\nexport type { AffinityContext } from \"~/lib/account-affinity\"\n\nexport type SelectAccountForRequestFailureReason =\n | \"NO_ACCOUNTS\"\n | \"MODEL_NOT_SUPPORTED\"\n | \"NO_QUOTA\"\n\ntype SelectAccountForRequestSuccess = {\n ok: true\n account: AccountRuntime\n selectedModel: Model\n endpoint: string\n costUnits: number\n reservation?: QuotaReservation\n /** Call after a successful upstream response to persist the affinity mapping. */\n confirmAffinity?: () => void\n /** Whether this selection was served from the affinity cache. */\n affinityHit?: boolean\n /** The cache key used for affinity lookup (e.g. `\"session-1:claude-sonnet-4\"`). */\n affinityCacheKey?: string\n}\n\nexport type SelectAccountForRequestResult =\n | SelectAccountForRequestSuccess\n | {\n ok: false\n reason: SelectAccountForRequestFailureReason\n }\n\n/** Manages multiple GitHub Copilot accounts at runtime. */\nexport class AccountsManager {\n private accounts: Map<string, AccountRuntime> = new Map()\n private accountOrder: Array<string> = []\n private temporaryAccount?: AccountRuntime\n private vsCodeVersion?: string\n private accountAffinityEnabled = true\n private affinityCache = new AccountAffinityCache()\n private loadBalanceCursor = 0\n\n private quotaRefreshSnapshotByAccount = new WeakMap<\n AccountRuntime,\n AuthSnapshot\n >()\n private modelsRefreshSnapshotByAccount = new WeakMap<\n AccountRuntime,\n AuthSnapshot\n >()\n private tokenRefreshEnabledAccounts = new WeakSet<AccountRuntime>()\n private modelsRefreshTimer?: ReturnType<typeof setTimeout>\n private modelsRefreshIntervalMs = 0\n\n // Registry file watcher for hot reload\n private registryWatcher?: fs.FSWatcher\n private reloadDebounceTimer?: ReturnType<typeof setTimeout>\n private registryWatcherRestartTimer?: ReturnType<typeof setTimeout>\n private registryWatcherRestartDelayMs = WATCHER_RESTART_INITIAL_DELAY_MS\n private isReloading = false\n\n /** Initialize accounts manager (load registry, migrate legacy token). */\n async initialize(vsCodeVersion?: string): Promise<void> {\n this.vsCodeVersion = vsCodeVersion\n\n // Check if we need to migrate legacy token\n const hasReg = await hasRegistry()\n const hasLegacy = await hasLegacyToken()\n\n if (!hasReg && hasLegacy) {\n await this.migrateLegacyToken()\n }\n\n // Load accounts from registry\n const accountMetas = await listAccountsFromRegistry()\n\n for (const meta of accountMetas) {\n const token = await loadAccountToken(meta.id)\n if (!token) {\n consola.warn(`No token found for account ${meta.id}, skipping`)\n continue\n }\n\n const runtime: AccountRuntime = {\n ...meta,\n githubToken: token,\n vsCodeVersion: this.vsCodeVersion,\n }\n\n this.accounts.set(meta.id, runtime)\n this.accountOrder.push(meta.id)\n }\n\n // Initialize Copilot tokens for all accounts\n for (const account of this.accounts.values()) {\n try {\n await this.initializeAccount(account)\n } catch (error) {\n consola.error(`Failed to initialize account ${account.id}:`, error)\n account.failed = true\n account.failureReason = String(error)\n }\n }\n\n consola.info(`Loaded ${this.accounts.size} account(s)`)\n\n // Start watching the registry file for hot reload\n this.startRegistryWatcher()\n }\n\n setAccountAffinityEnabled(enabled: boolean): void {\n this.accountAffinityEnabled = enabled\n if (!enabled) {\n this.affinityCache.clear()\n }\n }\n\n setModelsRefreshIntervalMs(intervalMs: number): void {\n this.modelsRefreshIntervalMs =\n Number.isFinite(intervalMs) && intervalMs > 0 ? intervalMs : 0\n this.scheduleModelsRefresh()\n }\n\n private computeTokenRefreshDelayMs(refreshInSeconds: number): number {\n return Math.max((refreshInSeconds - 60) * 1000, 1000)\n }\n\n private shouldContinueTokenRefresh(\n account: AccountRuntime,\n snapshot: AuthSnapshot,\n ): boolean {\n return (\n this.tokenRefreshEnabledAccounts.has(account)\n && isAuthSnapshotCurrent(account, snapshot)\n )\n }\n\n private async runTokenRefreshTick(\n account: AccountRuntime,\n snapshot: AuthSnapshot,\n refreshInSeconds: number,\n ): Promise<void> {\n if (!this.shouldContinueTokenRefresh(account, snapshot)) {\n return\n }\n\n try {\n const ctx = toAccountContextFromSnapshot(account, snapshot)\n const { token, refresh_in } = await getCopilotToken(ctx)\n\n if (!this.shouldContinueTokenRefresh(account, snapshot)) {\n return\n }\n\n const applied = applyTokenRefreshSuccessIfCurrent(\n account,\n snapshot,\n token,\n )\n if (!applied) {\n return\n }\n\n consola.debug(`Refreshed token for account ${account.id}`)\n\n // Schedule next refresh using the new refresh interval.\n if (!this.shouldContinueTokenRefresh(account, snapshot)) {\n return\n }\n this.startTokenRefresh(account, refresh_in)\n } catch (error) {\n consola.error(`Failed to refresh token for ${account.id}:`, error)\n\n if (!this.shouldContinueTokenRefresh(account, snapshot)) {\n return\n }\n\n applyTokenRefreshFailureIfCurrent(account, snapshot, error)\n\n // Retry using the previous refresh interval (best effort).\n if (!this.shouldContinueTokenRefresh(account, snapshot)) {\n return\n }\n this.startTokenRefresh(account, refreshInSeconds)\n }\n }\n\n private finalizeQuotaRefreshPromise(\n account: AccountRuntime,\n promise: Promise<void>,\n ): void {\n if (account.quotaRefreshPromise !== promise) {\n return\n }\n\n account.isRefreshingQuota = false\n account.quotaRefreshPromise = undefined\n this.quotaRefreshSnapshotByAccount.delete(account)\n }\n\n /** Initialize a single account. */\n private async initializeAccount(account: AccountRuntime): Promise<void> {\n const snapshot = takeAuthSnapshot(account)\n\n try {\n // Get Copilot token\n const tokenCtx = toAccountContextFromSnapshot(account, snapshot)\n const { token, refresh_in } = await getCopilotToken(tokenCtx)\n\n if (!applyCopilotTokenIfCurrent(account, snapshot, token)) {\n return\n }\n\n // Resolve the account-specific Copilot endpoint before the first models fetch.\n await this.refreshQuota(account)\n\n // Start token refresh timer\n this.startTokenRefresh(account, refresh_in)\n\n // Get models\n const modelsCtx = toAccountContextFromSnapshot(account, snapshot, token)\n const models = await getModels(modelsCtx)\n\n if (!applyModelsIfCurrent(account, snapshot, models)) {\n return\n }\n account.lastModelsFetch = Date.now()\n\n consola.debug(`Account ${account.id} initialized`)\n } catch (error) {\n // Ignore stale results if registry hot reload changed auth.\n if (!isAuthSnapshotCurrent(account, snapshot)) {\n return\n }\n\n throw error\n }\n }\n\n /** Migrate legacy github_token to the new multi-account system. */\n private async migrateLegacyToken(): Promise<void> {\n const token = await readLegacyToken()\n if (!token) return\n\n try {\n // Get user info to determine the account ID\n const user = await getGitHubUser({\n githubToken: token,\n accountType: \"individual\",\n })\n const id = user.login\n\n // Save token to new location\n await saveAccountToken(id, token)\n\n // Add to registry\n await addAccountToRegistry({\n id,\n accountType: \"individual\",\n addedAt: Date.now(),\n })\n\n consola.info(`Migrated legacy token to account: ${id}`)\n } catch (error) {\n consola.error(\"Failed to migrate legacy token:\", error)\n }\n }\n\n /** Start token refresh timer for an account. */\n private startTokenRefresh(\n account: AccountRuntime,\n refreshInSeconds: number,\n ): void {\n // Stop existing timer if any\n this.stopTokenRefresh(account)\n\n this.tokenRefreshEnabledAccounts.add(account)\n\n const snapshot = takeAuthSnapshot(account)\n const delayMs = this.computeTokenRefreshDelayMs(refreshInSeconds)\n\n account.refreshTimer = setTimeout(() => {\n void this.runTokenRefreshTick(account, snapshot, refreshInSeconds)\n }, delayMs)\n }\n\n /** Stop token refresh timer for an account. */\n private stopTokenRefresh(account: AccountRuntime): void {\n this.tokenRefreshEnabledAccounts.delete(account)\n\n if (account.refreshTimer) {\n clearTimeout(account.refreshTimer)\n account.refreshTimer = undefined\n }\n }\n\n /** Stop all token refresh timers. */\n private stopAllTokenRefresh(): void {\n for (const account of this.accounts.values()) {\n this.stopTokenRefresh(account)\n }\n if (this.temporaryAccount) {\n this.stopTokenRefresh(this.temporaryAccount)\n }\n }\n\n private scheduleModelsRefresh(): void {\n this.stopModelsRefresh()\n\n if (!this.modelsRefreshIntervalMs || this.modelsRefreshIntervalMs <= 0) {\n return\n }\n\n this.modelsRefreshTimer = setTimeout(() => {\n void this.runModelsRefreshTick()\n }, this.modelsRefreshIntervalMs)\n }\n\n private stopModelsRefresh(): void {\n if (this.modelsRefreshTimer) {\n clearTimeout(this.modelsRefreshTimer)\n this.modelsRefreshTimer = undefined\n }\n }\n\n private async runModelsRefreshTick(): Promise<void> {\n try {\n await this.refreshAllModels()\n } catch (error) {\n consola.error(\"Failed to refresh models:\", error)\n } finally {\n this.scheduleModelsRefresh()\n }\n }\n\n private finalizeModelsRefreshPromise(\n account: AccountRuntime,\n promise: Promise<void>,\n ): void {\n if (account.modelsRefreshPromise !== promise) {\n return\n }\n\n account.isRefreshingModels = false\n account.modelsRefreshPromise = undefined\n this.modelsRefreshSnapshotByAccount.delete(account)\n }\n\n private async refreshModels(account: AccountRuntime): Promise<void> {\n if (!account.copilotToken) {\n consola.debug(\n `Skip model refresh for ${account.id}: missing Copilot token`,\n )\n return\n }\n\n const snapshot = takeAuthSnapshot(account)\n\n if (account.modelsRefreshPromise) {\n const existingSnapshot = this.modelsRefreshSnapshotByAccount.get(account)\n if (isSameAuthSnapshot(existingSnapshot, snapshot)) {\n await account.modelsRefreshPromise\n return\n }\n }\n\n account.isRefreshingModels = true\n\n const ctx = toAccountContextFromSnapshot(\n account,\n snapshot,\n account.copilotToken,\n )\n\n const promise = (async () => {\n try {\n const models = await getModels(ctx)\n const applied = applyModelsIfCurrent(account, snapshot, models)\n if (applied) {\n account.lastModelsFetch = Date.now()\n }\n } catch (error) {\n if (error instanceof HTTPError && error.response.status === 401) {\n applyUnauthorizedIfCurrent(account, snapshot, \"Unauthorized (401)\")\n return\n }\n\n consola.error(`Failed to refresh models for ${account.id}:`, error)\n }\n })()\n\n account.modelsRefreshPromise = promise\n this.modelsRefreshSnapshotByAccount.set(account, snapshot)\n\n void promise.finally(() => {\n this.finalizeModelsRefreshPromise(account, promise)\n })\n\n await promise\n }\n\n private async refreshAllModels(): Promise<void> {\n const accounts: Array<AccountRuntime> = []\n\n if (this.temporaryAccount) {\n accounts.push(this.temporaryAccount)\n }\n\n for (const id of this.accountOrder) {\n const account = this.accounts.get(id)\n if (account) {\n accounts.push(account)\n }\n }\n\n if (accounts.length === 0) {\n return\n }\n\n await Promise.allSettled(\n accounts.map((account) => this.refreshModels(account)),\n )\n }\n\n /** Refresh quota information for an account. */\n async refreshQuota(account: AccountRuntime): Promise<void> {\n const snapshot = takeAuthSnapshot(account)\n\n if (account.quotaRefreshPromise) {\n const existingSnapshot = this.quotaRefreshSnapshotByAccount.get(account)\n if (isSameAuthSnapshot(existingSnapshot, snapshot)) {\n await account.quotaRefreshPromise\n return\n }\n }\n\n account.isRefreshingQuota = true\n\n const ctx = toAccountContextFromSnapshot(account, snapshot)\n const promise = (async () => {\n try {\n const usage = await getCopilotUsage(ctx)\n const premium = usage.quota_snapshots.premium_interactions\n applyQuotaRefreshSuccessIfCurrent(account, snapshot, {\n premium,\n copilotApiUrl: usage.endpoints.api,\n })\n } catch (error) {\n if (error instanceof HTTPError && error.response.status === 401) {\n applyUnauthorizedIfCurrent(account, snapshot, \"Unauthorized (401)\")\n return\n }\n\n consola.error(`Failed to refresh quota for ${account.id}:`, error)\n // Don't mark as failed for non-401 quota refresh errors\n }\n })()\n\n account.quotaRefreshPromise = promise\n this.quotaRefreshSnapshotByAccount.set(account, snapshot)\n\n void promise.finally(() => {\n this.finalizeQuotaRefreshPromise(account, promise)\n })\n\n await promise\n }\n\n /** Check if quota cache is expired. */\n private isQuotaCacheExpired(account: AccountRuntime): boolean {\n if (!account.lastQuotaFetch) return true\n return Date.now() - account.lastQuotaFetch > QUOTA_CACHE_TTL\n }\n\n private isAccountFailed(account: AccountRuntime): boolean {\n return account.failed === true\n }\n\n private useOverageFallback(fallback: {\n account: AccountRuntime\n model: Model\n endpoint: string\n costUnits: number\n }): SelectAccountForRequestSuccess {\n const reservation = reservePremiumUnits(\n fallback.account,\n fallback.costUnits,\n )\n return {\n ok: true,\n account: fallback.account,\n selectedModel: fallback.model,\n endpoint: fallback.endpoint,\n costUnits: fallback.costUnits,\n reservation,\n }\n }\n\n private isModelSupportedForEndpoint(model: Model, endpoint: string): boolean {\n if (endpoint === \"/responses\") {\n return model.supported_endpoints?.includes(endpoint) ?? false\n }\n\n const supported = model.supported_endpoints\n if (!supported) {\n return true\n }\n\n return supported.includes(endpoint)\n }\n\n private pickSupportedCandidate(\n account: AccountRuntime,\n candidates: Array<AccountRequestCandidate>,\n ): { candidate: AccountRequestCandidate; model: Model } | null {\n const models = account.models?.data\n if (!models) {\n return null\n }\n\n for (const candidate of candidates) {\n const model = models.find((m) => m.id === candidate.modelId)\n if (!model) {\n continue\n }\n\n if (!this.isModelSupportedForEndpoint(model, candidate.endpoint)) {\n continue\n }\n\n return { candidate, model }\n }\n\n return null\n }\n\n private async selectAccountForCandidates(\n orderedAccounts: Array<AccountRuntime>,\n candidates: Array<AccountRequestCandidate>,\n ): Promise<SelectAccountForRequestResult> {\n if (orderedAccounts.length === 0) {\n return { ok: false, reason: \"NO_ACCOUNTS\" }\n }\n\n let supportedCandidateFound = false\n let overageFallback:\n | {\n account: AccountRuntime\n model: Model\n endpoint: string\n costUnits: number\n }\n | undefined\n\n for (const account of orderedAccounts) {\n if (this.isAccountFailed(account)) {\n continue\n }\n\n const supported = this.pickSupportedCandidate(account, candidates)\n if (!supported) {\n continue\n }\n\n supportedCandidateFound = true\n\n const { candidate, model } = supported\n const costUnits = getCostUnits(model)\n\n if (costUnits <= 0) {\n // Free model: sequential routing (first available account).\n // When account affinity is enabled, the caller (selectAccountForRequest)\n // handles cache lookup/write-back; this path only runs on cache miss.\n return {\n ok: true,\n account,\n selectedModel: model,\n endpoint: candidate.endpoint,\n costUnits,\n }\n }\n\n if (!account.unlimited && this.isQuotaCacheExpired(account)) {\n await this.refreshQuota(account)\n }\n\n if (this.isAccountFailed(account)) {\n continue\n }\n\n if (account.unlimited) {\n return {\n ok: true,\n account,\n selectedModel: model,\n endpoint: candidate.endpoint,\n costUnits,\n }\n }\n\n // Check if account has sufficient quota.\n const effectiveRemaining = getEffectivePremiumRemaining(account)\n if (effectiveRemaining !== undefined && effectiveRemaining < costUnits) {\n // Insufficient quota - store as overage fallback if permitted, but keep\n // looking for accounts with quota to avoid unnecessary overage charges.\n if (account.overagePermitted && !overageFallback) {\n overageFallback = {\n account,\n model,\n endpoint: candidate.endpoint,\n costUnits,\n }\n }\n continue\n }\n\n const reservation = reservePremiumUnits(account, costUnits)\n\n return {\n ok: true,\n account,\n selectedModel: model,\n endpoint: candidate.endpoint,\n costUnits,\n reservation,\n }\n }\n\n if (!supportedCandidateFound) {\n return { ok: false, reason: \"MODEL_NOT_SUPPORTED\" }\n }\n\n // No account with quota found - use overage fallback if available.\n return overageFallback ?\n this.useOverageFallback(overageFallback)\n : { ok: false, reason: \"NO_QUOTA\" }\n }\n\n /**\n * Try to use a preferred (affinity) account for the request.\n * Returns a successful selection if the account is usable; null otherwise.\n */\n\n private async tryAffinityAccount(\n preferredAccountId: string,\n orderedAccounts: Array<AccountRuntime>,\n candidates: Array<AccountRequestCandidate>,\n ): Promise<SelectAccountForRequestSuccess | null> {\n const account = isAffinityAccountUsable(preferredAccountId, orderedAccounts)\n if (!account) {\n return null\n }\n\n // Try original candidates first, then alias-resolved candidates.\n const supported =\n this.pickSupportedCandidate(account, candidates)\n ?? this.pickAliasFallbackCandidate(account, candidates)\n if (!supported) {\n return null\n }\n\n return this.validateAffinityQuota(account, supported)\n }\n\n /**\n * Resolve model aliases and try to pick a supported candidate.\n * Returns null if no alias differs or the account doesn't support the alias.\n */\n private pickAliasFallbackCandidate(\n account: AccountRuntime,\n candidates: Array<AccountRequestCandidate>,\n ): { candidate: AccountRequestCandidate; model: Model } | null {\n const aliasCandidates = candidates.map((candidate) => {\n const modelId = resolveModelAlias(candidate.modelId)\n if (modelId === candidate.modelId) return candidate\n return { ...candidate, modelId }\n })\n const aliasChanged = aliasCandidates.some(\n (candidate, index) => candidate.modelId !== candidates[index].modelId,\n )\n if (!aliasChanged) return null\n\n return this.pickSupportedCandidate(account, aliasCandidates)\n }\n\n /**\n * Validate quota for an affinity candidate. Free models pass immediately;\n * premium models go through quota refresh / reservation.\n */\n private async validateAffinityQuota(\n account: AccountRuntime,\n supported: { candidate: AccountRequestCandidate; model: Model },\n ): Promise<SelectAccountForRequestSuccess | null> {\n const { candidate, model } = supported\n const costUnits = getCostUnits(model)\n\n // Free model — no quota checks needed.\n if (costUnits <= 0) {\n return {\n ok: true,\n account,\n selectedModel: model,\n endpoint: candidate.endpoint,\n costUnits,\n }\n }\n\n // Premium model — validate quota.\n if (!account.unlimited && this.isQuotaCacheExpired(account)) {\n await this.refreshQuota(account)\n }\n\n if (this.isAccountFailed(account)) {\n return null\n }\n\n if (account.unlimited) {\n return {\n ok: true,\n account,\n selectedModel: model,\n endpoint: candidate.endpoint,\n costUnits,\n }\n }\n\n const effectiveRemaining = getEffectivePremiumRemaining(account)\n if (effectiveRemaining !== undefined && effectiveRemaining < costUnits) {\n return null\n }\n\n const reservation = reservePremiumUnits(account, costUnits)\n\n return {\n ok: true,\n account,\n selectedModel: model,\n endpoint: candidate.endpoint,\n costUnits,\n reservation,\n }\n }\n\n /**\n * Select an available account for a specific request (model + endpoint).\n * When account affinity is enabled, routes to the previously successful account\n * for the same affinity key + model combination.\n * Uses reservation to avoid oversubscribing premium quota under concurrency.\n */\n async selectAccountForRequest(\n candidates: Array<AccountRequestCandidate>,\n affinityContext?: AffinityContext,\n ): Promise<SelectAccountForRequestResult> {\n if (candidates.length === 0) {\n throw new Error(\"selectAccountForRequest requires at least one candidate\")\n }\n\n const orderedAccounts = [\n ...(this.temporaryAccount ? [this.temporaryAccount] : []),\n ...this.accountOrder\n .map((id) => this.accounts.get(id))\n .filter((account): account is AccountRuntime => account !== undefined),\n ]\n\n // Resolve the affinity key once — reused for both lookup and write-back.\n const affinityKey =\n this.accountAffinityEnabled && affinityContext ?\n extractAffinityKey(affinityContext)\n : undefined\n\n const modelKey = candidates[0].modelId\n const cacheKey =\n affinityKey ? buildAffinityCacheKey(affinityKey, modelKey) : undefined\n\n // Step 1: Try the preferred (affinity) account.\n if (cacheKey) {\n const preferredId = this.affinityCache.get(cacheKey)\n if (preferredId) {\n const affinityResult = await this.tryAffinityAccount(\n preferredId,\n orderedAccounts,\n candidates,\n )\n if (affinityResult) {\n affinityResult.affinityHit = true\n affinityResult.affinityCacheKey = cacheKey\n affinityResult.confirmAffinity = () => {\n if (!this.accountAffinityEnabled) return\n this.affinityCache.set(cacheKey, affinityResult.account.id)\n }\n return affinityResult\n }\n }\n }\n\n // Step 2: Cache miss — rotate accounts for load balancing when affinity is enabled.\n const accountsForSelection =\n this.accountAffinityEnabled && orderedAccounts.length > 1 ?\n this.rotateAccounts(orderedAccounts)\n : orderedAccounts\n\n const result = await this.selectWithAliasFallback(\n accountsForSelection,\n candidates,\n )\n\n if (result.ok) {\n this.loadBalanceCursor++\n }\n\n // Attach confirmAffinity callback so the handler can persist the mapping on success.\n if (result.ok && cacheKey) {\n const successResult = result\n successResult.confirmAffinity = () => {\n if (!this.accountAffinityEnabled) return\n this.affinityCache.set(cacheKey, successResult.account.id)\n }\n }\n\n return result\n }\n\n /**\n * Rotate the accounts array by the current load-balance cursor for round-robin distribution.\n * This ensures cache-miss requests are spread across accounts instead of always hitting the first.\n */\n private rotateAccounts(\n accounts: Array<AccountRuntime>,\n ): Array<AccountRuntime> {\n const start = this.loadBalanceCursor % accounts.length\n if (start === 0) return accounts\n return [...accounts.slice(start), ...accounts.slice(0, start)]\n }\n\n /**\n * Normal account selection with alias fallback.\n * Extracted to keep selectAccountForRequest readable after adding affinity logic.\n */\n private async selectWithAliasFallback(\n orderedAccounts: Array<AccountRuntime>,\n candidates: Array<AccountRequestCandidate>,\n ): Promise<SelectAccountForRequestResult> {\n const primary = await this.selectAccountForCandidates(\n orderedAccounts,\n candidates,\n )\n if (primary.ok || primary.reason !== \"MODEL_NOT_SUPPORTED\") {\n return primary\n }\n\n const aliasCandidates = candidates.map((candidate) => {\n const modelId = resolveModelAlias(candidate.modelId)\n if (modelId === candidate.modelId) return candidate\n return { ...candidate, modelId }\n })\n const aliasChanged = aliasCandidates.some(\n (candidate, index) => candidate.modelId !== candidates[index].modelId,\n )\n if (!aliasChanged) {\n return primary\n }\n\n return this.selectAccountForCandidates(orderedAccounts, aliasCandidates)\n }\n\n /**\n * Finalize quota after a request completes.\n * This releases any in-flight reservation and refreshes the actual quota from the API.\n */\n async finalizeQuota(\n account: AccountRuntime,\n reservation?: QuotaReservation,\n ): Promise<void> {\n releasePremiumReservation(account, reservation)\n\n try {\n await this.refreshQuota(account)\n } catch (error) {\n consola.debug(`Failed to finalize quota for ${account.id}:`, error)\n }\n }\n\n /**\n * Mark an account as failed.\n */\n markAccountFailed(id: string, reason: string): void {\n const account = this.accounts.get(id)\n if (account) {\n setAccountFailedState(account, reason)\n return\n }\n\n if (this.temporaryAccount && this.temporaryAccount.id === id) {\n setAccountFailedState(this.temporaryAccount, reason)\n }\n }\n\n /**\n * Get status of all accounts.\n */\n getAccountStatus(): Array<{\n id: string\n entitlement?: number\n remaining?: number\n unlimited?: boolean\n overagePermitted?: boolean\n failed?: boolean\n failureReason?: string\n }> {\n const statuses: Array<{\n id: string\n entitlement?: number\n remaining?: number\n unlimited?: boolean\n overagePermitted?: boolean\n failed?: boolean\n failureReason?: string\n }> = []\n\n if (this.temporaryAccount) {\n statuses.push({\n id: \"(temporary)\",\n entitlement: this.temporaryAccount.premiumEntitlement,\n remaining: this.temporaryAccount.premiumRemaining,\n unlimited: this.temporaryAccount.unlimited,\n overagePermitted: this.temporaryAccount.overagePermitted,\n failed: this.temporaryAccount.failed,\n failureReason: this.temporaryAccount.failureReason,\n })\n }\n\n for (const id of this.accountOrder) {\n const account = this.accounts.get(id)\n if (account) {\n statuses.push({\n id: account.id,\n entitlement: account.premiumEntitlement,\n remaining: account.premiumRemaining,\n unlimited: account.unlimited,\n overagePermitted: account.overagePermitted,\n failed: account.failed,\n failureReason: account.failureReason,\n })\n }\n }\n\n return statuses\n }\n\n /**\n * Set a temporary account from a GitHub token (--github-token).\n * This account takes priority over registered accounts.\n */\n async setTemporaryAccount(\n githubToken: string,\n accountType: AccountType,\n ): Promise<void> {\n const runtime: AccountRuntime = {\n id: \"(temporary)\",\n accountType,\n addedAt: Date.now(),\n githubToken,\n vsCodeVersion: this.vsCodeVersion,\n }\n\n try {\n await this.initializeAccount(runtime)\n this.temporaryAccount = runtime\n consola.info(\"Temporary account initialized\")\n } catch (error) {\n consola.error(\"Failed to initialize temporary account:\", error)\n throw error\n }\n }\n\n /**\n * Check if any accounts are available.\n */\n hasAccounts(): boolean {\n return this.accounts.size > 0 || this.temporaryAccount !== undefined\n }\n\n /**\n * Get the first available account's models.\n * Used for caching models in legacy compatibility mode.\n */\n getFirstAccountModels(): AccountRuntime[\"models\"] {\n if (this.temporaryAccount?.models) {\n return this.temporaryAccount.models\n }\n\n for (const id of this.accountOrder) {\n const account = this.accounts.get(id)\n if (account?.models) {\n return account.models\n }\n }\n\n return undefined\n }\n\n /**\n * Get account context by index.\n * Index 0 is the temporary account (if exists), otherwise the first registered account.\n * Returns null if index is out of bounds.\n */\n getAccountContextByIndex(index: number): AccountContext | null {\n // Build the same order as getAccountStatus()\n const allAccounts: Array<AccountRuntime> = []\n\n if (this.temporaryAccount) {\n allAccounts.push(this.temporaryAccount)\n }\n\n for (const id of this.accountOrder) {\n const account = this.accounts.get(id)\n if (account) {\n allAccounts.push(account)\n }\n }\n\n if (index < 0 || index >= allAccounts.length) {\n return null\n }\n\n return this.toAccountContext(allAccounts[index])\n }\n\n /**\n * Get the total number of accounts (including temporary).\n */\n getAccountCount(): number {\n return (this.temporaryAccount ? 1 : 0) + this.accountOrder.length\n }\n\n /**\n * Convert AccountRuntime to AccountContext for service calls.\n */\n private toAccountContext(account: AccountRuntime): AccountContext {\n return {\n githubToken: account.githubToken,\n copilotToken: account.copilotToken,\n ...(account.copilotApiUrl !== undefined ?\n { copilotApiUrl: account.copilotApiUrl }\n : {}),\n accountType: account.accountType,\n vsCodeVersion: account.vsCodeVersion,\n }\n }\n\n /**\n * Start watching the registry file for changes.\n * Enables hot reload of accounts when the file is modified.\n */\n private startRegistryWatcher(): void {\n // Stop existing watcher if any\n this.stopRegistryWatcher()\n\n try {\n this.registryWatcher = fs.watch(\n PATHS.ACCOUNTS_REGISTRY_PATH,\n (eventType) => {\n // Only react to 'change' events (file content modified)\n if (eventType === \"change\") {\n this.scheduleReload()\n }\n },\n )\n\n // Successful start: reset restart backoff.\n this.registryWatcherRestartDelayMs = WATCHER_RESTART_INITIAL_DELAY_MS\n if (this.registryWatcherRestartTimer) {\n clearTimeout(this.registryWatcherRestartTimer)\n this.registryWatcherRestartTimer = undefined\n }\n\n // Handle watcher errors (e.g., file deleted)\n this.registryWatcher.on(\"error\", (error) => {\n consola.debug(\"Registry watcher error:\", error)\n\n const delayMs = this.registryWatcherRestartDelayMs\n this.registryWatcherRestartDelayMs = Math.min(\n this.registryWatcherRestartDelayMs * 2,\n WATCHER_RESTART_MAX_DELAY_MS,\n )\n\n // Close broken watcher to avoid repeated error events.\n this.stopRegistryWatcher()\n\n // Try to restart the watcher after a delay (with backoff)\n this.registryWatcherRestartTimer = setTimeout(() => {\n this.registryWatcherRestartTimer = undefined\n this.startRegistryWatcher()\n }, delayMs)\n\n consola.debug(`Restarting registry watcher in ${delayMs}ms`)\n })\n\n consola.debug(\"Started registry file watcher\")\n } catch (error) {\n consola.warn(\"Failed to start registry watcher:\", error)\n }\n }\n\n /**\n * Schedule a registry reload with debouncing.\n */\n private scheduleReload(): void {\n // Clear existing timer\n if (this.reloadDebounceTimer) {\n clearTimeout(this.reloadDebounceTimer)\n }\n\n // Schedule reload after debounce delay\n this.reloadDebounceTimer = setTimeout(() => {\n void this.reloadRegistry()\n }, RELOAD_DEBOUNCE_MS)\n }\n\n /**\n * Reload the registry and perform incremental updates.\n * Adds new accounts, removes deleted ones, and reinitializes existing accounts\n * when token/accountType changes.\n */\n private async reloadRegistry(): Promise<void> {\n // Prevent concurrent reloads\n if (this.isReloading) {\n return\n }\n this.isReloading = true\n\n try {\n const newMetas = await listAccountsFromRegistry()\n const newIds = new Set(newMetas.map((m) => m.id))\n const currentIds = new Set(this.accountOrder)\n\n // Track changes for logging\n const added: Array<string> = []\n const removed: Array<string> = []\n const updated: Array<string> = []\n\n this.removeDeletedAccounts(currentIds, newIds, removed)\n\n // Add new accounts (newIds - currentIds)\n for (const meta of newMetas) {\n if (!currentIds.has(meta.id)) {\n await this.addNewAccount(meta, added)\n }\n }\n\n // Update existing accounts when meta/token changed\n await this.reinitializeUpdatedAccounts(newMetas, currentIds, updated)\n\n // Update accountOrder to reflect new order\n this.accountOrder = newMetas\n .map((m) => m.id)\n .filter((id) => this.accounts.has(id))\n\n // Reset load-balance cursor on account list/order changes.\n this.loadBalanceCursor = 0\n\n this.logRegistryReloadChanges(added, removed, updated)\n } catch (error) {\n consola.error(\"Failed to reload registry:\", error)\n this.shutdown()\n process.exit(1)\n } finally {\n this.isReloading = false\n }\n }\n\n private removeDeletedAccounts(\n currentIds: Set<string>,\n newIds: Set<string>,\n removed: Array<string>,\n ): void {\n for (const id of currentIds) {\n if (!newIds.has(id)) {\n const account = this.accounts.get(id)\n if (!account) {\n continue\n }\n\n this.stopTokenRefresh(account)\n this.accounts.delete(id)\n removed.push(id)\n }\n }\n }\n\n private async reinitializeUpdatedAccounts(\n newMetas: Array<{ id: string; accountType: AccountType; addedAt: number }>,\n currentIds: Set<string>,\n updated: Array<string>,\n ): Promise<void> {\n for (const meta of newMetas) {\n if (!currentIds.has(meta.id)) {\n continue\n }\n\n const account = this.accounts.get(meta.id)\n if (!account) {\n continue\n }\n\n const token = await loadAccountToken(meta.id)\n if (!token) {\n consola.warn(`No token found for account ${meta.id}, skipping update`)\n continue\n }\n\n const accountTypeChanged = account.accountType !== meta.accountType\n const tokenChanged = account.githubToken !== token\n const addedAtChanged = account.addedAt !== meta.addedAt\n\n // Keep runtime metadata in sync with the registry.\n if (accountTypeChanged) {\n account.accountType = meta.accountType\n }\n if (addedAtChanged) {\n account.addedAt = meta.addedAt\n }\n if (tokenChanged) {\n account.githubToken = token\n }\n\n if (!accountTypeChanged && !tokenChanged) {\n continue\n }\n\n try {\n await this.initializeAccount(account)\n updated.push(meta.id)\n } catch (error) {\n consola.error(\n `Failed to reinitialize account ${meta.id} after update:`,\n error,\n )\n account.failed = true\n account.failureReason = String(error)\n updated.push(`${meta.id} (failed)`)\n }\n }\n }\n\n private logRegistryReloadChanges(\n added: Array<string>,\n removed: Array<string>,\n updated: Array<string>,\n ): void {\n if (added.length === 0 && removed.length === 0 && updated.length === 0) {\n return\n }\n\n const changes: Array<string> = []\n if (added.length > 0) {\n changes.push(`added: ${added.join(\", \")}`)\n }\n if (removed.length > 0) {\n changes.push(`removed: ${removed.join(\", \")}`)\n }\n if (updated.length > 0) {\n changes.push(`updated: ${updated.join(\", \")}`)\n }\n\n consola.info(\n `Registry reloaded (${changes.join(\"; \")}). Total: ${this.accounts.size} account(s)`,\n )\n }\n\n /**\n * Helper to add a new account during reload.\n */\n private async addNewAccount(\n meta: { id: string; accountType: AccountType; addedAt: number },\n added: Array<string>,\n ): Promise<void> {\n const token = await loadAccountToken(meta.id)\n if (!token) {\n consola.warn(`No token found for new account ${meta.id}, skipping`)\n return\n }\n\n const runtime: AccountRuntime = {\n ...meta,\n githubToken: token,\n vsCodeVersion: this.vsCodeVersion,\n }\n\n try {\n await this.initializeAccount(runtime)\n this.accounts.set(meta.id, runtime)\n added.push(meta.id)\n } catch (error) {\n consola.error(`Failed to initialize new account ${meta.id}:`, error)\n runtime.failed = true\n runtime.failureReason = String(error)\n this.accounts.set(meta.id, runtime)\n added.push(`${meta.id} (failed)`)\n }\n }\n\n /**\n * Stop the registry file watcher.\n */\n private stopRegistryWatcher(): void {\n if (this.reloadDebounceTimer) {\n clearTimeout(this.reloadDebounceTimer)\n this.reloadDebounceTimer = undefined\n }\n if (this.registryWatcherRestartTimer) {\n clearTimeout(this.registryWatcherRestartTimer)\n this.registryWatcherRestartTimer = undefined\n }\n if (this.registryWatcher) {\n this.registryWatcher.close()\n this.registryWatcher = undefined\n }\n }\n\n /**\n * Shutdown the manager and clean up resources.\n */\n shutdown(): void {\n this.stopRegistryWatcher()\n this.stopAllTokenRefresh()\n this.stopModelsRefresh()\n this.affinityCache.clear()\n this.loadBalanceCursor = 0\n this.accounts.clear()\n this.accountOrder = []\n this.temporaryAccount = undefined\n }\n}\n\n/** Singleton instance of AccountsManager */\nexport const accountsManager = new AccountsManager()\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as accountTokenPath, t as PATHS } from "./paths-Cvzy-eLX.js";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
@@ -176,5 +176,5 @@ async function hasRegistry() {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
//#endregion
|
|
179
|
-
export {
|
|
180
|
-
//# sourceMappingURL=accounts-registry-
|
|
179
|
+
export { loadAccountToken as a, removeAccountToken as c, listAccountsFromRegistry as i, saveAccountToken as l, hasLegacyToken as n, readLegacyToken as o, hasRegistry as r, removeAccountFromRegistry as s, addAccountToRegistry as t, saveRegistry as u };
|
|
180
|
+
//# sourceMappingURL=accounts-registry-CQYvRe65.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accounts-registry-c7rs5Ed9.js","names":["parsed: unknown","index: number"],"sources":["../src/lib/accounts-registry.ts"],"sourcesContent":["import fs from \"node:fs/promises\"\nimport { z } from \"zod\"\n\nimport type { AccountMeta, AccountRegistry } from \"~/lib/types/account\"\n\nimport { accountTokenPath, PATHS } from \"./paths\"\n\n/**\n * Validate account ID (GitHub login).\n * Rules:\n * - Only alphanumeric characters or single hyphens\n * - 1-39 chars\n * - Cannot begin or end with a hyphen\n * - No consecutive hyphens\n */\nexport function validateAccountId(id: string): boolean {\n if (id.length === 0 || id.length > 39) return false\n if (!/^[a-z0-9-]+$/i.test(id)) return false\n if (id.startsWith(\"-\") || id.endsWith(\"-\")) return false\n if (id.includes(\"--\")) return false\n return true\n}\n\nconst accountMetaSchema = z.object({\n id: z.string().refine(validateAccountId, {\n message:\n \"Invalid account id. Expected a GitHub login (1-39 chars, alphanumeric or single hyphens, no leading/trailing hyphen, no consecutive hyphens).\",\n }),\n accountType: z.enum([\"individual\", \"business\", \"enterprise\"]),\n addedAt: z.number(),\n})\n\nconst accountRegistrySchema = z.object({\n version: z.literal(1),\n accounts: z.array(accountMetaSchema),\n})\n\n/**\n * Create an empty registry with the current schema version.\n */\nfunction createEmptyRegistry(): AccountRegistry {\n return {\n version: 1,\n accounts: [],\n }\n}\n\n/**\n * Load the accounts registry from disk.\n * Returns an empty registry if the file doesn't exist.\n */\nexport async function loadRegistry(): Promise<AccountRegistry> {\n try {\n const content = await fs.readFile(PATHS.ACCOUNTS_REGISTRY_PATH, \"utf8\")\n if (!content.trim()) {\n return createEmptyRegistry()\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(content) as unknown\n } catch (error) {\n throw new Error(\n `Invalid accounts registry JSON at ${PATHS.ACCOUNTS_REGISTRY_PATH}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n )\n }\n\n const result = accountRegistrySchema.safeParse(parsed)\n if (!result.success) {\n const issues = result.error.issues\n .map((issue) => `${issue.path.join(\".\")}: ${issue.message}`)\n .join(\"; \")\n\n throw new Error(\n `Invalid accounts registry at ${PATHS.ACCOUNTS_REGISTRY_PATH}: ${issues}`,\n )\n }\n\n const registry = result.data\n\n const seen = new Set<string>()\n for (const account of registry.accounts) {\n if (seen.has(account.id)) {\n throw new Error(\n `Invalid accounts registry at ${PATHS.ACCOUNTS_REGISTRY_PATH}: duplicate account id \"${account.id}\"`,\n )\n }\n seen.add(account.id)\n }\n\n return registry\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return createEmptyRegistry()\n }\n throw error\n }\n}\n\n/**\n * Save the accounts registry to disk with secure permissions.\n */\nexport async function saveRegistry(registry: AccountRegistry): Promise<void> {\n const content = JSON.stringify(registry, null, 2)\n await fs.writeFile(PATHS.ACCOUNTS_REGISTRY_PATH, content, { mode: 0o600 })\n}\n\n/**\n * Add an account to the registry.\n * The account is appended to the end of the list (lowest priority).\n */\nexport async function addAccountToRegistry(meta: AccountMeta): Promise<void> {\n if (!validateAccountId(meta.id)) {\n throw new Error(`Invalid account ID: ${meta.id}`)\n }\n\n const registry = await loadRegistry()\n\n // Check for duplicate\n if (registry.accounts.some((a) => a.id === meta.id)) {\n throw new Error(`Account already exists: ${meta.id}`)\n }\n\n registry.accounts.push(meta)\n await saveRegistry(registry)\n}\n\n/**\n * Remove an account from the registry by ID or index (1-based).\n * Returns the removed account metadata.\n */\nexport async function removeAccountFromRegistry(\n idOrIndex: string | number,\n): Promise<AccountMeta> {\n const registry = await loadRegistry()\n let index: number\n\n if (typeof idOrIndex === \"number\") {\n // 1-based index\n index = idOrIndex - 1\n if (index < 0 || index >= registry.accounts.length) {\n throw new Error(`Invalid account index: ${idOrIndex}`)\n }\n } else {\n index = registry.accounts.findIndex((a) => a.id === idOrIndex)\n if (index === -1) {\n throw new Error(`Account not found: ${idOrIndex}`)\n }\n }\n\n const [removed] = registry.accounts.splice(index, 1)\n await saveRegistry(registry)\n return removed\n}\n\n/**\n * List all accounts from the registry.\n */\nexport async function listAccountsFromRegistry(): Promise<Array<AccountMeta>> {\n const registry = await loadRegistry()\n return registry.accounts\n}\n\n/**\n * Load the GitHub token for a specific account.\n * Returns null if the token file doesn't exist.\n */\nexport async function loadAccountToken(id: string): Promise<string | null> {\n if (!validateAccountId(id)) {\n throw new Error(`Invalid account ID: ${id}`)\n }\n\n try {\n const tokenPath = accountTokenPath(id)\n const token = await fs.readFile(tokenPath, \"utf8\")\n return token.trim() || null\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null\n }\n throw error\n }\n}\n\n/**\n * Save the GitHub token for a specific account with secure permissions.\n */\nexport async function saveAccountToken(\n id: string,\n token: string,\n): Promise<void> {\n if (!validateAccountId(id)) {\n throw new Error(`Invalid account ID: ${id}`)\n }\n\n const tokenPath = accountTokenPath(id)\n await fs.writeFile(tokenPath, token, { mode: 0o600 })\n}\n\n/**\n * Remove the GitHub token file for a specific account.\n */\nexport async function removeAccountToken(id: string): Promise<void> {\n if (!validateAccountId(id)) {\n throw new Error(`Invalid account ID: ${id}`)\n }\n\n const tokenPath = accountTokenPath(id)\n try {\n await fs.unlink(tokenPath)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw error\n }\n // File doesn't exist, nothing to remove\n }\n}\n\n/**\n * Check if the legacy github_token file exists.\n */\nexport async function hasLegacyToken(): Promise<boolean> {\n try {\n const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n return content.trim().length > 0\n } catch {\n return false\n }\n}\n\n/**\n * Read the legacy github_token file.\n * Returns null if the file doesn't exist or is empty.\n */\nexport async function readLegacyToken(): Promise<string | null> {\n try {\n const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n return content.trim() || null\n } catch {\n return null\n }\n}\n\n/**\n * Check if the registry file exists and has accounts.\n */\nexport async function hasRegistry(): Promise<boolean> {\n const registry = await loadRegistry()\n return registry.accounts.length > 0\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"accounts-registry-CQYvRe65.js","names":["parsed: unknown","index: number"],"sources":["../src/lib/accounts-registry.ts"],"sourcesContent":["import fs from \"node:fs/promises\"\nimport { z } from \"zod\"\n\nimport type { AccountMeta, AccountRegistry } from \"~/lib/types/account\"\n\nimport { accountTokenPath, PATHS } from \"./paths\"\n\n/**\n * Validate account ID (GitHub login).\n * Rules:\n * - Only alphanumeric characters or single hyphens\n * - 1-39 chars\n * - Cannot begin or end with a hyphen\n * - No consecutive hyphens\n */\nexport function validateAccountId(id: string): boolean {\n if (id.length === 0 || id.length > 39) return false\n if (!/^[a-z0-9-]+$/i.test(id)) return false\n if (id.startsWith(\"-\") || id.endsWith(\"-\")) return false\n if (id.includes(\"--\")) return false\n return true\n}\n\nconst accountMetaSchema = z.object({\n id: z.string().refine(validateAccountId, {\n message:\n \"Invalid account id. Expected a GitHub login (1-39 chars, alphanumeric or single hyphens, no leading/trailing hyphen, no consecutive hyphens).\",\n }),\n accountType: z.enum([\"individual\", \"business\", \"enterprise\"]),\n addedAt: z.number(),\n})\n\nconst accountRegistrySchema = z.object({\n version: z.literal(1),\n accounts: z.array(accountMetaSchema),\n})\n\n/**\n * Create an empty registry with the current schema version.\n */\nfunction createEmptyRegistry(): AccountRegistry {\n return {\n version: 1,\n accounts: [],\n }\n}\n\n/**\n * Load the accounts registry from disk.\n * Returns an empty registry if the file doesn't exist.\n */\nexport async function loadRegistry(): Promise<AccountRegistry> {\n try {\n const content = await fs.readFile(PATHS.ACCOUNTS_REGISTRY_PATH, \"utf8\")\n if (!content.trim()) {\n return createEmptyRegistry()\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(content) as unknown\n } catch (error) {\n throw new Error(\n `Invalid accounts registry JSON at ${PATHS.ACCOUNTS_REGISTRY_PATH}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n )\n }\n\n const result = accountRegistrySchema.safeParse(parsed)\n if (!result.success) {\n const issues = result.error.issues\n .map((issue) => `${issue.path.join(\".\")}: ${issue.message}`)\n .join(\"; \")\n\n throw new Error(\n `Invalid accounts registry at ${PATHS.ACCOUNTS_REGISTRY_PATH}: ${issues}`,\n )\n }\n\n const registry = result.data\n\n const seen = new Set<string>()\n for (const account of registry.accounts) {\n if (seen.has(account.id)) {\n throw new Error(\n `Invalid accounts registry at ${PATHS.ACCOUNTS_REGISTRY_PATH}: duplicate account id \"${account.id}\"`,\n )\n }\n seen.add(account.id)\n }\n\n return registry\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return createEmptyRegistry()\n }\n throw error\n }\n}\n\n/**\n * Save the accounts registry to disk with secure permissions.\n */\nexport async function saveRegistry(registry: AccountRegistry): Promise<void> {\n const content = JSON.stringify(registry, null, 2)\n await fs.writeFile(PATHS.ACCOUNTS_REGISTRY_PATH, content, { mode: 0o600 })\n}\n\n/**\n * Add an account to the registry.\n * The account is appended to the end of the list (lowest priority).\n */\nexport async function addAccountToRegistry(meta: AccountMeta): Promise<void> {\n if (!validateAccountId(meta.id)) {\n throw new Error(`Invalid account ID: ${meta.id}`)\n }\n\n const registry = await loadRegistry()\n\n // Check for duplicate\n if (registry.accounts.some((a) => a.id === meta.id)) {\n throw new Error(`Account already exists: ${meta.id}`)\n }\n\n registry.accounts.push(meta)\n await saveRegistry(registry)\n}\n\n/**\n * Remove an account from the registry by ID or index (1-based).\n * Returns the removed account metadata.\n */\nexport async function removeAccountFromRegistry(\n idOrIndex: string | number,\n): Promise<AccountMeta> {\n const registry = await loadRegistry()\n let index: number\n\n if (typeof idOrIndex === \"number\") {\n // 1-based index\n index = idOrIndex - 1\n if (index < 0 || index >= registry.accounts.length) {\n throw new Error(`Invalid account index: ${idOrIndex}`)\n }\n } else {\n index = registry.accounts.findIndex((a) => a.id === idOrIndex)\n if (index === -1) {\n throw new Error(`Account not found: ${idOrIndex}`)\n }\n }\n\n const [removed] = registry.accounts.splice(index, 1)\n await saveRegistry(registry)\n return removed\n}\n\n/**\n * List all accounts from the registry.\n */\nexport async function listAccountsFromRegistry(): Promise<Array<AccountMeta>> {\n const registry = await loadRegistry()\n return registry.accounts\n}\n\n/**\n * Load the GitHub token for a specific account.\n * Returns null if the token file doesn't exist.\n */\nexport async function loadAccountToken(id: string): Promise<string | null> {\n if (!validateAccountId(id)) {\n throw new Error(`Invalid account ID: ${id}`)\n }\n\n try {\n const tokenPath = accountTokenPath(id)\n const token = await fs.readFile(tokenPath, \"utf8\")\n return token.trim() || null\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null\n }\n throw error\n }\n}\n\n/**\n * Save the GitHub token for a specific account with secure permissions.\n */\nexport async function saveAccountToken(\n id: string,\n token: string,\n): Promise<void> {\n if (!validateAccountId(id)) {\n throw new Error(`Invalid account ID: ${id}`)\n }\n\n const tokenPath = accountTokenPath(id)\n await fs.writeFile(tokenPath, token, { mode: 0o600 })\n}\n\n/**\n * Remove the GitHub token file for a specific account.\n */\nexport async function removeAccountToken(id: string): Promise<void> {\n if (!validateAccountId(id)) {\n throw new Error(`Invalid account ID: ${id}`)\n }\n\n const tokenPath = accountTokenPath(id)\n try {\n await fs.unlink(tokenPath)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n throw error\n }\n // File doesn't exist, nothing to remove\n }\n}\n\n/**\n * Check if the legacy github_token file exists.\n */\nexport async function hasLegacyToken(): Promise<boolean> {\n try {\n const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n return content.trim().length > 0\n } catch {\n return false\n }\n}\n\n/**\n * Read the legacy github_token file.\n * Returns null if the file doesn't exist or is empty.\n */\nexport async function readLegacyToken(): Promise<string | null> {\n try {\n const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n return content.trim() || null\n } catch {\n return null\n }\n}\n\n/**\n * Check if the registry file exists and has accounts.\n */\nexport async function hasRegistry(): Promise<boolean> {\n const registry = await loadRegistry()\n return registry.accounts.length > 0\n}\n"],"mappings":"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"}
|