opencode-anthropic-multi-account 0.2.2 → 0.2.4
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/anthropic-prompt.d.ts +1 -0
- package/dist/auth-handler.d.ts +3 -2
- package/dist/constants.d.ts +0 -1
- package/dist/executor.d.ts +8 -2
- package/dist/index.js +1438 -496
- package/dist/pi-ai-adapter.d.ts +16 -0
- package/dist/pool-chain-executor.d.ts +11 -0
- package/dist/request-transform.d.ts +2 -0
- package/dist/runtime-factory.d.ts +3 -6
- package/dist/token.d.ts +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { AnthropicAuthPlugin as AnthropicAuthPlugin2 } from "opencode-anthropic-auth";
|
|
3
2
|
import { tool } from "@opencode-ai/plugin";
|
|
4
3
|
|
|
5
4
|
// ../multi-account-core/src/account-manager.ts
|
|
@@ -85,6 +84,23 @@ var PluginConfigSchema = v.object({
|
|
|
85
84
|
quiet_mode: v.optional(v.boolean(), false),
|
|
86
85
|
debug: v.optional(v.boolean(), false)
|
|
87
86
|
});
|
|
87
|
+
var TokenRefreshError = class _TokenRefreshError extends Error {
|
|
88
|
+
status;
|
|
89
|
+
permanent;
|
|
90
|
+
constructor(permanent, status) {
|
|
91
|
+
super(status === void 0 ? "Token refresh failed" : `Token refresh failed: ${status}`);
|
|
92
|
+
this.name = "TokenRefreshError";
|
|
93
|
+
this.status = status;
|
|
94
|
+
this.permanent = permanent;
|
|
95
|
+
Object.setPrototypeOf(this, _TokenRefreshError.prototype);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
function isTokenRefreshError(error) {
|
|
99
|
+
if (error instanceof TokenRefreshError) return true;
|
|
100
|
+
if (!(error instanceof Error)) return false;
|
|
101
|
+
const candidate = error;
|
|
102
|
+
return candidate.name === "TokenRefreshError" && typeof candidate.permanent === "boolean" && (candidate.status === void 0 || typeof candidate.status === "number");
|
|
103
|
+
}
|
|
88
104
|
|
|
89
105
|
// ../multi-account-core/src/config.ts
|
|
90
106
|
var DEFAULT_CONFIG_FILENAME = "multiauth-config.json";
|
|
@@ -225,6 +241,14 @@ function createMinimalClient() {
|
|
|
225
241
|
}
|
|
226
242
|
};
|
|
227
243
|
}
|
|
244
|
+
function getClearedOAuthBody() {
|
|
245
|
+
return {
|
|
246
|
+
type: "oauth",
|
|
247
|
+
refresh: "",
|
|
248
|
+
access: "",
|
|
249
|
+
expires: 0
|
|
250
|
+
};
|
|
251
|
+
}
|
|
228
252
|
|
|
229
253
|
// ../multi-account-core/src/claims.ts
|
|
230
254
|
var CLAIMS_FILENAME = "multiauth-claims.json";
|
|
@@ -648,13 +672,7 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
648
672
|
});
|
|
649
673
|
}
|
|
650
674
|
async markRevoked(uuid) {
|
|
651
|
-
await this.
|
|
652
|
-
account.isAuthDisabled = true;
|
|
653
|
-
account.authDisabledReason = "OAuth token revoked (403)";
|
|
654
|
-
account.accessToken = void 0;
|
|
655
|
-
account.expiresAt = void 0;
|
|
656
|
-
});
|
|
657
|
-
this.runtimeFactory?.invalidate(uuid);
|
|
675
|
+
await this.removeAccountByUuid(uuid);
|
|
658
676
|
}
|
|
659
677
|
async markSuccess(uuid) {
|
|
660
678
|
this.last429Map.delete(uuid);
|
|
@@ -677,15 +695,32 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
677
695
|
}).catch(() => {
|
|
678
696
|
});
|
|
679
697
|
}
|
|
698
|
+
async clearOpenCodeAuthIfNoAccountsRemain() {
|
|
699
|
+
if (!this.client) return;
|
|
700
|
+
const storage = await this.store.load();
|
|
701
|
+
if (storage.accounts.length > 0) return;
|
|
702
|
+
await this.client.auth.set({
|
|
703
|
+
path: { id: providerAuthId },
|
|
704
|
+
body: getClearedOAuthBody()
|
|
705
|
+
}).catch(() => {
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
async removeAccountByUuid(uuid) {
|
|
709
|
+
const removed = await this.store.removeAccount(uuid);
|
|
710
|
+
if (!removed) return;
|
|
711
|
+
this.last429Map.delete(uuid);
|
|
712
|
+
this.runtimeFactory?.invalidate(uuid);
|
|
713
|
+
await this.refresh();
|
|
714
|
+
await this.clearOpenCodeAuthIfNoAccountsRemain();
|
|
715
|
+
}
|
|
680
716
|
async markAuthFailure(uuid, result) {
|
|
717
|
+
if (!result.ok && result.permanent) {
|
|
718
|
+
await this.removeAccountByUuid(uuid);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
681
721
|
await this.store.mutateStorage((storage) => {
|
|
682
722
|
const account = storage.accounts.find((entry) => entry.uuid === uuid);
|
|
683
723
|
if (!account) return;
|
|
684
|
-
if (!result.ok && result.permanent) {
|
|
685
|
-
account.isAuthDisabled = true;
|
|
686
|
-
account.authDisabledReason = "Token permanently rejected (400/401/403)";
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
724
|
account.consecutiveAuthFailures = (account.consecutiveAuthFailures ?? 0) + 1;
|
|
690
725
|
const maxFailures = getConfig().max_consecutive_auth_failures;
|
|
691
726
|
const usableCount = storage.accounts.filter(
|
|
@@ -782,11 +817,21 @@ function createAccountManagerForProvider(dependencies) {
|
|
|
782
817
|
this.cached = [];
|
|
783
818
|
this.activeAccountUuid = void 0;
|
|
784
819
|
}
|
|
785
|
-
async addAccount(auth) {
|
|
820
|
+
async addAccount(auth, email) {
|
|
786
821
|
if (!auth.refresh) return;
|
|
787
|
-
const
|
|
788
|
-
if (
|
|
822
|
+
const existingByToken = this.cached.find((account) => account.refreshToken === auth.refresh);
|
|
823
|
+
if (existingByToken) return;
|
|
824
|
+
if (email) {
|
|
825
|
+
const existingByEmail = this.cached.find(
|
|
826
|
+
(account) => account.email && account.email === email
|
|
827
|
+
);
|
|
828
|
+
if (existingByEmail?.uuid) {
|
|
829
|
+
await this.replaceAccountCredentials(existingByEmail.uuid, auth);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
789
833
|
const newAccount = this.createNewAccount(auth, Date.now());
|
|
834
|
+
if (email) newAccount.email = email;
|
|
790
835
|
await this.store.addAccount(newAccount);
|
|
791
836
|
this.activeAccountUuid = newAccount.uuid;
|
|
792
837
|
await this.store.setActiveUuid(newAccount.uuid);
|
|
@@ -1112,7 +1157,9 @@ var MAX_SERVER_RETRIES_PER_ATTEMPT = 2;
|
|
|
1112
1157
|
var MAX_RESOLVE_ATTEMPTS = 10;
|
|
1113
1158
|
var SERVER_RETRY_BASE_MS = 1e3;
|
|
1114
1159
|
var SERVER_RETRY_MAX_MS = 4e3;
|
|
1115
|
-
|
|
1160
|
+
function isAbortError(error) {
|
|
1161
|
+
return error instanceof Error && error.name === "AbortError";
|
|
1162
|
+
}
|
|
1116
1163
|
function createExecutorForProvider(providerName, dependencies) {
|
|
1117
1164
|
const {
|
|
1118
1165
|
handleRateLimitResponse: handleRateLimitResponse2,
|
|
@@ -1123,77 +1170,49 @@ function createExecutorForProvider(providerName, dependencies) {
|
|
|
1123
1170
|
} = dependencies;
|
|
1124
1171
|
async function executeWithAccountRotation2(manager, runtimeFactory, client, input, init) {
|
|
1125
1172
|
const maxRetries = Math.max(MIN_MAX_RETRIES, manager.getAccountCount() * RETRIES_PER_ACCOUNT);
|
|
1126
|
-
let retries = 0;
|
|
1127
1173
|
let previousAccountUuid;
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
);
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
let response;
|
|
1144
|
-
try {
|
|
1145
|
-
runtime = await runtimeFactory.getRuntime(accountUuid);
|
|
1146
|
-
response = await runtime.fetch(input, init);
|
|
1147
|
-
} catch (error) {
|
|
1148
|
-
if (await handleRuntimeFetchFailure(manager, runtimeFactory, client, account, error)) {
|
|
1149
|
-
continue;
|
|
1174
|
+
async function retryServerErrors(account, runtime) {
|
|
1175
|
+
for (let attempt = 0; attempt < MAX_SERVER_RETRIES_PER_ATTEMPT; attempt++) {
|
|
1176
|
+
const backoff = Math.min(SERVER_RETRY_BASE_MS * 2 ** attempt, SERVER_RETRY_MAX_MS);
|
|
1177
|
+
const jitteredBackoff = backoff * (0.5 + Math.random() * 0.5);
|
|
1178
|
+
await sleep2(jitteredBackoff);
|
|
1179
|
+
let retryResponse;
|
|
1180
|
+
try {
|
|
1181
|
+
retryResponse = await runtime.fetch(input, init);
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
if (isAbortError(error)) throw error;
|
|
1184
|
+
if (await handleRuntimeFetchFailure(manager, runtimeFactory, client, account, error)) {
|
|
1185
|
+
return null;
|
|
1186
|
+
}
|
|
1187
|
+
void showToast2(client, `${getAccountLabel2(account)} network error \u2014 switching`, "warning");
|
|
1188
|
+
return null;
|
|
1150
1189
|
}
|
|
1151
|
-
|
|
1152
|
-
continue;
|
|
1190
|
+
if (retryResponse.status < 500) return retryResponse;
|
|
1153
1191
|
}
|
|
1192
|
+
return null;
|
|
1193
|
+
}
|
|
1194
|
+
const dispatchResponseStatus = async (account, accountUuid, runtime, response, allow401Retry, from401RefreshRetry) => {
|
|
1154
1195
|
if (response.status >= 500) {
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1196
|
+
const recovered = await retryServerErrors(account, runtime);
|
|
1197
|
+
if (recovered === null) {
|
|
1198
|
+
return { type: "retryOuter" };
|
|
1199
|
+
}
|
|
1200
|
+
response = recovered;
|
|
1201
|
+
}
|
|
1202
|
+
if (response.status === 401) {
|
|
1203
|
+
if (allow401Retry) {
|
|
1204
|
+
runtimeFactory.invalidate(accountUuid);
|
|
1162
1205
|
try {
|
|
1163
|
-
|
|
1206
|
+
const retryRuntime = await runtimeFactory.getRuntime(accountUuid);
|
|
1207
|
+
const retryResponse = await retryRuntime.fetch(input, init);
|
|
1208
|
+
return dispatchResponseStatus(account, accountUuid, retryRuntime, retryResponse, false, true);
|
|
1164
1209
|
} catch (error) {
|
|
1210
|
+
if (isAbortError(error)) throw error;
|
|
1165
1211
|
if (await handleRuntimeFetchFailure(manager, runtimeFactory, client, account, error)) {
|
|
1166
|
-
|
|
1167
|
-
break;
|
|
1212
|
+
return { type: "retryOuter" };
|
|
1168
1213
|
}
|
|
1169
|
-
|
|
1170
|
-
void showToast2(client, `${getAccountLabel2(account)} network error \u2014 switching`, "warning");
|
|
1171
|
-
break;
|
|
1172
|
-
}
|
|
1173
|
-
if (serverResponse.status < 500) break;
|
|
1174
|
-
}
|
|
1175
|
-
if (authFailureDuringServerRetry) {
|
|
1176
|
-
continue;
|
|
1177
|
-
}
|
|
1178
|
-
if (networkErrorDuringServerRetry || serverResponse.status >= 500) {
|
|
1179
|
-
continue;
|
|
1180
|
-
}
|
|
1181
|
-
response = serverResponse;
|
|
1182
|
-
}
|
|
1183
|
-
if (response.status === 401) {
|
|
1184
|
-
runtimeFactory.invalidate(accountUuid);
|
|
1185
|
-
try {
|
|
1186
|
-
const retryRuntime = await runtimeFactory.getRuntime(accountUuid);
|
|
1187
|
-
const retryResponse = await retryRuntime.fetch(input, init);
|
|
1188
|
-
if (retryResponse.status !== 401) {
|
|
1189
|
-
await manager.markSuccess(accountUuid);
|
|
1190
|
-
return retryResponse;
|
|
1214
|
+
return { type: "retryOuter" };
|
|
1191
1215
|
}
|
|
1192
|
-
} catch (error) {
|
|
1193
|
-
if (await handleRuntimeFetchFailure(manager, runtimeFactory, client, account, error)) {
|
|
1194
|
-
continue;
|
|
1195
|
-
}
|
|
1196
|
-
continue;
|
|
1197
1216
|
}
|
|
1198
1217
|
await manager.markAuthFailure(accountUuid, { ok: false, permanent: false });
|
|
1199
1218
|
await manager.refresh();
|
|
@@ -1204,7 +1223,7 @@ function createExecutorForProvider(providerName, dependencies) {
|
|
|
1204
1223
|
);
|
|
1205
1224
|
}
|
|
1206
1225
|
void showToast2(client, `${getAccountLabel2(account)} auth failed \u2014 switching to next account.`, "warning");
|
|
1207
|
-
|
|
1226
|
+
return { type: "retryOuter" };
|
|
1208
1227
|
}
|
|
1209
1228
|
if (response.status === 403) {
|
|
1210
1229
|
const revoked = await isRevokedTokenResponse(response);
|
|
@@ -1221,26 +1240,62 @@ function createExecutorForProvider(providerName, dependencies) {
|
|
|
1221
1240
|
`All ${providerName} accounts have been revoked or disabled. Re-authenticate with \`opencode auth login\`.`
|
|
1222
1241
|
);
|
|
1223
1242
|
}
|
|
1224
|
-
|
|
1243
|
+
return { type: "retryOuter" };
|
|
1244
|
+
}
|
|
1245
|
+
if (from401RefreshRetry) {
|
|
1246
|
+
return { type: "handled", response };
|
|
1225
1247
|
}
|
|
1226
1248
|
}
|
|
1227
1249
|
if (response.status === 429) {
|
|
1228
1250
|
await handleRateLimitResponse2(manager, client, account, response);
|
|
1251
|
+
return { type: "handled" };
|
|
1252
|
+
}
|
|
1253
|
+
return { type: "success", response };
|
|
1254
|
+
};
|
|
1255
|
+
for (let retries = 1; retries <= maxRetries; retries++) {
|
|
1256
|
+
await manager.refresh();
|
|
1257
|
+
const account = await resolveAccount(manager, client);
|
|
1258
|
+
const accountUuid = account.uuid;
|
|
1259
|
+
if (!accountUuid) continue;
|
|
1260
|
+
if (previousAccountUuid && accountUuid !== previousAccountUuid && manager.getAccountCount() > 1) {
|
|
1261
|
+
void showToast2(client, `Switched to ${getAccountLabel2(account)}`, "info");
|
|
1262
|
+
}
|
|
1263
|
+
previousAccountUuid = accountUuid;
|
|
1264
|
+
let runtime;
|
|
1265
|
+
let response;
|
|
1266
|
+
try {
|
|
1267
|
+
runtime = await runtimeFactory.getRuntime(accountUuid);
|
|
1268
|
+
response = await runtime.fetch(input, init);
|
|
1269
|
+
} catch (error) {
|
|
1270
|
+
if (isAbortError(error)) throw error;
|
|
1271
|
+
if (await handleRuntimeFetchFailure(manager, runtimeFactory, client, account, error)) {
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
void showToast2(client, `${getAccountLabel2(account)} network error \u2014 switching`, "warning");
|
|
1275
|
+
continue;
|
|
1276
|
+
}
|
|
1277
|
+
const transition = await dispatchResponseStatus(account, accountUuid, runtime, response, true, false);
|
|
1278
|
+
if (transition.type === "retryOuter" || transition.type === "handled") {
|
|
1279
|
+
if (transition.type === "handled" && transition.response) {
|
|
1280
|
+
return transition.response;
|
|
1281
|
+
}
|
|
1229
1282
|
continue;
|
|
1230
1283
|
}
|
|
1231
1284
|
await manager.markSuccess(accountUuid);
|
|
1232
|
-
return response;
|
|
1285
|
+
return transition.response;
|
|
1233
1286
|
}
|
|
1287
|
+
throw new Error(
|
|
1288
|
+
`Exhausted ${maxRetries} retries across all accounts. All attempts failed due to auth errors, rate limits, or token issues.`
|
|
1289
|
+
);
|
|
1234
1290
|
}
|
|
1235
1291
|
async function handleRuntimeFetchFailure(manager, runtimeFactory, client, account, error) {
|
|
1236
|
-
|
|
1237
|
-
if (refreshFailureStatus === void 0) return false;
|
|
1292
|
+
if (!isTokenRefreshError(error)) return false;
|
|
1238
1293
|
if (!account.uuid) return false;
|
|
1239
1294
|
const accountUuid = account.uuid;
|
|
1240
1295
|
runtimeFactory.invalidate(accountUuid);
|
|
1241
1296
|
await manager.markAuthFailure(accountUuid, {
|
|
1242
1297
|
ok: false,
|
|
1243
|
-
permanent:
|
|
1298
|
+
permanent: error.permanent
|
|
1244
1299
|
});
|
|
1245
1300
|
await manager.refresh();
|
|
1246
1301
|
if (!manager.hasAnyUsableAccount()) {
|
|
@@ -1285,13 +1340,6 @@ function createExecutorForProvider(providerName, dependencies) {
|
|
|
1285
1340
|
executeWithAccountRotation: executeWithAccountRotation2
|
|
1286
1341
|
};
|
|
1287
1342
|
}
|
|
1288
|
-
function getRefreshFailureStatus(error) {
|
|
1289
|
-
if (!(error instanceof Error)) return void 0;
|
|
1290
|
-
const matched = error.message.match(/Token refresh failed:\s*(\d{3})/);
|
|
1291
|
-
if (!matched) return void 0;
|
|
1292
|
-
const status = Number(matched[1]);
|
|
1293
|
-
return Number.isFinite(status) ? status : void 0;
|
|
1294
|
-
}
|
|
1295
1343
|
async function isRevokedTokenResponse(response) {
|
|
1296
1344
|
try {
|
|
1297
1345
|
const cloned = response.clone();
|
|
@@ -1306,6 +1354,7 @@ async function isRevokedTokenResponse(response) {
|
|
|
1306
1354
|
var INITIAL_DELAY_MS = 5e3;
|
|
1307
1355
|
function createProactiveRefreshQueueForProvider(dependencies) {
|
|
1308
1356
|
const {
|
|
1357
|
+
providerAuthId,
|
|
1309
1358
|
getConfig: getConfig2,
|
|
1310
1359
|
refreshToken: refreshToken2,
|
|
1311
1360
|
isTokenExpired: isTokenExpired2,
|
|
@@ -1324,6 +1373,10 @@ function createProactiveRefreshQueueForProvider(dependencies) {
|
|
|
1324
1373
|
const config = getConfig2();
|
|
1325
1374
|
if (!config.proactive_refresh) return;
|
|
1326
1375
|
this.runToken++;
|
|
1376
|
+
if (this.timeoutHandle) {
|
|
1377
|
+
clearTimeout(this.timeoutHandle);
|
|
1378
|
+
this.timeoutHandle = null;
|
|
1379
|
+
}
|
|
1327
1380
|
this.scheduleNext(this.runToken, INITIAL_DELAY_MS);
|
|
1328
1381
|
debugLog2(this.client, "Proactive refresh started", {
|
|
1329
1382
|
intervalSeconds: config.proactive_refresh_interval_seconds,
|
|
@@ -1398,23 +1451,41 @@ function createProactiveRefreshQueueForProvider(dependencies) {
|
|
|
1398
1451
|
}
|
|
1399
1452
|
async persistFailure(account, permanent) {
|
|
1400
1453
|
try {
|
|
1401
|
-
|
|
1402
|
-
|
|
1454
|
+
const accountUuid = account.uuid;
|
|
1455
|
+
if (!accountUuid) return;
|
|
1456
|
+
if (permanent) {
|
|
1457
|
+
const removed = await this.store.removeAccount(accountUuid);
|
|
1458
|
+
if (!removed) return;
|
|
1459
|
+
this.onInvalidate?.(accountUuid);
|
|
1460
|
+
await this.clearOpenCodeAuthIfNoAccountsRemain();
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
await this.store.mutateStorage((storage) => {
|
|
1464
|
+
const target = storage.accounts.find((entry) => entry.uuid === accountUuid);
|
|
1465
|
+
if (!target) return;
|
|
1466
|
+
target.consecutiveAuthFailures = (target.consecutiveAuthFailures ?? 0) + 1;
|
|
1467
|
+
const maxFailures = getConfig2().max_consecutive_auth_failures;
|
|
1468
|
+
const usableCount = storage.accounts.filter(
|
|
1469
|
+
(entry) => entry.enabled && !entry.isAuthDisabled && entry.uuid !== accountUuid
|
|
1470
|
+
).length;
|
|
1471
|
+
if (target.consecutiveAuthFailures >= maxFailures && usableCount > 0) {
|
|
1403
1472
|
target.isAuthDisabled = true;
|
|
1404
|
-
target.authDisabledReason =
|
|
1405
|
-
} else {
|
|
1406
|
-
target.consecutiveAuthFailures = (target.consecutiveAuthFailures ?? 0) + 1;
|
|
1407
|
-
const maxFailures = getConfig2().max_consecutive_auth_failures;
|
|
1408
|
-
if (target.consecutiveAuthFailures >= maxFailures) {
|
|
1409
|
-
target.isAuthDisabled = true;
|
|
1410
|
-
target.authDisabledReason = `${maxFailures} consecutive auth failures (proactive refresh)`;
|
|
1411
|
-
}
|
|
1473
|
+
target.authDisabledReason = `${maxFailures} consecutive auth failures (proactive refresh)`;
|
|
1412
1474
|
}
|
|
1413
1475
|
});
|
|
1414
1476
|
} catch {
|
|
1415
1477
|
debugLog2(this.client, `Failed to persist auth failure for ${account.uuid}`);
|
|
1416
1478
|
}
|
|
1417
1479
|
}
|
|
1480
|
+
async clearOpenCodeAuthIfNoAccountsRemain() {
|
|
1481
|
+
const storage = await this.store.load();
|
|
1482
|
+
if (storage.accounts.length > 0) return;
|
|
1483
|
+
await this.client.auth.set({
|
|
1484
|
+
path: { id: providerAuthId },
|
|
1485
|
+
body: getClearedOAuthBody()
|
|
1486
|
+
}).catch(() => {
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1418
1489
|
};
|
|
1419
1490
|
}
|
|
1420
1491
|
|
|
@@ -1763,6 +1834,8 @@ var anthropicOAuthAdapter = {
|
|
|
1763
1834
|
oauthBetaHeader: "oauth-2025-04-20",
|
|
1764
1835
|
requestBetaHeader: "oauth-2025-04-20,interleaved-thinking-2025-05-14",
|
|
1765
1836
|
cliUserAgent: "claude-cli/2.1.2 (external, cli)",
|
|
1837
|
+
cliVersion: "2.1.80",
|
|
1838
|
+
billingSalt: "59cf53e54c78",
|
|
1766
1839
|
toolPrefix: "mcp_",
|
|
1767
1840
|
accountStorageFilename: "anthropic-multi-account-accounts.json",
|
|
1768
1841
|
transform: {
|
|
@@ -1779,6 +1852,336 @@ var anthropicOAuthAdapter = {
|
|
|
1779
1852
|
supported: true
|
|
1780
1853
|
};
|
|
1781
1854
|
|
|
1855
|
+
// ../multi-account-core/src/pool-types.ts
|
|
1856
|
+
import * as v5 from "valibot";
|
|
1857
|
+
var PoolConfigSchema = v5.object({
|
|
1858
|
+
name: v5.string(),
|
|
1859
|
+
baseProvider: v5.string(),
|
|
1860
|
+
members: v5.array(v5.string()),
|
|
1861
|
+
enabled: v5.boolean()
|
|
1862
|
+
});
|
|
1863
|
+
var ChainEntryConfigSchema = v5.object({
|
|
1864
|
+
pool: v5.string(),
|
|
1865
|
+
model: v5.optional(v5.string()),
|
|
1866
|
+
enabled: v5.boolean()
|
|
1867
|
+
});
|
|
1868
|
+
var ChainConfigSchema = v5.object({
|
|
1869
|
+
name: v5.string(),
|
|
1870
|
+
entries: v5.array(ChainEntryConfigSchema),
|
|
1871
|
+
enabled: v5.boolean()
|
|
1872
|
+
});
|
|
1873
|
+
var PoolChainConfigSchema = v5.object({
|
|
1874
|
+
pools: v5.optional(v5.array(PoolConfigSchema), []),
|
|
1875
|
+
chains: v5.optional(v5.array(ChainConfigSchema), [])
|
|
1876
|
+
});
|
|
1877
|
+
|
|
1878
|
+
// ../multi-account-core/src/pool-config-store.ts
|
|
1879
|
+
import { promises as fs6 } from "node:fs";
|
|
1880
|
+
import { dirname as dirname5, join as join7 } from "node:path";
|
|
1881
|
+
import lockfile2 from "proper-lockfile";
|
|
1882
|
+
import * as v6 from "valibot";
|
|
1883
|
+
var POOL_CONFIG_FILENAME = "multiauth-pools.json";
|
|
1884
|
+
function createEmptyConfig() {
|
|
1885
|
+
return { pools: [], chains: [] };
|
|
1886
|
+
}
|
|
1887
|
+
function getGlobalConfigPath() {
|
|
1888
|
+
return join7(getConfigDir2(), POOL_CONFIG_FILENAME);
|
|
1889
|
+
}
|
|
1890
|
+
async function resolveConfigPath() {
|
|
1891
|
+
const projectPath = join7(process.cwd(), ".opencode", POOL_CONFIG_FILENAME);
|
|
1892
|
+
try {
|
|
1893
|
+
await fs6.access(projectPath);
|
|
1894
|
+
return projectPath;
|
|
1895
|
+
} catch {
|
|
1896
|
+
}
|
|
1897
|
+
return getGlobalConfigPath();
|
|
1898
|
+
}
|
|
1899
|
+
function parsePoolChainConfig(content) {
|
|
1900
|
+
let parsed;
|
|
1901
|
+
try {
|
|
1902
|
+
parsed = JSON.parse(content);
|
|
1903
|
+
} catch {
|
|
1904
|
+
return null;
|
|
1905
|
+
}
|
|
1906
|
+
const validation = v6.safeParse(PoolChainConfigSchema, parsed);
|
|
1907
|
+
return validation.success ? validation.output : null;
|
|
1908
|
+
}
|
|
1909
|
+
async function loadPoolChainConfig() {
|
|
1910
|
+
const path = await resolveConfigPath();
|
|
1911
|
+
try {
|
|
1912
|
+
const content = await fs6.readFile(path, "utf-8");
|
|
1913
|
+
return parsePoolChainConfig(content) ?? createEmptyConfig();
|
|
1914
|
+
} catch {
|
|
1915
|
+
return createEmptyConfig();
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
// ../multi-account-core/src/pool-manager.ts
|
|
1920
|
+
var DEFAULT_EXHAUSTED_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
1921
|
+
var PoolManager = class {
|
|
1922
|
+
poolsByName = /* @__PURE__ */ new Map();
|
|
1923
|
+
exhaustedUntilByAccount = /* @__PURE__ */ new Map();
|
|
1924
|
+
exhaustedCooldownMs;
|
|
1925
|
+
constructor(options) {
|
|
1926
|
+
this.exhaustedCooldownMs = options?.exhaustedCooldownMs ?? DEFAULT_EXHAUSTED_COOLDOWN_MS;
|
|
1927
|
+
}
|
|
1928
|
+
loadPools(configs) {
|
|
1929
|
+
this.poolsByName.clear();
|
|
1930
|
+
for (const pool of configs) {
|
|
1931
|
+
this.poolsByName.set(pool.name, pool);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
getPoolForAccount(accountUuid) {
|
|
1935
|
+
for (const pool of this.poolsByName.values()) {
|
|
1936
|
+
if (!pool.enabled) continue;
|
|
1937
|
+
if (pool.members.includes(accountUuid)) return pool;
|
|
1938
|
+
}
|
|
1939
|
+
return null;
|
|
1940
|
+
}
|
|
1941
|
+
getAvailableMembers(pool, accountManager) {
|
|
1942
|
+
if (!pool.enabled) return [];
|
|
1943
|
+
this.clearExpiredExhausted();
|
|
1944
|
+
const accountsByUuid = /* @__PURE__ */ new Map();
|
|
1945
|
+
for (const account of accountManager.getAccounts()) {
|
|
1946
|
+
if (!account.uuid) continue;
|
|
1947
|
+
accountsByUuid.set(account.uuid, account);
|
|
1948
|
+
}
|
|
1949
|
+
return pool.members.filter((accountUuid) => {
|
|
1950
|
+
const account = accountsByUuid.get(accountUuid);
|
|
1951
|
+
if (!account) return false;
|
|
1952
|
+
if (!account.enabled || account.isAuthDisabled) return false;
|
|
1953
|
+
if (this.isExhausted(accountUuid)) return false;
|
|
1954
|
+
if (accountManager.isRateLimited(account)) return false;
|
|
1955
|
+
return true;
|
|
1956
|
+
});
|
|
1957
|
+
}
|
|
1958
|
+
markExhausted(accountUuid) {
|
|
1959
|
+
this.exhaustedUntilByAccount.set(accountUuid, Date.now() + this.exhaustedCooldownMs);
|
|
1960
|
+
}
|
|
1961
|
+
async getNextMember(pool, currentUuid, accountManager) {
|
|
1962
|
+
const availableMembers = this.getAvailableMembers(pool, accountManager);
|
|
1963
|
+
if (availableMembers.length === 0) return null;
|
|
1964
|
+
const excluded = /* @__PURE__ */ new Set();
|
|
1965
|
+
if (currentUuid) excluded.add(currentUuid);
|
|
1966
|
+
const preferred = await this.selectPreferredMember(availableMembers, excluded, accountManager);
|
|
1967
|
+
if (preferred) return preferred;
|
|
1968
|
+
for (const candidate of availableMembers) {
|
|
1969
|
+
if (candidate !== currentUuid) return candidate;
|
|
1970
|
+
}
|
|
1971
|
+
return null;
|
|
1972
|
+
}
|
|
1973
|
+
async buildFailoverPlan(currentAccount, config, accountManager, options) {
|
|
1974
|
+
this.loadPools(config.pools ?? []);
|
|
1975
|
+
if ((config.pools?.length ?? 0) === 0 && (config.chains?.length ?? 0) === 0) {
|
|
1976
|
+
return { candidates: [], skips: [] };
|
|
1977
|
+
}
|
|
1978
|
+
const attemptedAccounts = options?.attemptedAccounts ?? /* @__PURE__ */ new Set();
|
|
1979
|
+
const visitedChainIndexes = options?.visitedChainIndexes ?? /* @__PURE__ */ new Set();
|
|
1980
|
+
const currentUuid = currentAccount?.uuid;
|
|
1981
|
+
const candidates = [];
|
|
1982
|
+
const skips = [];
|
|
1983
|
+
const addedCandidateUuids = /* @__PURE__ */ new Set();
|
|
1984
|
+
const appendPoolCandidates = async (poolName, source, chainIndex) => {
|
|
1985
|
+
const pool = this.poolsByName.get(poolName);
|
|
1986
|
+
if (!pool || !pool.enabled) {
|
|
1987
|
+
skips.push({
|
|
1988
|
+
type: "chain_disabled",
|
|
1989
|
+
poolName,
|
|
1990
|
+
reason: "Pool is missing or disabled"
|
|
1991
|
+
});
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
const available = this.getAvailableMembers(pool, accountManager);
|
|
1995
|
+
if (available.length === 0) {
|
|
1996
|
+
skips.push({
|
|
1997
|
+
type: "pool_exhausted",
|
|
1998
|
+
poolName,
|
|
1999
|
+
reason: "No available members"
|
|
2000
|
+
});
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
const poolExclusions = /* @__PURE__ */ new Set();
|
|
2004
|
+
if (currentUuid) poolExclusions.add(currentUuid);
|
|
2005
|
+
while (poolExclusions.size < available.length + (currentUuid ? 1 : 0)) {
|
|
2006
|
+
const nextMember = await this.selectPreferredMember(available, poolExclusions, accountManager);
|
|
2007
|
+
if (!nextMember) break;
|
|
2008
|
+
poolExclusions.add(nextMember);
|
|
2009
|
+
if (attemptedAccounts.has(nextMember)) {
|
|
2010
|
+
skips.push({
|
|
2011
|
+
type: "account_attempted",
|
|
2012
|
+
poolName,
|
|
2013
|
+
reason: "Already attempted in this cascade",
|
|
2014
|
+
detail: nextMember
|
|
2015
|
+
});
|
|
2016
|
+
continue;
|
|
2017
|
+
}
|
|
2018
|
+
if (addedCandidateUuids.has(nextMember)) continue;
|
|
2019
|
+
candidates.push({
|
|
2020
|
+
poolName,
|
|
2021
|
+
accountUuid: nextMember,
|
|
2022
|
+
source,
|
|
2023
|
+
chainIndex
|
|
2024
|
+
});
|
|
2025
|
+
addedCandidateUuids.add(nextMember);
|
|
2026
|
+
}
|
|
2027
|
+
for (const memberUuid of available) {
|
|
2028
|
+
if (poolExclusions.has(memberUuid)) continue;
|
|
2029
|
+
if (attemptedAccounts.has(memberUuid)) {
|
|
2030
|
+
skips.push({
|
|
2031
|
+
type: "account_attempted",
|
|
2032
|
+
poolName,
|
|
2033
|
+
reason: "Already attempted in this cascade",
|
|
2034
|
+
detail: memberUuid
|
|
2035
|
+
});
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
2038
|
+
if (addedCandidateUuids.has(memberUuid)) continue;
|
|
2039
|
+
candidates.push({
|
|
2040
|
+
poolName,
|
|
2041
|
+
accountUuid: memberUuid,
|
|
2042
|
+
source,
|
|
2043
|
+
chainIndex
|
|
2044
|
+
});
|
|
2045
|
+
addedCandidateUuids.add(memberUuid);
|
|
2046
|
+
}
|
|
2047
|
+
};
|
|
2048
|
+
if (currentUuid) {
|
|
2049
|
+
const currentPool = this.getPoolForAccount(currentUuid);
|
|
2050
|
+
if (currentPool) {
|
|
2051
|
+
await appendPoolCandidates(currentPool.name, "pool");
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
let flattenedChainIndex = 0;
|
|
2055
|
+
for (const chain of config.chains ?? []) {
|
|
2056
|
+
if (!chain.enabled) {
|
|
2057
|
+
for (let i = 0; i < chain.entries.length; i++) {
|
|
2058
|
+
skips.push({
|
|
2059
|
+
type: "chain_disabled",
|
|
2060
|
+
poolName: chain.entries[i]?.pool ?? chain.name,
|
|
2061
|
+
reason: `Chain '${chain.name}' is disabled`
|
|
2062
|
+
});
|
|
2063
|
+
flattenedChainIndex += 1;
|
|
2064
|
+
}
|
|
2065
|
+
continue;
|
|
2066
|
+
}
|
|
2067
|
+
for (const entry of chain.entries) {
|
|
2068
|
+
if (visitedChainIndexes.has(flattenedChainIndex)) {
|
|
2069
|
+
skips.push({
|
|
2070
|
+
type: "chain_disabled",
|
|
2071
|
+
poolName: entry.pool,
|
|
2072
|
+
reason: "Chain entry already visited in this cascade",
|
|
2073
|
+
detail: `${flattenedChainIndex}`
|
|
2074
|
+
});
|
|
2075
|
+
flattenedChainIndex += 1;
|
|
2076
|
+
continue;
|
|
2077
|
+
}
|
|
2078
|
+
if (!entry.enabled) {
|
|
2079
|
+
skips.push({
|
|
2080
|
+
type: "chain_disabled",
|
|
2081
|
+
poolName: entry.pool,
|
|
2082
|
+
reason: "Chain entry is disabled",
|
|
2083
|
+
detail: `${flattenedChainIndex}`
|
|
2084
|
+
});
|
|
2085
|
+
flattenedChainIndex += 1;
|
|
2086
|
+
continue;
|
|
2087
|
+
}
|
|
2088
|
+
await appendPoolCandidates(entry.pool, "chain", flattenedChainIndex);
|
|
2089
|
+
flattenedChainIndex += 1;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
return { candidates, skips };
|
|
2093
|
+
}
|
|
2094
|
+
isExhausted(accountUuid) {
|
|
2095
|
+
const exhaustedUntil = this.exhaustedUntilByAccount.get(accountUuid);
|
|
2096
|
+
if (!exhaustedUntil) return false;
|
|
2097
|
+
if (Date.now() >= exhaustedUntil) {
|
|
2098
|
+
this.exhaustedUntilByAccount.delete(accountUuid);
|
|
2099
|
+
return false;
|
|
2100
|
+
}
|
|
2101
|
+
return true;
|
|
2102
|
+
}
|
|
2103
|
+
clearExpiredExhausted() {
|
|
2104
|
+
const now = Date.now();
|
|
2105
|
+
for (const [accountUuid, exhaustedUntil] of this.exhaustedUntilByAccount.entries()) {
|
|
2106
|
+
if (now >= exhaustedUntil) this.exhaustedUntilByAccount.delete(accountUuid);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
async selectPreferredMember(availableMembers, excludedMembers, accountManager) {
|
|
2110
|
+
const availableSet = new Set(availableMembers);
|
|
2111
|
+
const maxAttempts = Math.max(availableMembers.length * 2, 6);
|
|
2112
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
2113
|
+
const selected = await accountManager.selectAccount();
|
|
2114
|
+
if (!selected?.uuid) continue;
|
|
2115
|
+
if (!availableSet.has(selected.uuid)) continue;
|
|
2116
|
+
if (excludedMembers.has(selected.uuid)) continue;
|
|
2117
|
+
return selected.uuid;
|
|
2118
|
+
}
|
|
2119
|
+
for (const memberUuid of availableMembers) {
|
|
2120
|
+
if (!excludedMembers.has(memberUuid)) return memberUuid;
|
|
2121
|
+
}
|
|
2122
|
+
return null;
|
|
2123
|
+
}
|
|
2124
|
+
};
|
|
2125
|
+
|
|
2126
|
+
// ../multi-account-core/src/cascade-state.ts
|
|
2127
|
+
function createCascadeState(prompt, currentAccountUuid) {
|
|
2128
|
+
const attemptedAccounts = /* @__PURE__ */ new Set();
|
|
2129
|
+
if (currentAccountUuid) {
|
|
2130
|
+
attemptedAccounts.add(currentAccountUuid);
|
|
2131
|
+
}
|
|
2132
|
+
return {
|
|
2133
|
+
prompt,
|
|
2134
|
+
attemptedAccounts,
|
|
2135
|
+
visitedChainIndexes: /* @__PURE__ */ new Set()
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
var CascadeStateManager = class {
|
|
2139
|
+
suppressNextStartTurn = false;
|
|
2140
|
+
cascadeState = null;
|
|
2141
|
+
startTurn(prompt, currentAccountUuid) {
|
|
2142
|
+
if (this.suppressNextStartTurn) {
|
|
2143
|
+
this.suppressNextStartTurn = false;
|
|
2144
|
+
return this.ensureCascadeState(prompt, currentAccountUuid);
|
|
2145
|
+
}
|
|
2146
|
+
const shouldReset = !this.cascadeState || this.cascadeState.prompt !== prompt;
|
|
2147
|
+
if (shouldReset) {
|
|
2148
|
+
this.cascadeState = createCascadeState(prompt, currentAccountUuid);
|
|
2149
|
+
return this.cascadeState;
|
|
2150
|
+
}
|
|
2151
|
+
return this.ensureCascadeState(prompt, currentAccountUuid);
|
|
2152
|
+
}
|
|
2153
|
+
ensureCascadeState(prompt, currentAccountUuid) {
|
|
2154
|
+
if (!this.cascadeState || this.cascadeState.prompt !== prompt) {
|
|
2155
|
+
this.cascadeState = createCascadeState(prompt, currentAccountUuid);
|
|
2156
|
+
return this.cascadeState;
|
|
2157
|
+
}
|
|
2158
|
+
if (currentAccountUuid) {
|
|
2159
|
+
this.cascadeState.attemptedAccounts.add(currentAccountUuid);
|
|
2160
|
+
}
|
|
2161
|
+
return this.cascadeState;
|
|
2162
|
+
}
|
|
2163
|
+
markAttempted(accountUuid) {
|
|
2164
|
+
if (!this.cascadeState) return;
|
|
2165
|
+
this.cascadeState.attemptedAccounts.add(accountUuid);
|
|
2166
|
+
}
|
|
2167
|
+
markVisitedChainIndex(index) {
|
|
2168
|
+
if (!this.cascadeState) return;
|
|
2169
|
+
this.cascadeState.visitedChainIndexes.add(index);
|
|
2170
|
+
}
|
|
2171
|
+
clearCascadeState() {
|
|
2172
|
+
this.cascadeState = null;
|
|
2173
|
+
this.suppressNextStartTurn = false;
|
|
2174
|
+
}
|
|
2175
|
+
getSnapshot() {
|
|
2176
|
+
if (!this.cascadeState) return null;
|
|
2177
|
+
return {
|
|
2178
|
+
prompt: this.cascadeState.prompt,
|
|
2179
|
+
attemptedAccounts: new Set(this.cascadeState.attemptedAccounts),
|
|
2180
|
+
visitedChainIndexes: new Set(this.cascadeState.visitedChainIndexes)
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
};
|
|
2184
|
+
|
|
1782
2185
|
// src/constants.ts
|
|
1783
2186
|
var ANTHROPIC_OAUTH_ADAPTER = anthropicOAuthAdapter;
|
|
1784
2187
|
var ANTHROPIC_CLIENT_ID = ANTHROPIC_OAUTH_ADAPTER.oauthClientId;
|
|
@@ -1791,207 +2194,133 @@ var TOOL_PREFIX = ANTHROPIC_OAUTH_ADAPTER.toolPrefix;
|
|
|
1791
2194
|
var ACCOUNTS_FILENAME2 = ANTHROPIC_OAUTH_ADAPTER.accountStorageFilename;
|
|
1792
2195
|
var PLAN_LABELS = ANTHROPIC_OAUTH_ADAPTER.planLabels;
|
|
1793
2196
|
var TOKEN_EXPIRY_BUFFER_MS = 6e4;
|
|
1794
|
-
var TOKEN_REFRESH_TIMEOUT_MS = 3e4;
|
|
1795
2197
|
|
|
1796
|
-
// src/
|
|
1797
|
-
import
|
|
2198
|
+
// src/pi-ai-adapter.ts
|
|
2199
|
+
import { loginAnthropic, refreshAnthropicToken } from "@mariozechner/pi-ai/oauth";
|
|
2200
|
+
|
|
2201
|
+
// src/config.ts
|
|
2202
|
+
initCoreConfig("claude-multiauth.json");
|
|
2203
|
+
|
|
2204
|
+
// src/utils.ts
|
|
2205
|
+
setConfigGetter(getConfig);
|
|
2206
|
+
|
|
2207
|
+
// src/usage.ts
|
|
2208
|
+
import * as v8 from "valibot";
|
|
1798
2209
|
|
|
1799
2210
|
// src/types.ts
|
|
1800
|
-
import * as
|
|
1801
|
-
var OAuthCredentialsSchema2 =
|
|
1802
|
-
type:
|
|
1803
|
-
refresh:
|
|
1804
|
-
access:
|
|
1805
|
-
expires:
|
|
2211
|
+
import * as v7 from "valibot";
|
|
2212
|
+
var OAuthCredentialsSchema2 = v7.object({
|
|
2213
|
+
type: v7.literal("oauth"),
|
|
2214
|
+
refresh: v7.string(),
|
|
2215
|
+
access: v7.string(),
|
|
2216
|
+
expires: v7.number()
|
|
1806
2217
|
});
|
|
1807
|
-
var UsageLimitEntrySchema2 =
|
|
1808
|
-
utilization:
|
|
1809
|
-
resets_at:
|
|
2218
|
+
var UsageLimitEntrySchema2 = v7.object({
|
|
2219
|
+
utilization: v7.number(),
|
|
2220
|
+
resets_at: v7.nullable(v7.string())
|
|
1810
2221
|
});
|
|
1811
|
-
var UsageLimitsSchema2 =
|
|
1812
|
-
five_hour:
|
|
1813
|
-
seven_day:
|
|
1814
|
-
seven_day_sonnet:
|
|
2222
|
+
var UsageLimitsSchema2 = v7.object({
|
|
2223
|
+
five_hour: v7.optional(v7.nullable(UsageLimitEntrySchema2), null),
|
|
2224
|
+
seven_day: v7.optional(v7.nullable(UsageLimitEntrySchema2), null),
|
|
2225
|
+
seven_day_sonnet: v7.optional(v7.nullable(UsageLimitEntrySchema2), null)
|
|
1815
2226
|
});
|
|
1816
|
-
var CredentialRefreshPatchSchema2 =
|
|
1817
|
-
accessToken:
|
|
1818
|
-
expiresAt:
|
|
1819
|
-
refreshToken:
|
|
1820
|
-
uuid:
|
|
1821
|
-
email:
|
|
2227
|
+
var CredentialRefreshPatchSchema2 = v7.object({
|
|
2228
|
+
accessToken: v7.string(),
|
|
2229
|
+
expiresAt: v7.number(),
|
|
2230
|
+
refreshToken: v7.optional(v7.string()),
|
|
2231
|
+
uuid: v7.optional(v7.string()),
|
|
2232
|
+
email: v7.optional(v7.string())
|
|
1822
2233
|
});
|
|
1823
|
-
var StoredAccountSchema2 =
|
|
1824
|
-
uuid:
|
|
1825
|
-
label:
|
|
1826
|
-
email:
|
|
1827
|
-
planTier:
|
|
1828
|
-
refreshToken:
|
|
1829
|
-
accessToken:
|
|
1830
|
-
expiresAt:
|
|
1831
|
-
addedAt:
|
|
1832
|
-
lastUsed:
|
|
1833
|
-
enabled:
|
|
1834
|
-
rateLimitResetAt:
|
|
1835
|
-
cachedUsage:
|
|
1836
|
-
cachedUsageAt:
|
|
1837
|
-
consecutiveAuthFailures:
|
|
1838
|
-
isAuthDisabled:
|
|
1839
|
-
authDisabledReason:
|
|
2234
|
+
var StoredAccountSchema2 = v7.object({
|
|
2235
|
+
uuid: v7.optional(v7.string()),
|
|
2236
|
+
label: v7.optional(v7.string()),
|
|
2237
|
+
email: v7.optional(v7.string()),
|
|
2238
|
+
planTier: v7.optional(v7.string(), ""),
|
|
2239
|
+
refreshToken: v7.string(),
|
|
2240
|
+
accessToken: v7.optional(v7.string()),
|
|
2241
|
+
expiresAt: v7.optional(v7.number()),
|
|
2242
|
+
addedAt: v7.number(),
|
|
2243
|
+
lastUsed: v7.number(),
|
|
2244
|
+
enabled: v7.optional(v7.boolean(), true),
|
|
2245
|
+
rateLimitResetAt: v7.optional(v7.number()),
|
|
2246
|
+
cachedUsage: v7.optional(UsageLimitsSchema2),
|
|
2247
|
+
cachedUsageAt: v7.optional(v7.number()),
|
|
2248
|
+
consecutiveAuthFailures: v7.optional(v7.number(), 0),
|
|
2249
|
+
isAuthDisabled: v7.optional(v7.boolean(), false),
|
|
2250
|
+
authDisabledReason: v7.optional(v7.string())
|
|
1840
2251
|
});
|
|
1841
|
-
var AccountStorageSchema2 =
|
|
1842
|
-
version:
|
|
1843
|
-
accounts:
|
|
1844
|
-
activeAccountUuid:
|
|
2252
|
+
var AccountStorageSchema2 = v7.object({
|
|
2253
|
+
version: v7.literal(1),
|
|
2254
|
+
accounts: v7.optional(v7.array(StoredAccountSchema2), []),
|
|
2255
|
+
activeAccountUuid: v7.optional(v7.string())
|
|
1845
2256
|
});
|
|
1846
|
-
var TokenResponseSchema =
|
|
1847
|
-
access_token:
|
|
1848
|
-
refresh_token:
|
|
1849
|
-
expires_in:
|
|
1850
|
-
account:
|
|
1851
|
-
uuid:
|
|
1852
|
-
email_address:
|
|
2257
|
+
var TokenResponseSchema = v7.object({
|
|
2258
|
+
access_token: v7.string(),
|
|
2259
|
+
refresh_token: v7.optional(v7.string()),
|
|
2260
|
+
expires_in: v7.number(),
|
|
2261
|
+
account: v7.optional(v7.object({
|
|
2262
|
+
uuid: v7.optional(v7.string()),
|
|
2263
|
+
email_address: v7.optional(v7.string())
|
|
1853
2264
|
}))
|
|
1854
2265
|
});
|
|
1855
|
-
var AccountSelectionStrategySchema2 =
|
|
1856
|
-
var PluginConfigSchema2 =
|
|
2266
|
+
var AccountSelectionStrategySchema2 = v7.picklist(["sticky", "round-robin", "hybrid"]);
|
|
2267
|
+
var PluginConfigSchema2 = v7.object({
|
|
1857
2268
|
/** sticky: same account until failure, round-robin: rotate every request, hybrid: health+usage scoring */
|
|
1858
|
-
account_selection_strategy:
|
|
2269
|
+
account_selection_strategy: v7.optional(AccountSelectionStrategySchema2, "sticky"),
|
|
1859
2270
|
/** Use cross-process claim file to distribute parallel sessions across accounts */
|
|
1860
|
-
cross_process_claims:
|
|
2271
|
+
cross_process_claims: v7.optional(v7.boolean(), true),
|
|
1861
2272
|
/** Skip account when any usage tier utilization >= this % (100 = disabled) */
|
|
1862
|
-
soft_quota_threshold_percent:
|
|
2273
|
+
soft_quota_threshold_percent: v7.optional(v7.pipe(v7.number(), v7.minValue(0), v7.maxValue(100)), 100),
|
|
1863
2274
|
/** Minimum backoff after rate limit (ms) */
|
|
1864
|
-
rate_limit_min_backoff_ms:
|
|
2275
|
+
rate_limit_min_backoff_ms: v7.optional(v7.pipe(v7.number(), v7.minValue(0)), 3e4),
|
|
1865
2276
|
/** Default retry-after when header is missing (ms) */
|
|
1866
|
-
default_retry_after_ms:
|
|
2277
|
+
default_retry_after_ms: v7.optional(v7.pipe(v7.number(), v7.minValue(0)), 6e4),
|
|
1867
2278
|
/** Consecutive auth failures before disabling account */
|
|
1868
|
-
max_consecutive_auth_failures:
|
|
2279
|
+
max_consecutive_auth_failures: v7.optional(v7.pipe(v7.number(), v7.integer(), v7.minValue(1)), 3),
|
|
1869
2280
|
/** Backoff after token refresh failure (ms) */
|
|
1870
|
-
token_failure_backoff_ms:
|
|
2281
|
+
token_failure_backoff_ms: v7.optional(v7.pipe(v7.number(), v7.minValue(0)), 3e4),
|
|
1871
2282
|
/** Enable proactive background token refresh */
|
|
1872
|
-
proactive_refresh:
|
|
2283
|
+
proactive_refresh: v7.optional(v7.boolean(), true),
|
|
1873
2284
|
/** Seconds before expiry to trigger proactive refresh (default 30 min) */
|
|
1874
|
-
proactive_refresh_buffer_seconds:
|
|
2285
|
+
proactive_refresh_buffer_seconds: v7.optional(v7.pipe(v7.number(), v7.minValue(60)), 1800),
|
|
1875
2286
|
/** Interval between background refresh checks in seconds (default 5 min) */
|
|
1876
|
-
proactive_refresh_interval_seconds:
|
|
2287
|
+
proactive_refresh_interval_seconds: v7.optional(v7.pipe(v7.number(), v7.minValue(30)), 300),
|
|
1877
2288
|
/** Suppress toast notifications */
|
|
1878
|
-
quiet_mode:
|
|
2289
|
+
quiet_mode: v7.optional(v7.boolean(), false),
|
|
1879
2290
|
/** Enable debug logging */
|
|
1880
|
-
debug:
|
|
2291
|
+
debug: v7.optional(v7.boolean(), false)
|
|
1881
2292
|
});
|
|
1882
2293
|
|
|
1883
|
-
// src/
|
|
1884
|
-
var
|
|
1885
|
-
var
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
service: ANTHROPIC_OAUTH_ADAPTER.serviceLogName,
|
|
1914
|
-
level: isPermanent ? "error" : "warn",
|
|
1915
|
-
message: `Token refresh failed: ${response.status}${isPermanent ? " (permanent)" : ""}`,
|
|
1916
|
-
extra: { accountId }
|
|
1917
|
-
}
|
|
1918
|
-
}).catch(() => {
|
|
1919
|
-
});
|
|
1920
|
-
return { ok: false, permanent: isPermanent };
|
|
1921
|
-
}
|
|
1922
|
-
const json = v6.parse(TokenResponseSchema, await response.json());
|
|
1923
|
-
const patch = {
|
|
1924
|
-
accessToken: json.access_token,
|
|
1925
|
-
expiresAt: startTime + json.expires_in * 1e3,
|
|
1926
|
-
refreshToken: json.refresh_token,
|
|
1927
|
-
uuid: json.account?.uuid,
|
|
1928
|
-
email: json.account?.email_address
|
|
1929
|
-
};
|
|
1930
|
-
return { ok: true, patch };
|
|
1931
|
-
} catch (error) {
|
|
1932
|
-
await client.app.log({
|
|
1933
|
-
body: {
|
|
1934
|
-
service: ANTHROPIC_OAUTH_ADAPTER.serviceLogName,
|
|
1935
|
-
level: "warn",
|
|
1936
|
-
message: `Token refresh network error: ${error instanceof Error ? error.message : String(error)}`,
|
|
1937
|
-
extra: { accountId }
|
|
1938
|
-
}
|
|
1939
|
-
}).catch(() => {
|
|
1940
|
-
});
|
|
1941
|
-
return { ok: false, permanent: false };
|
|
1942
|
-
} finally {
|
|
1943
|
-
clearTimeout(timeout);
|
|
1944
|
-
refreshMutexByAccountId.delete(accountId);
|
|
1945
|
-
}
|
|
1946
|
-
})();
|
|
1947
|
-
refreshMutexByAccountId.set(accountId, refreshPromise);
|
|
1948
|
-
return refreshPromise;
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
// src/account-manager.ts
|
|
1952
|
-
var AccountManager = createAccountManagerForProvider({
|
|
1953
|
-
providerAuthId: "anthropic",
|
|
1954
|
-
isTokenExpired,
|
|
1955
|
-
refreshToken
|
|
1956
|
-
});
|
|
1957
|
-
|
|
1958
|
-
// src/config.ts
|
|
1959
|
-
initCoreConfig("claude-multiauth.json");
|
|
1960
|
-
|
|
1961
|
-
// src/utils.ts
|
|
1962
|
-
setConfigGetter(getConfig);
|
|
1963
|
-
|
|
1964
|
-
// src/usage.ts
|
|
1965
|
-
import * as v7 from "valibot";
|
|
1966
|
-
var OAUTH_BETA_HEADER = ANTHROPIC_OAUTH_ADAPTER.oauthBetaHeader;
|
|
1967
|
-
var ProfileResponseSchema = v7.object({
|
|
1968
|
-
account: v7.object({
|
|
1969
|
-
email: v7.optional(v7.string()),
|
|
1970
|
-
has_claude_pro: v7.optional(v7.boolean(), false),
|
|
1971
|
-
has_claude_max: v7.optional(v7.boolean(), false)
|
|
1972
|
-
})
|
|
1973
|
-
});
|
|
1974
|
-
async function fetchUsage(accessToken) {
|
|
1975
|
-
try {
|
|
1976
|
-
const response = await fetch(ANTHROPIC_USAGE_ENDPOINT, {
|
|
1977
|
-
method: "GET",
|
|
1978
|
-
headers: {
|
|
1979
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1980
|
-
"anthropic-beta": OAUTH_BETA_HEADER
|
|
1981
|
-
}
|
|
1982
|
-
});
|
|
1983
|
-
if (!response.ok) {
|
|
1984
|
-
return { ok: false, reason: `HTTP ${response.status} ${response.statusText}` };
|
|
1985
|
-
}
|
|
1986
|
-
const result = v7.safeParse(UsageLimitsSchema2, await response.json());
|
|
1987
|
-
if (!result.success) {
|
|
1988
|
-
return { ok: false, reason: `Invalid response: ${result.issues[0]?.message ?? "unknown"}` };
|
|
1989
|
-
}
|
|
1990
|
-
return { ok: true, data: result.output };
|
|
1991
|
-
} catch (error) {
|
|
1992
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1993
|
-
return { ok: false, reason: message };
|
|
1994
|
-
}
|
|
2294
|
+
// src/usage.ts
|
|
2295
|
+
var OAUTH_BETA_HEADER = ANTHROPIC_OAUTH_ADAPTER.oauthBetaHeader;
|
|
2296
|
+
var ProfileResponseSchema = v8.object({
|
|
2297
|
+
account: v8.object({
|
|
2298
|
+
email: v8.optional(v8.string()),
|
|
2299
|
+
has_claude_pro: v8.optional(v8.boolean(), false),
|
|
2300
|
+
has_claude_max: v8.optional(v8.boolean(), false)
|
|
2301
|
+
})
|
|
2302
|
+
});
|
|
2303
|
+
async function fetchUsage(accessToken) {
|
|
2304
|
+
try {
|
|
2305
|
+
const response = await fetch(ANTHROPIC_USAGE_ENDPOINT, {
|
|
2306
|
+
method: "GET",
|
|
2307
|
+
headers: {
|
|
2308
|
+
Authorization: `Bearer ${accessToken}`,
|
|
2309
|
+
"anthropic-beta": OAUTH_BETA_HEADER
|
|
2310
|
+
}
|
|
2311
|
+
});
|
|
2312
|
+
if (!response.ok) {
|
|
2313
|
+
return { ok: false, reason: `HTTP ${response.status} ${response.statusText}` };
|
|
2314
|
+
}
|
|
2315
|
+
const result = v8.safeParse(UsageLimitsSchema2, await response.json());
|
|
2316
|
+
if (!result.success) {
|
|
2317
|
+
return { ok: false, reason: `Invalid response: ${result.issues[0]?.message ?? "unknown"}` };
|
|
2318
|
+
}
|
|
2319
|
+
return { ok: true, data: result.output };
|
|
2320
|
+
} catch (error) {
|
|
2321
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2322
|
+
return { ok: false, reason: message };
|
|
2323
|
+
}
|
|
1995
2324
|
}
|
|
1996
2325
|
async function fetchProfile(accessToken) {
|
|
1997
2326
|
try {
|
|
@@ -2005,7 +2334,7 @@ async function fetchProfile(accessToken) {
|
|
|
2005
2334
|
if (!response.ok) {
|
|
2006
2335
|
return { ok: false, reason: `HTTP ${response.status} ${response.statusText}` };
|
|
2007
2336
|
}
|
|
2008
|
-
const result =
|
|
2337
|
+
const result = v8.safeParse(ProfileResponseSchema, await response.json());
|
|
2009
2338
|
if (!result.success) {
|
|
2010
2339
|
return { ok: false, reason: `Invalid response: ${result.issues[0]?.message ?? "unknown"}` };
|
|
2011
2340
|
}
|
|
@@ -2027,11 +2356,11 @@ function getUsageSummary(account) {
|
|
|
2027
2356
|
const parts = [];
|
|
2028
2357
|
const { five_hour, seven_day } = account.cachedUsage;
|
|
2029
2358
|
if (five_hour) {
|
|
2030
|
-
const reset = five_hour.
|
|
2359
|
+
const reset = five_hour.resets_at ? ` (resets ${formatTimeRemaining(five_hour.resets_at)})` : "";
|
|
2031
2360
|
parts.push(`5h: ${five_hour.utilization.toFixed(0)}%${reset}`);
|
|
2032
2361
|
}
|
|
2033
2362
|
if (seven_day) {
|
|
2034
|
-
const reset = seven_day.
|
|
2363
|
+
const reset = seven_day.resets_at ? ` (resets ${formatTimeRemaining(seven_day.resets_at)})` : "";
|
|
2035
2364
|
parts.push(`7d: ${seven_day.utilization.toFixed(0)}%${reset}`);
|
|
2036
2365
|
}
|
|
2037
2366
|
return parts.length > 0 ? parts.join(", ") : "no data";
|
|
@@ -2041,6 +2370,90 @@ function getPlanLabel(account) {
|
|
|
2041
2370
|
return PLAN_LABELS[account.planTier] ?? account.planTier.charAt(0).toUpperCase() + account.planTier.slice(1);
|
|
2042
2371
|
}
|
|
2043
2372
|
|
|
2373
|
+
// src/pi-ai-adapter.ts
|
|
2374
|
+
function fromPiAiCredentials(creds) {
|
|
2375
|
+
return {
|
|
2376
|
+
accessToken: creds.access,
|
|
2377
|
+
refreshToken: creds.refresh,
|
|
2378
|
+
expiresAt: creds.expires
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
async function loginWithPiAi(callbacks) {
|
|
2382
|
+
const piCreds = await loginAnthropic({
|
|
2383
|
+
onAuth: callbacks.onAuth,
|
|
2384
|
+
onPrompt: callbacks.onPrompt,
|
|
2385
|
+
onProgress: callbacks.onProgress,
|
|
2386
|
+
onManualCodeInput: callbacks.onManualCodeInput
|
|
2387
|
+
});
|
|
2388
|
+
const base = fromPiAiCredentials(piCreds);
|
|
2389
|
+
let profileResult = await fetchProfile(piCreds.access);
|
|
2390
|
+
if (!profileResult.ok) {
|
|
2391
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
2392
|
+
profileResult = await fetchProfile(piCreds.access);
|
|
2393
|
+
}
|
|
2394
|
+
const profileData = profileResult.ok ? profileResult.data : void 0;
|
|
2395
|
+
return {
|
|
2396
|
+
...base,
|
|
2397
|
+
email: profileData?.email,
|
|
2398
|
+
planTier: profileData?.planTier ?? "",
|
|
2399
|
+
addedAt: Date.now(),
|
|
2400
|
+
lastUsed: Date.now()
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
async function refreshWithPiAi(currentRefreshToken) {
|
|
2404
|
+
const piCreds = await refreshAnthropicToken(currentRefreshToken);
|
|
2405
|
+
return {
|
|
2406
|
+
accessToken: piCreds.access,
|
|
2407
|
+
refreshToken: piCreds.refresh,
|
|
2408
|
+
expiresAt: piCreds.expires
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
var PI_AI_ADAPTER_SERVICE = ANTHROPIC_OAUTH_ADAPTER.serviceLogName;
|
|
2412
|
+
|
|
2413
|
+
// src/token.ts
|
|
2414
|
+
var PERMANENT_FAILURE_HTTP_STATUSES = /* @__PURE__ */ new Set([400, 401, 403]);
|
|
2415
|
+
var refreshMutexByAccountId = /* @__PURE__ */ new Map();
|
|
2416
|
+
function isTokenExpired(account) {
|
|
2417
|
+
if (!account.accessToken || !account.expiresAt) return true;
|
|
2418
|
+
return account.expiresAt <= Date.now() + TOKEN_EXPIRY_BUFFER_MS;
|
|
2419
|
+
}
|
|
2420
|
+
async function refreshToken(currentRefreshToken, accountId, client) {
|
|
2421
|
+
if (!currentRefreshToken) return { ok: false, permanent: true };
|
|
2422
|
+
const inFlightRefresh = refreshMutexByAccountId.get(accountId);
|
|
2423
|
+
if (inFlightRefresh) return inFlightRefresh;
|
|
2424
|
+
const refreshPromise = (async () => {
|
|
2425
|
+
try {
|
|
2426
|
+
const patch = await refreshWithPiAi(currentRefreshToken);
|
|
2427
|
+
return { ok: true, patch };
|
|
2428
|
+
} catch (error) {
|
|
2429
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2430
|
+
const statusMatch = message.match(/\b(400|401|403)\b/);
|
|
2431
|
+
const isPermanent = statusMatch !== null && PERMANENT_FAILURE_HTTP_STATUSES.has(Number(statusMatch[1]));
|
|
2432
|
+
await client.app.log({
|
|
2433
|
+
body: {
|
|
2434
|
+
service: ANTHROPIC_OAUTH_ADAPTER.serviceLogName,
|
|
2435
|
+
level: isPermanent ? "error" : "warn",
|
|
2436
|
+
message: `Token refresh failed: ${message}${isPermanent ? " (permanent)" : ""}`,
|
|
2437
|
+
extra: { accountId }
|
|
2438
|
+
}
|
|
2439
|
+
}).catch(() => {
|
|
2440
|
+
});
|
|
2441
|
+
return { ok: false, permanent: isPermanent };
|
|
2442
|
+
} finally {
|
|
2443
|
+
refreshMutexByAccountId.delete(accountId);
|
|
2444
|
+
}
|
|
2445
|
+
})();
|
|
2446
|
+
refreshMutexByAccountId.set(accountId, refreshPromise);
|
|
2447
|
+
return refreshPromise;
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
// src/account-manager.ts
|
|
2451
|
+
var AccountManager = createAccountManagerForProvider({
|
|
2452
|
+
providerAuthId: "anthropic",
|
|
2453
|
+
isTokenExpired,
|
|
2454
|
+
refreshToken
|
|
2455
|
+
});
|
|
2456
|
+
|
|
2044
2457
|
// src/rate-limit.ts
|
|
2045
2458
|
var {
|
|
2046
2459
|
fetchUsageLimits,
|
|
@@ -2055,8 +2468,92 @@ var {
|
|
|
2055
2468
|
showToast
|
|
2056
2469
|
});
|
|
2057
2470
|
|
|
2471
|
+
// src/pool-chain-executor.ts
|
|
2472
|
+
function buildCascadePrompt(input, init) {
|
|
2473
|
+
if (typeof init?.body === "string" && init.body.length > 0) {
|
|
2474
|
+
return init.body;
|
|
2475
|
+
}
|
|
2476
|
+
const method = init?.method ?? "GET";
|
|
2477
|
+
return `${method}:${String(input)}`;
|
|
2478
|
+
}
|
|
2479
|
+
function createQueueAwareManager(manager, queue, cascadeStateManager) {
|
|
2480
|
+
return Object.create(manager, {
|
|
2481
|
+
selectAccount: { value: async function selectAccount() {
|
|
2482
|
+
await manager.refresh();
|
|
2483
|
+
while (queue.length > 0) {
|
|
2484
|
+
const next = queue.shift();
|
|
2485
|
+
if (!next) break;
|
|
2486
|
+
const account = manager.getAccounts().find((candidate) => candidate.uuid === next.accountUuid);
|
|
2487
|
+
if (!account?.uuid) continue;
|
|
2488
|
+
if (!account.enabled || account.isAuthDisabled) continue;
|
|
2489
|
+
if (manager.isRateLimited(account)) continue;
|
|
2490
|
+
cascadeStateManager.markAttempted(account.uuid);
|
|
2491
|
+
if (next.chainIndex !== void 0) {
|
|
2492
|
+
cascadeStateManager.markVisitedChainIndex(next.chainIndex);
|
|
2493
|
+
}
|
|
2494
|
+
return account;
|
|
2495
|
+
}
|
|
2496
|
+
return manager.selectAccount();
|
|
2497
|
+
} }
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2500
|
+
async function executeWithPoolChainRotation(manager, runtimeFactory, poolManager, cascadeStateManager, poolChainConfig, client, input, init) {
|
|
2501
|
+
const cascadePrompt = buildCascadePrompt(input, init);
|
|
2502
|
+
const currentAccountUuid = manager.getActiveAccount()?.uuid;
|
|
2503
|
+
cascadeStateManager.startTurn(cascadePrompt, currentAccountUuid);
|
|
2504
|
+
const queue = [];
|
|
2505
|
+
const queuedAccountUuids = /* @__PURE__ */ new Set();
|
|
2506
|
+
const queueAwareManager = createQueueAwareManager(manager, queue, cascadeStateManager);
|
|
2507
|
+
const { executeWithAccountRotation: executeWithAccountRotation2 } = createExecutorForProvider("Anthropic", {
|
|
2508
|
+
handleRateLimitResponse: async (rawManager, rawClient, account, response) => {
|
|
2509
|
+
await handleRateLimitResponse(
|
|
2510
|
+
rawManager,
|
|
2511
|
+
rawClient,
|
|
2512
|
+
account,
|
|
2513
|
+
response
|
|
2514
|
+
);
|
|
2515
|
+
if (!account.uuid) return;
|
|
2516
|
+
poolManager.markExhausted(account.uuid);
|
|
2517
|
+
cascadeStateManager.markAttempted(account.uuid);
|
|
2518
|
+
const cascadeState = cascadeStateManager.ensureCascadeState(cascadePrompt, account.uuid);
|
|
2519
|
+
const failoverPlan = await poolManager.buildFailoverPlan(
|
|
2520
|
+
account,
|
|
2521
|
+
poolChainConfig,
|
|
2522
|
+
manager,
|
|
2523
|
+
{
|
|
2524
|
+
attemptedAccounts: cascadeState.attemptedAccounts,
|
|
2525
|
+
visitedChainIndexes: cascadeState.visitedChainIndexes
|
|
2526
|
+
}
|
|
2527
|
+
);
|
|
2528
|
+
for (const candidate of failoverPlan.candidates) {
|
|
2529
|
+
if (queuedAccountUuids.has(candidate.accountUuid)) continue;
|
|
2530
|
+
queue.push({
|
|
2531
|
+
accountUuid: candidate.accountUuid,
|
|
2532
|
+
chainIndex: candidate.chainIndex
|
|
2533
|
+
});
|
|
2534
|
+
queuedAccountUuids.add(candidate.accountUuid);
|
|
2535
|
+
}
|
|
2536
|
+
},
|
|
2537
|
+
formatWaitTime,
|
|
2538
|
+
sleep,
|
|
2539
|
+
showToast,
|
|
2540
|
+
getAccountLabel
|
|
2541
|
+
});
|
|
2542
|
+
try {
|
|
2543
|
+
return await executeWithAccountRotation2(
|
|
2544
|
+
queueAwareManager,
|
|
2545
|
+
runtimeFactory,
|
|
2546
|
+
client,
|
|
2547
|
+
input,
|
|
2548
|
+
init
|
|
2549
|
+
);
|
|
2550
|
+
} finally {
|
|
2551
|
+
cascadeStateManager.clearCascadeState();
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2058
2555
|
// src/executor.ts
|
|
2059
|
-
var { executeWithAccountRotation } = createExecutorForProvider("Anthropic", {
|
|
2556
|
+
var { executeWithAccountRotation: executeWithCoreAccountRotation } = createExecutorForProvider("Anthropic", {
|
|
2060
2557
|
handleRateLimitResponse: async (manager, client, account, response) => handleRateLimitResponse(
|
|
2061
2558
|
manager,
|
|
2062
2559
|
client,
|
|
@@ -2068,6 +2565,44 @@ var { executeWithAccountRotation } = createExecutorForProvider("Anthropic", {
|
|
|
2068
2565
|
showToast,
|
|
2069
2566
|
getAccountLabel
|
|
2070
2567
|
});
|
|
2568
|
+
function isAllAccountsTerminalError(error) {
|
|
2569
|
+
if (!(error instanceof Error)) return false;
|
|
2570
|
+
return error.message.includes("All Anthropic accounts");
|
|
2571
|
+
}
|
|
2572
|
+
async function clearAuthIfNoUsableAccount(manager, client) {
|
|
2573
|
+
await manager.refresh();
|
|
2574
|
+
if (manager.hasAnyUsableAccount()) return;
|
|
2575
|
+
await client.auth.set({
|
|
2576
|
+
path: { id: ANTHROPIC_OAUTH_ADAPTER.authProviderId },
|
|
2577
|
+
body: getClearedOAuthBody()
|
|
2578
|
+
}).catch(() => {
|
|
2579
|
+
});
|
|
2580
|
+
}
|
|
2581
|
+
function hasPoolChainEntries(config) {
|
|
2582
|
+
return (config.pools?.length ?? 0) > 0 || (config.chains?.length ?? 0) > 0;
|
|
2583
|
+
}
|
|
2584
|
+
async function executeWithAccountRotation(manager, runtimeFactory, client, input, init, options) {
|
|
2585
|
+
try {
|
|
2586
|
+
if (!options || !hasPoolChainEntries(options.poolChainConfig)) {
|
|
2587
|
+
return await executeWithCoreAccountRotation(manager, runtimeFactory, client, input, init);
|
|
2588
|
+
}
|
|
2589
|
+
return await executeWithPoolChainRotation(
|
|
2590
|
+
manager,
|
|
2591
|
+
runtimeFactory,
|
|
2592
|
+
options.poolManager,
|
|
2593
|
+
options.cascadeStateManager,
|
|
2594
|
+
options.poolChainConfig,
|
|
2595
|
+
client,
|
|
2596
|
+
input,
|
|
2597
|
+
init
|
|
2598
|
+
);
|
|
2599
|
+
} catch (error) {
|
|
2600
|
+
if (isAllAccountsTerminalError(error)) {
|
|
2601
|
+
await clearAuthIfNoUsableAccount(manager, client);
|
|
2602
|
+
}
|
|
2603
|
+
throw error;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2071
2606
|
|
|
2072
2607
|
// src/ui/auth-menu.ts
|
|
2073
2608
|
function formatRelativeTime(timestamp) {
|
|
@@ -2288,23 +2823,77 @@ function makeFailedFlowResult(message) {
|
|
|
2288
2823
|
callback: async () => ({ type: "failed" })
|
|
2289
2824
|
};
|
|
2290
2825
|
}
|
|
2291
|
-
function
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2826
|
+
function toOAuthCredentials(result) {
|
|
2827
|
+
return { type: "oauth", refresh: result.refresh, access: result.access, expires: result.expires };
|
|
2828
|
+
}
|
|
2829
|
+
function asOAuthCallbackResponse(account) {
|
|
2830
|
+
if (!account.refreshToken || !account.accessToken || typeof account.expiresAt !== "number") {
|
|
2831
|
+
return { type: "failed" };
|
|
2295
2832
|
}
|
|
2296
|
-
return
|
|
2297
|
-
|
|
2298
|
-
|
|
2833
|
+
return {
|
|
2834
|
+
type: "success",
|
|
2835
|
+
refresh: account.refreshToken,
|
|
2836
|
+
access: account.accessToken,
|
|
2837
|
+
expires: account.expiresAt
|
|
2838
|
+
};
|
|
2299
2839
|
}
|
|
2300
|
-
function
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2840
|
+
function promptLine(message) {
|
|
2841
|
+
if (!isTTY()) return Promise.resolve("");
|
|
2842
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
2843
|
+
return new Promise((resolve) => {
|
|
2844
|
+
rl.question(message, (answer) => {
|
|
2845
|
+
rl.close();
|
|
2846
|
+
resolve(answer.trim());
|
|
2847
|
+
});
|
|
2848
|
+
});
|
|
2849
|
+
}
|
|
2850
|
+
function normalizePromptMessage(prompt) {
|
|
2851
|
+
return prompt.message ?? prompt.text ?? prompt.label ?? prompt.title ?? prompt.placeholder ?? "Continue authentication: ";
|
|
2852
|
+
}
|
|
2853
|
+
var ANTHROPIC_TOKEN_HOST = "platform.claude.com";
|
|
2854
|
+
async function startPiAiFlow() {
|
|
2855
|
+
const originalFetch = globalThis.fetch;
|
|
2856
|
+
globalThis.fetch = ((input, init) => {
|
|
2857
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
2858
|
+
if (url.includes(ANTHROPIC_TOKEN_HOST)) {
|
|
2859
|
+
const headers = new Headers(init?.headers);
|
|
2860
|
+
headers.set("user-agent", CLAUDE_CLI_USER_AGENT);
|
|
2861
|
+
return originalFetch(input, { ...init, headers });
|
|
2862
|
+
}
|
|
2863
|
+
return originalFetch(input, init);
|
|
2864
|
+
});
|
|
2865
|
+
try {
|
|
2866
|
+
const completedAccount = await loginWithPiAi({
|
|
2867
|
+
onAuth: (info) => {
|
|
2868
|
+
if (info.url) {
|
|
2869
|
+
openBrowser(info.url);
|
|
2870
|
+
}
|
|
2871
|
+
const instruction = info.instructions ?? "Complete authorization in your browser.";
|
|
2872
|
+
const urlLine = info.url ? `
|
|
2873
|
+
Auth URL (manual fallback): ${info.url}` : "";
|
|
2874
|
+
console.log(`
|
|
2875
|
+
${instruction}${urlLine}
|
|
2876
|
+
`);
|
|
2877
|
+
},
|
|
2878
|
+
onPrompt: async (prompt) => {
|
|
2879
|
+
const text = normalizePromptMessage(prompt);
|
|
2880
|
+
return promptLine(text.endsWith(":") || text.endsWith("?") ? `${text} ` : `${text}: `);
|
|
2881
|
+
}
|
|
2882
|
+
});
|
|
2883
|
+
const completedResult = asOAuthCallbackResponse(completedAccount);
|
|
2884
|
+
const accountEmail = completedAccount.email;
|
|
2885
|
+
return {
|
|
2886
|
+
url: "",
|
|
2887
|
+
instructions: "",
|
|
2888
|
+
method: "auto",
|
|
2889
|
+
callback: async () => completedResult,
|
|
2890
|
+
_email: accountEmail
|
|
2891
|
+
};
|
|
2892
|
+
} catch {
|
|
2893
|
+
return makeFailedFlowResult("Failed to start OAuth flow");
|
|
2894
|
+
} finally {
|
|
2895
|
+
globalThis.fetch = originalFetch;
|
|
2304
2896
|
}
|
|
2305
|
-
return originalMethod.authorize(inputs).then(
|
|
2306
|
-
(result) => wrapCallbackWithAccountReplace(result, manager, targetAccount)
|
|
2307
|
-
);
|
|
2308
2897
|
}
|
|
2309
2898
|
function wrapCallbackWithAccountReplace(result, manager, targetAccount) {
|
|
2310
2899
|
const originalCallback = result.callback;
|
|
@@ -2313,126 +2902,53 @@ function wrapCallbackWithAccountReplace(result, manager, targetAccount) {
|
|
|
2313
2902
|
callback: async function(code) {
|
|
2314
2903
|
const callbackResult = await originalCallback(code);
|
|
2315
2904
|
if (callbackResult?.type === "success" && callbackResult.refresh) {
|
|
2316
|
-
const auth = {
|
|
2317
|
-
type: "oauth",
|
|
2318
|
-
refresh: callbackResult.refresh,
|
|
2319
|
-
access: callbackResult.access,
|
|
2320
|
-
expires: callbackResult.expires
|
|
2321
|
-
};
|
|
2322
2905
|
if (targetAccount.uuid) {
|
|
2323
|
-
await manager.replaceAccountCredentials(targetAccount.uuid,
|
|
2906
|
+
await manager.replaceAccountCredentials(targetAccount.uuid, toOAuthCredentials(callbackResult));
|
|
2324
2907
|
}
|
|
2325
|
-
const label = getAccountLabel(targetAccount);
|
|
2326
2908
|
console.log(`
|
|
2327
|
-
\u2705 ${
|
|
2909
|
+
\u2705 ${getAccountLabel(targetAccount)} re-authenticated successfully.
|
|
2328
2910
|
`);
|
|
2329
2911
|
}
|
|
2330
2912
|
return callbackResult;
|
|
2331
2913
|
}
|
|
2332
2914
|
};
|
|
2333
2915
|
}
|
|
2334
|
-
function wrapCallbackWithManagerSync(result, manager
|
|
2916
|
+
function wrapCallbackWithManagerSync(result, manager) {
|
|
2335
2917
|
const originalCallback = result.callback;
|
|
2918
|
+
const email = result._email;
|
|
2336
2919
|
return {
|
|
2337
2920
|
...result,
|
|
2338
2921
|
callback: async function(code) {
|
|
2339
2922
|
const callbackResult = await originalCallback(code);
|
|
2340
2923
|
if (callbackResult?.type === "success" && callbackResult.refresh) {
|
|
2341
|
-
const auth =
|
|
2342
|
-
type: "oauth",
|
|
2343
|
-
refresh: callbackResult.refresh,
|
|
2344
|
-
access: callbackResult.access,
|
|
2345
|
-
expires: callbackResult.expires
|
|
2346
|
-
};
|
|
2924
|
+
const auth = toOAuthCredentials(callbackResult);
|
|
2347
2925
|
if (manager) {
|
|
2348
2926
|
const countBefore = manager.getAccounts().length;
|
|
2349
|
-
await manager.addAccount(auth);
|
|
2927
|
+
await manager.addAccount(auth, email);
|
|
2350
2928
|
const countAfter = manager.getAccounts().length;
|
|
2351
|
-
|
|
2352
|
-
|
|
2929
|
+
const added = countAfter > countBefore;
|
|
2930
|
+
console.log(added ? `
|
|
2353
2931
|
\u2705 Account added to multi-auth pool (${countAfter} total).
|
|
2354
|
-
`
|
|
2355
|
-
} else {
|
|
2356
|
-
console.log(`
|
|
2932
|
+
` : `
|
|
2357
2933
|
\u2139\uFE0F Account already exists in multi-auth pool (${countAfter} total).
|
|
2358
2934
|
`);
|
|
2359
|
-
}
|
|
2360
|
-
if (originalAuth && inputs && isTTY()) {
|
|
2361
|
-
await addMoreAccountsLoop(manager, originalAuth, inputs);
|
|
2362
|
-
}
|
|
2363
2935
|
} else {
|
|
2364
2936
|
await persistFallback(auth);
|
|
2365
|
-
console.log(
|
|
2366
|
-
\u2705 Account saved.
|
|
2367
|
-
`);
|
|
2937
|
+
console.log("\n\u2705 Account saved.\n");
|
|
2368
2938
|
}
|
|
2369
2939
|
}
|
|
2370
2940
|
return callbackResult;
|
|
2371
2941
|
}
|
|
2372
2942
|
};
|
|
2373
2943
|
}
|
|
2374
|
-
function promptYesNo(message) {
|
|
2375
|
-
if (!isTTY()) return Promise.resolve(false);
|
|
2376
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
2377
|
-
return new Promise((resolve) => {
|
|
2378
|
-
rl.question(message, (answer) => {
|
|
2379
|
-
rl.close();
|
|
2380
|
-
resolve(answer.trim().toLowerCase() === "y");
|
|
2381
|
-
});
|
|
2382
|
-
});
|
|
2383
|
-
}
|
|
2384
2944
|
function openBrowser(url) {
|
|
2385
|
-
const
|
|
2945
|
+
const commands = {
|
|
2946
|
+
darwin: "open",
|
|
2947
|
+
win32: "start"
|
|
2948
|
+
};
|
|
2949
|
+
const cmd = commands[process.platform] ?? "xdg-open";
|
|
2386
2950
|
exec(`${cmd} ${JSON.stringify(url)}`);
|
|
2387
2951
|
}
|
|
2388
|
-
async function addMoreAccountsLoop(manager, originalAuth, inputs) {
|
|
2389
|
-
const originalMethod = originalAuth.methods?.[0];
|
|
2390
|
-
if (!originalMethod?.authorize) return;
|
|
2391
|
-
while (true) {
|
|
2392
|
-
const currentCount = manager.getAccounts().length;
|
|
2393
|
-
const shouldAdd = await promptYesNo(`Add another account? (${currentCount} added) (y/n): `);
|
|
2394
|
-
if (!shouldAdd) break;
|
|
2395
|
-
let flow;
|
|
2396
|
-
try {
|
|
2397
|
-
flow = await originalMethod.authorize(inputs);
|
|
2398
|
-
} catch {
|
|
2399
|
-
console.log("\n\u274C Failed to start OAuth flow.\n");
|
|
2400
|
-
break;
|
|
2401
|
-
}
|
|
2402
|
-
if (flow.url) {
|
|
2403
|
-
openBrowser(flow.url);
|
|
2404
|
-
}
|
|
2405
|
-
let callbackResult;
|
|
2406
|
-
try {
|
|
2407
|
-
callbackResult = await flow.callback();
|
|
2408
|
-
} catch {
|
|
2409
|
-
console.log("\n\u274C Authentication failed.\n");
|
|
2410
|
-
break;
|
|
2411
|
-
}
|
|
2412
|
-
if (callbackResult?.type !== "success" || !("refresh" in callbackResult)) {
|
|
2413
|
-
console.log("\n\u274C Authentication failed.\n");
|
|
2414
|
-
break;
|
|
2415
|
-
}
|
|
2416
|
-
const auth = {
|
|
2417
|
-
type: "oauth",
|
|
2418
|
-
refresh: callbackResult.refresh,
|
|
2419
|
-
access: callbackResult.access,
|
|
2420
|
-
expires: callbackResult.expires
|
|
2421
|
-
};
|
|
2422
|
-
const countBefore = manager.getAccounts().length;
|
|
2423
|
-
await manager.addAccount(auth);
|
|
2424
|
-
const countAfter = manager.getAccounts().length;
|
|
2425
|
-
if (countAfter > countBefore) {
|
|
2426
|
-
console.log(`
|
|
2427
|
-
\u2705 Account added to multi-auth pool (${countAfter} total).
|
|
2428
|
-
`);
|
|
2429
|
-
} else {
|
|
2430
|
-
console.log(`
|
|
2431
|
-
\u2139\uFE0F Account already exists in multi-auth pool (${countAfter} total).
|
|
2432
|
-
`);
|
|
2433
|
-
}
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
2952
|
async function persistFallback(auth) {
|
|
2437
2953
|
try {
|
|
2438
2954
|
const store = new AccountStore();
|
|
@@ -2454,15 +2970,15 @@ async function persistFallback(auth) {
|
|
|
2454
2970
|
} catch {
|
|
2455
2971
|
}
|
|
2456
2972
|
}
|
|
2457
|
-
async function handleAuthorize(
|
|
2973
|
+
async function handleAuthorize(manager, inputs, client) {
|
|
2458
2974
|
if (!inputs || !isTTY()) {
|
|
2459
|
-
return
|
|
2975
|
+
return wrapCallbackWithManagerSync(await startPiAiFlow(), manager);
|
|
2460
2976
|
}
|
|
2461
2977
|
const effectiveManager = manager ?? await loadManagerFromDisk(client);
|
|
2462
2978
|
if (!effectiveManager || effectiveManager.getAccounts().length === 0) {
|
|
2463
|
-
return
|
|
2979
|
+
return wrapCallbackWithManagerSync(await startPiAiFlow(), manager);
|
|
2464
2980
|
}
|
|
2465
|
-
return runAccountManagementMenu(
|
|
2981
|
+
return runAccountManagementMenu(effectiveManager, client);
|
|
2466
2982
|
}
|
|
2467
2983
|
async function loadManagerFromDisk(client) {
|
|
2468
2984
|
const store = new AccountStore();
|
|
@@ -2472,13 +2988,13 @@ async function loadManagerFromDisk(client) {
|
|
|
2472
2988
|
const mgr = await AccountManager.create(store, emptyAuth, client);
|
|
2473
2989
|
return mgr;
|
|
2474
2990
|
}
|
|
2475
|
-
async function runAccountManagementMenu(
|
|
2991
|
+
async function runAccountManagementMenu(manager, client) {
|
|
2476
2992
|
while (true) {
|
|
2477
2993
|
const allAccounts = manager.getAccounts();
|
|
2478
2994
|
const menuAction = await showAuthMenu(allAccounts);
|
|
2479
2995
|
switch (menuAction.type) {
|
|
2480
2996
|
case "add":
|
|
2481
|
-
return
|
|
2997
|
+
return wrapCallbackWithManagerSync(await startPiAiFlow(), manager);
|
|
2482
2998
|
case "check-quotas":
|
|
2483
2999
|
await handleCheckQuotas(manager, client);
|
|
2484
3000
|
continue;
|
|
@@ -2487,7 +3003,7 @@ async function runAccountManagementMenu(originalAuth, manager, inputs, client) {
|
|
|
2487
3003
|
if (result.action === "back" || result.action === "cancel") continue;
|
|
2488
3004
|
const manageResult = await handleManageAction(manager, result.action, result.account, client);
|
|
2489
3005
|
if (manageResult.triggerOAuth) {
|
|
2490
|
-
return
|
|
3006
|
+
return wrapCallbackWithAccountReplace(await startPiAiFlow(), manager, manageResult.account);
|
|
2491
3007
|
}
|
|
2492
3008
|
continue;
|
|
2493
3009
|
}
|
|
@@ -2497,7 +3013,7 @@ async function runAccountManagementMenu(originalAuth, manager, inputs, client) {
|
|
|
2497
3013
|
case "delete-all":
|
|
2498
3014
|
await manager.clearAllAccounts();
|
|
2499
3015
|
console.log("\nAll accounts deleted.\n");
|
|
2500
|
-
return
|
|
3016
|
+
return wrapCallbackWithManagerSync(await startPiAiFlow(), manager);
|
|
2501
3017
|
case "cancel":
|
|
2502
3018
|
return makeFailedFlowResult("Authentication cancelled");
|
|
2503
3019
|
}
|
|
@@ -2512,46 +3028,49 @@ async function handleCheckQuotas(manager, client) {
|
|
|
2512
3028
|
\u{1F4CA} Checking quotas for ${accounts.length} account(s)...
|
|
2513
3029
|
`);
|
|
2514
3030
|
for (const account of accounts) {
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
continue;
|
|
2524
|
-
}
|
|
2525
|
-
await manager.refresh();
|
|
2526
|
-
}
|
|
2527
|
-
const freshAccounts = manager.getAccounts();
|
|
2528
|
-
const freshAccount = freshAccounts.find((candidate) => candidate.uuid === account.uuid);
|
|
2529
|
-
if (!freshAccount?.accessToken) {
|
|
2530
|
-
printQuotaError(account, "No access token available");
|
|
2531
|
-
continue;
|
|
3031
|
+
await checkAccountQuota(manager, account, effectiveClient);
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
async function checkAccountQuota(manager, account, client) {
|
|
3035
|
+
if (account.isAuthDisabled || !account.accessToken || isTokenExpired(account)) {
|
|
3036
|
+
if (!account.uuid) {
|
|
3037
|
+
printQuotaError(account, "Missing account UUID");
|
|
3038
|
+
return;
|
|
2532
3039
|
}
|
|
2533
|
-
const
|
|
2534
|
-
if (!
|
|
2535
|
-
printQuotaError(
|
|
2536
|
-
|
|
3040
|
+
const refreshResult = await manager.ensureValidToken(account.uuid, client);
|
|
3041
|
+
if (!refreshResult.ok) {
|
|
3042
|
+
printQuotaError(account, account.isAuthDisabled ? `${account.authDisabledReason ?? "Auth disabled"} (refresh failed)` : "Failed to refresh token");
|
|
3043
|
+
return;
|
|
2537
3044
|
}
|
|
3045
|
+
await manager.refresh();
|
|
3046
|
+
}
|
|
3047
|
+
const freshAccounts = manager.getAccounts();
|
|
3048
|
+
const freshAccount = freshAccounts.find((candidate) => candidate.uuid === account.uuid);
|
|
3049
|
+
if (!freshAccount?.accessToken) {
|
|
3050
|
+
printQuotaError(account, "No access token available");
|
|
3051
|
+
return;
|
|
3052
|
+
}
|
|
3053
|
+
const usageResult = await fetchUsage(freshAccount.accessToken);
|
|
3054
|
+
if (!usageResult.ok) {
|
|
3055
|
+
printQuotaError(freshAccount, `Failed to fetch usage: ${usageResult.reason}`);
|
|
3056
|
+
return;
|
|
3057
|
+
}
|
|
3058
|
+
if (freshAccount.uuid) {
|
|
3059
|
+
await manager.applyUsageCache(freshAccount.uuid, usageResult.data);
|
|
3060
|
+
}
|
|
3061
|
+
let reportAccount = freshAccount;
|
|
3062
|
+
const profileResult = await fetchProfile(freshAccount.accessToken);
|
|
3063
|
+
if (profileResult.ok) {
|
|
2538
3064
|
if (freshAccount.uuid) {
|
|
2539
|
-
await manager.
|
|
2540
|
-
}
|
|
2541
|
-
let reportAccount = freshAccount;
|
|
2542
|
-
const profileResult = await fetchProfile(freshAccount.accessToken);
|
|
2543
|
-
if (profileResult.ok) {
|
|
2544
|
-
if (freshAccount.uuid) {
|
|
2545
|
-
await manager.applyProfileCache(freshAccount.uuid, profileResult.data);
|
|
2546
|
-
}
|
|
2547
|
-
reportAccount = {
|
|
2548
|
-
...freshAccount,
|
|
2549
|
-
email: profileResult.data.email ?? freshAccount.email,
|
|
2550
|
-
planTier: profileResult.data.planTier
|
|
2551
|
-
};
|
|
3065
|
+
await manager.applyProfileCache(freshAccount.uuid, profileResult.data);
|
|
2552
3066
|
}
|
|
2553
|
-
|
|
3067
|
+
reportAccount = {
|
|
3068
|
+
...freshAccount,
|
|
3069
|
+
email: profileResult.data.email ?? freshAccount.email,
|
|
3070
|
+
planTier: profileResult.data.planTier
|
|
3071
|
+
};
|
|
2554
3072
|
}
|
|
3073
|
+
printQuotaReport(reportAccount, usageResult.data);
|
|
2555
3074
|
}
|
|
2556
3075
|
async function handleLoadBalancing() {
|
|
2557
3076
|
const current = getConfig().account_selection_strategy;
|
|
@@ -2595,8 +3114,7 @@ Retrying authentication for ${label}...
|
|
|
2595
3114
|
console.log(`\u2705 ${label} re-authenticated successfully.
|
|
2596
3115
|
`);
|
|
2597
3116
|
} else {
|
|
2598
|
-
console.log(
|
|
2599
|
-
`);
|
|
3117
|
+
console.log("Token refresh failed \u2014 starting OAuth flow...\n");
|
|
2600
3118
|
return { triggerOAuth: true, account };
|
|
2601
3119
|
}
|
|
2602
3120
|
break;
|
|
@@ -2605,8 +3123,362 @@ Retrying authentication for ${label}...
|
|
|
2605
3123
|
return { triggerOAuth: false };
|
|
2606
3124
|
}
|
|
2607
3125
|
|
|
3126
|
+
// src/request-transform.ts
|
|
3127
|
+
import { createHash } from "node:crypto";
|
|
3128
|
+
|
|
3129
|
+
// src/anthropic-prompt.ts
|
|
3130
|
+
var SYSTEM_PROMPT = `You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
|
|
3131
|
+
|
|
3132
|
+
IMPORTANT: Assist with defensive security tasks only. Refuse to create, modify, or improve code that may be used maliciously. Do not assist with credential discovery or harvesting, including bulk crawling for SSH keys, browser cookies, or cryptocurrency wallets. Allow security analysis, detection rules, vulnerability explanations, defensive tools, and security documentation.
|
|
3133
|
+
IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.
|
|
3134
|
+
|
|
3135
|
+
If the user asks for help or wants to give feedback inform them of the following:
|
|
3136
|
+
- /help: Get help with using Claude Code
|
|
3137
|
+
- To give feedback, users should report the issue at https://github.com/anthropics/claude-code/issues
|
|
3138
|
+
|
|
3139
|
+
When the user directly asks about Claude Code (eg. "can Claude Code do...", "does Claude Code have..."), or asks in second person (eg. "are you able...", "can you do..."), or asks how to use a specific Claude Code feature (eg. implement a hook, or write a slash command), use the WebFetch tool to gather information to answer the question from Claude Code docs. The list of available docs is available at https://docs.claude.com/en/docs/claude-code/claude_code_docs_map.md.
|
|
3140
|
+
|
|
3141
|
+
# Tone and style
|
|
3142
|
+
You should be concise, direct, and to the point, while providing complete information and matching the level of detail you provide in your response with the level of complexity of the user's query or the work you have completed.
|
|
3143
|
+
A concise response is generally less than 4 lines, not including tool calls or code generated. You should provide more detail when the task is complex or when the user asks you to.
|
|
3144
|
+
IMPORTANT: You should minimize output tokens as much as possible while maintaining helpfulness, quality, and accuracy. Only address the specific task at hand, avoiding tangential information unless absolutely critical for completing the request. If you can answer in 1-3 sentences or a short paragraph, please do.
|
|
3145
|
+
IMPORTANT: You should NOT answer with unnecessary preamble or postamble (such as explaining your code or summarizing your action), unless the user asks you to.
|
|
3146
|
+
Do not add additional code explanation summary unless requested by the user. After working on a file, briefly confirm that you have completed the task, rather than providing an explanation of what you did.
|
|
3147
|
+
Answer the user's question directly, avoiding any elaboration, explanation, introduction, conclusion, or excessive details. Brief answers are best, but be sure to provide complete information. You MUST avoid extra preamble before/after your response, such as "The answer is <answer>.", "Here is the content of the file..." or "Based on the information provided, the answer is..." or "Here is what I will do next...".
|
|
3148
|
+
|
|
3149
|
+
Here are some examples to demonstrate appropriate verbosity:
|
|
3150
|
+
<example>
|
|
3151
|
+
user: 2 + 2
|
|
3152
|
+
assistant: 4
|
|
3153
|
+
</example>
|
|
3154
|
+
|
|
3155
|
+
<example>
|
|
3156
|
+
user: what is 2+2?
|
|
3157
|
+
assistant: 4
|
|
3158
|
+
</example>
|
|
3159
|
+
|
|
3160
|
+
<example>
|
|
3161
|
+
user: is 11 a prime number?
|
|
3162
|
+
assistant: Yes
|
|
3163
|
+
</example>
|
|
3164
|
+
|
|
3165
|
+
<example>
|
|
3166
|
+
user: what command should I run to list files in the current directory?
|
|
3167
|
+
assistant: ls
|
|
3168
|
+
</example>
|
|
3169
|
+
|
|
3170
|
+
<example>
|
|
3171
|
+
user: what command should I run to watch files in the current directory?
|
|
3172
|
+
assistant: [runs ls to list the files in the current directory, then read docs/commands in the relevant file to find out how to watch files]
|
|
3173
|
+
npm run dev
|
|
3174
|
+
</example>
|
|
3175
|
+
|
|
3176
|
+
<example>
|
|
3177
|
+
user: How many golf balls fit inside a jetta?
|
|
3178
|
+
assistant: 150000
|
|
3179
|
+
</example>
|
|
3180
|
+
|
|
3181
|
+
<example>
|
|
3182
|
+
user: what files are in the directory src/?
|
|
3183
|
+
assistant: [runs ls and sees foo.c, bar.c, baz.c]
|
|
3184
|
+
user: which file contains the implementation of foo?
|
|
3185
|
+
assistant: src/foo.c
|
|
3186
|
+
</example>
|
|
3187
|
+
When you run a non-trivial bash command, you should explain what the command does and why you are running it, to make sure the user understands what you are doing (this is especially important when you are running a command that will make changes to the user's system).
|
|
3188
|
+
Remember that your output will be displayed on a command line interface. Your responses can use GitHub-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification.
|
|
3189
|
+
Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session.
|
|
3190
|
+
If you cannot or will not help the user with something, please do not say why or what it could lead to, since this comes across as preachy and annoying. Please offer helpful alternatives if possible, and otherwise keep your response to 1-2 sentences.
|
|
3191
|
+
Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.
|
|
3192
|
+
IMPORTANT: Keep your responses short, since they will be displayed on a command line interface.
|
|
3193
|
+
|
|
3194
|
+
# Proactiveness
|
|
3195
|
+
You are allowed to be proactive, but only when the user asks you to do something. You should strive to strike a balance between:
|
|
3196
|
+
- Doing the right thing when asked, including taking actions and follow-up actions
|
|
3197
|
+
- Not surprising the user with actions you take without asking
|
|
3198
|
+
For example, if the user asks you how to approach something, you should do your best to answer their question first, and not immediately jump into taking actions.
|
|
3199
|
+
|
|
3200
|
+
# Professional objectivity
|
|
3201
|
+
Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if Claude honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs.
|
|
3202
|
+
|
|
3203
|
+
# Task Management
|
|
3204
|
+
You have access to the TodoWrite tools to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress.
|
|
3205
|
+
These tools are also EXTREMELY helpful for planning tasks, and for breaking down larger complex tasks into smaller steps. If you do not use this tool when planning, you may forget to do important tasks - and that is unacceptable.
|
|
3206
|
+
|
|
3207
|
+
It is critical that you mark todos as completed as soon as you are done with a task. Do not batch up multiple tasks before marking them as completed.
|
|
3208
|
+
|
|
3209
|
+
Examples:
|
|
3210
|
+
|
|
3211
|
+
<example>
|
|
3212
|
+
user: Run the build and fix any type errors
|
|
3213
|
+
assistant: I'm going to use the TodoWrite tool to write the following items to the todo list:
|
|
3214
|
+
- Run the build
|
|
3215
|
+
- Fix any type errors
|
|
3216
|
+
|
|
3217
|
+
I'm now going to run the build using Bash.
|
|
3218
|
+
|
|
3219
|
+
Looks like I found 10 type errors. I'm going to use the TodoWrite tool to write 10 items to the todo list.
|
|
3220
|
+
|
|
3221
|
+
marking the first todo as in_progress
|
|
3222
|
+
|
|
3223
|
+
Let me start working on the first item...
|
|
3224
|
+
|
|
3225
|
+
The first item has been fixed, let me mark the first todo as completed, and move on to the second item...
|
|
3226
|
+
..
|
|
3227
|
+
..
|
|
3228
|
+
</example>
|
|
3229
|
+
In the above example, the assistant completes all the tasks, including the 10 error fixes and running the build and fixing all errors.
|
|
3230
|
+
|
|
3231
|
+
<example>
|
|
3232
|
+
user: Help me write a new feature that allows users to track their usage metrics and export them to various formats
|
|
3233
|
+
|
|
3234
|
+
assistant: I'll help you implement a usage metrics tracking and export feature. Let me first use the TodoWrite tool to plan this task.
|
|
3235
|
+
Adding the following todos to the todo list:
|
|
3236
|
+
1. Research existing metrics tracking in the codebase
|
|
3237
|
+
2. Design the metrics collection system
|
|
3238
|
+
3. Implement core metrics tracking functionality
|
|
3239
|
+
4. Create export functionality for different formats
|
|
3240
|
+
|
|
3241
|
+
Let me start by researching the existing codebase to understand what metrics we might already be tracking and how we can build on that.
|
|
3242
|
+
|
|
3243
|
+
I'm going to search for any existing metrics or telemetry code in the project.
|
|
3244
|
+
|
|
3245
|
+
I've found some existing telemetry code. Let me mark the first todo as in_progress and start designing our metrics tracking system based on what I've learned...
|
|
3246
|
+
|
|
3247
|
+
[Assistant continues implementing the feature step by step, marking todos as in_progress and completed as they go]
|
|
3248
|
+
</example>
|
|
3249
|
+
|
|
3250
|
+
|
|
3251
|
+
Users may configure 'hooks', shell commands that execute in response to events like tool calls, in settings. Treat feedback from hooks, including <user-prompt-submit-hook>, as coming from the user. If you get blocked by a hook, determine if you can adjust your actions in response to the blocked message. If not, ask the user to check their hooks configuration.
|
|
3252
|
+
|
|
3253
|
+
# Doing tasks
|
|
3254
|
+
The user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:
|
|
3255
|
+
- Use the TodoWrite tool to plan the task if required
|
|
3256
|
+
|
|
3257
|
+
- Tool results and user messages may include <system-reminder> tags. <system-reminder> tags contain useful information and reminders. They are automatically added by the system, and bear no direct relation to the specific tool results or user messages in which they appear.
|
|
3258
|
+
|
|
3259
|
+
|
|
3260
|
+
# Tool usage policy
|
|
3261
|
+
- When doing file search, prefer to use the Task tool in order to reduce context usage.
|
|
3262
|
+
- You should proactively use the Task tool with specialized agents when the task at hand matches the agent's description.
|
|
3263
|
+
|
|
3264
|
+
- When WebFetch returns a message about a redirect to a different host, you should immediately make a new WebFetch request with the redirect URL provided in the response.
|
|
3265
|
+
- You have the capability to call multiple tools in a single response. When multiple independent pieces of information are requested, batch your tool calls together for optimal performance. When making multiple bash tool calls, you MUST send a single message with multiple tools calls to run the calls in parallel. For example, if you need to run "git status" and "git diff", send a single message with two tool calls to run the calls in parallel.
|
|
3266
|
+
- If the user specifies that they want you to run tools "in parallel", you MUST send a single message with multiple tool use content blocks. For example, if you need to launch multiple agents in parallel, send a single message with multiple Task tool calls.
|
|
3267
|
+
- Use specialized tools instead of bash commands when possible, as this provides a better user experience. For file operations, use dedicated tools: Read for reading files instead of cat/head/tail, Edit for editing instead of sed/awk, and Write for creating files instead of cat with heredoc or echo redirection. Reserve bash tools exclusively for actual system commands and terminal operations that require shell execution. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.
|
|
3268
|
+
|
|
3269
|
+
|
|
3270
|
+
Here is useful information about the environment you are running in:
|
|
3271
|
+
<env>
|
|
3272
|
+
Working directory: /home/thdxr/dev/projects/anomalyco/opencode/packages/opencode
|
|
3273
|
+
Is directory a git repo: Yes
|
|
3274
|
+
Platform: linux
|
|
3275
|
+
OS Version: Linux 6.12.4-arch1-1
|
|
3276
|
+
Today's date: 2025-09-30
|
|
3277
|
+
</env>
|
|
3278
|
+
You are powered by the model named Sonnet 4.5. The exact model ID is claude-sonnet-4-5-20250929.
|
|
3279
|
+
|
|
3280
|
+
Assistant knowledge cutoff is January 2025.
|
|
3281
|
+
|
|
3282
|
+
|
|
3283
|
+
IMPORTANT: Assist with defensive security tasks only. Refuse to create, modify, or improve code that may be used maliciously. Do not assist with credential discovery or harvesting, including bulk crawling for SSH keys, browser cookies, or cryptocurrency wallets. Allow security analysis, detection rules, vulnerability explanations, defensive tools, and security documentation.
|
|
3284
|
+
|
|
3285
|
+
|
|
3286
|
+
IMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.
|
|
3287
|
+
|
|
3288
|
+
# Code References
|
|
3289
|
+
|
|
3290
|
+
When referencing specific functions or pieces of code include the pattern \`file_path:line_number\` to allow the user to easily navigate to the source code location.
|
|
3291
|
+
|
|
3292
|
+
<example>
|
|
3293
|
+
user: Where are errors from the client handled?
|
|
3294
|
+
assistant: Clients are marked as failed in the \`connectToServer\` function in src/services/process.ts:712.
|
|
3295
|
+
</example>`;
|
|
3296
|
+
|
|
3297
|
+
// src/request-transform.ts
|
|
3298
|
+
function getSystemPrompt() {
|
|
3299
|
+
return SYSTEM_PROMPT;
|
|
3300
|
+
}
|
|
3301
|
+
function sampleCodeUnits(text, indices) {
|
|
3302
|
+
return indices.map((i) => i < text.length ? text.charCodeAt(i).toString(16) : "30").join("");
|
|
3303
|
+
}
|
|
3304
|
+
function buildBillingHeader(firstUserMessage) {
|
|
3305
|
+
const version = ANTHROPIC_OAUTH_ADAPTER.cliVersion;
|
|
3306
|
+
const salt = ANTHROPIC_OAUTH_ADAPTER.billingSalt;
|
|
3307
|
+
if (!version || !salt) return "";
|
|
3308
|
+
const sampled = sampleCodeUnits(firstUserMessage, [4, 7, 20]);
|
|
3309
|
+
const hash = createHash("sha256").update(`${salt}${sampled}${version}`).digest("hex").slice(0, 3);
|
|
3310
|
+
return `x-anthropic-billing-header: cc_version=${version}.${hash}; cc_entrypoint=cli; cch=00000;`;
|
|
3311
|
+
}
|
|
3312
|
+
var OPENCODE_CAMEL_RE = /OpenCode/g;
|
|
3313
|
+
var OPENCODE_LOWER_RE = /(?<!\/)opencode/gi;
|
|
3314
|
+
var TOOL_PREFIX_RESPONSE_RE = /"name"\s*:\s*"mcp_([^"]+)"/g;
|
|
3315
|
+
function addToolPrefix(name) {
|
|
3316
|
+
if (!ANTHROPIC_OAUTH_ADAPTER.transform.addToolPrefix) {
|
|
3317
|
+
return name;
|
|
3318
|
+
}
|
|
3319
|
+
if (!name || name.startsWith(TOOL_PREFIX)) {
|
|
3320
|
+
return name;
|
|
3321
|
+
}
|
|
3322
|
+
return `${TOOL_PREFIX}${name}`;
|
|
3323
|
+
}
|
|
3324
|
+
function stripToolPrefixFromLine(line) {
|
|
3325
|
+
if (!ANTHROPIC_OAUTH_ADAPTER.transform.stripToolPrefixInResponse) {
|
|
3326
|
+
return line;
|
|
3327
|
+
}
|
|
3328
|
+
return line.replace(TOOL_PREFIX_RESPONSE_RE, '"name": "$1"');
|
|
3329
|
+
}
|
|
3330
|
+
function processCompleteLines(buffer) {
|
|
3331
|
+
const lines = buffer.split("\n");
|
|
3332
|
+
const remaining = lines.pop() ?? "";
|
|
3333
|
+
if (lines.length === 0) {
|
|
3334
|
+
return { output: "", remaining };
|
|
3335
|
+
}
|
|
3336
|
+
const output = `${lines.map(stripToolPrefixFromLine).join("\n")}
|
|
3337
|
+
`;
|
|
3338
|
+
return { output, remaining };
|
|
3339
|
+
}
|
|
3340
|
+
function buildRequestHeaders(input, init, accessToken) {
|
|
3341
|
+
const headers = new Headers();
|
|
3342
|
+
if (input instanceof Request) {
|
|
3343
|
+
input.headers.forEach((value, key) => {
|
|
3344
|
+
headers.set(key, value);
|
|
3345
|
+
});
|
|
3346
|
+
}
|
|
3347
|
+
if (init?.headers) {
|
|
3348
|
+
if (init.headers instanceof Headers) {
|
|
3349
|
+
init.headers.forEach((value, key) => {
|
|
3350
|
+
headers.set(key, value);
|
|
3351
|
+
});
|
|
3352
|
+
} else if (Array.isArray(init.headers)) {
|
|
3353
|
+
for (const [key, value] of init.headers) {
|
|
3354
|
+
if (value !== void 0) headers.set(key, String(value));
|
|
3355
|
+
}
|
|
3356
|
+
} else {
|
|
3357
|
+
for (const [key, value] of Object.entries(init.headers)) {
|
|
3358
|
+
if (value !== void 0) headers.set(key, String(value));
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
const incomingBetas = (headers.get("anthropic-beta") || "").split(",").map((b) => b.trim()).filter(Boolean);
|
|
3363
|
+
const mergedBetas = [.../* @__PURE__ */ new Set([
|
|
3364
|
+
...ANTHROPIC_BETA_HEADER.split(","),
|
|
3365
|
+
...incomingBetas
|
|
3366
|
+
])].join(",");
|
|
3367
|
+
headers.set("authorization", `Bearer ${accessToken}`);
|
|
3368
|
+
headers.set("anthropic-beta", mergedBetas);
|
|
3369
|
+
headers.set("user-agent", CLAUDE_CLI_USER_AGENT);
|
|
3370
|
+
headers.set("anthropic-dangerous-direct-browser-access", "true");
|
|
3371
|
+
headers.set("x-app", "cli");
|
|
3372
|
+
headers.delete("x-api-key");
|
|
3373
|
+
return headers;
|
|
3374
|
+
}
|
|
3375
|
+
function transformRequestBody(body) {
|
|
3376
|
+
if (!body) return body;
|
|
3377
|
+
try {
|
|
3378
|
+
const parsed = JSON.parse(body);
|
|
3379
|
+
if (parsed.system && Array.isArray(parsed.system)) {
|
|
3380
|
+
parsed.system = parsed.system.map((systemEntry) => {
|
|
3381
|
+
if (ANTHROPIC_OAUTH_ADAPTER.transform.rewriteOpenCodeBranding && systemEntry.type === "text" && systemEntry.text) {
|
|
3382
|
+
return {
|
|
3383
|
+
...systemEntry,
|
|
3384
|
+
text: systemEntry.text.replace(OPENCODE_CAMEL_RE, "Claude Code").replace(OPENCODE_LOWER_RE, "Claude")
|
|
3385
|
+
};
|
|
3386
|
+
}
|
|
3387
|
+
return systemEntry;
|
|
3388
|
+
});
|
|
3389
|
+
}
|
|
3390
|
+
if (parsed.tools && Array.isArray(parsed.tools)) {
|
|
3391
|
+
parsed.tools = parsed.tools.map((tool2) => ({
|
|
3392
|
+
...tool2,
|
|
3393
|
+
name: addToolPrefix(tool2.name)
|
|
3394
|
+
}));
|
|
3395
|
+
}
|
|
3396
|
+
if (parsed.messages && Array.isArray(parsed.messages)) {
|
|
3397
|
+
parsed.messages = parsed.messages.map((message) => {
|
|
3398
|
+
if (message.content && Array.isArray(message.content)) {
|
|
3399
|
+
message.content = message.content.map((contentBlock) => {
|
|
3400
|
+
if (contentBlock.type === "tool_use" && contentBlock.name) {
|
|
3401
|
+
return { ...contentBlock, name: addToolPrefix(contentBlock.name) };
|
|
3402
|
+
}
|
|
3403
|
+
return contentBlock;
|
|
3404
|
+
});
|
|
3405
|
+
}
|
|
3406
|
+
return message;
|
|
3407
|
+
});
|
|
3408
|
+
}
|
|
3409
|
+
return JSON.stringify(parsed);
|
|
3410
|
+
} catch {
|
|
3411
|
+
return body;
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
function transformRequestUrl(input) {
|
|
3415
|
+
let url = null;
|
|
3416
|
+
try {
|
|
3417
|
+
if (typeof input === "string" || input instanceof URL) {
|
|
3418
|
+
url = new URL(input.toString());
|
|
3419
|
+
} else if (input instanceof Request) {
|
|
3420
|
+
url = new URL(input.url);
|
|
3421
|
+
}
|
|
3422
|
+
} catch {
|
|
3423
|
+
return input;
|
|
3424
|
+
}
|
|
3425
|
+
if (ANTHROPIC_OAUTH_ADAPTER.transform.enableMessagesBetaQuery && url && url.pathname === "/v1/messages" && !url.searchParams.has("beta")) {
|
|
3426
|
+
url.searchParams.set("beta", "true");
|
|
3427
|
+
return input instanceof Request ? new Request(url.toString(), input) : url;
|
|
3428
|
+
}
|
|
3429
|
+
return input;
|
|
3430
|
+
}
|
|
3431
|
+
function createResponseStreamTransform(response) {
|
|
3432
|
+
if (!response.body) return response;
|
|
3433
|
+
const reader = response.body.getReader();
|
|
3434
|
+
const decoder = new TextDecoder();
|
|
3435
|
+
const encoder = new TextEncoder();
|
|
3436
|
+
let buffer = "";
|
|
3437
|
+
const stream = new ReadableStream({
|
|
3438
|
+
async pull(controller) {
|
|
3439
|
+
try {
|
|
3440
|
+
while (true) {
|
|
3441
|
+
const { done, value } = await reader.read();
|
|
3442
|
+
if (done) {
|
|
3443
|
+
buffer += decoder.decode();
|
|
3444
|
+
if (buffer) {
|
|
3445
|
+
controller.enqueue(encoder.encode(stripToolPrefixFromLine(buffer)));
|
|
3446
|
+
buffer = "";
|
|
3447
|
+
}
|
|
3448
|
+
controller.close();
|
|
3449
|
+
return;
|
|
3450
|
+
}
|
|
3451
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3452
|
+
const { output, remaining } = processCompleteLines(buffer);
|
|
3453
|
+
buffer = remaining;
|
|
3454
|
+
if (output) {
|
|
3455
|
+
controller.enqueue(encoder.encode(output));
|
|
3456
|
+
return;
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
} catch (error) {
|
|
3460
|
+
try {
|
|
3461
|
+
reader.cancel().catch(() => {
|
|
3462
|
+
});
|
|
3463
|
+
} catch {
|
|
3464
|
+
}
|
|
3465
|
+
controller.error(error);
|
|
3466
|
+
}
|
|
3467
|
+
},
|
|
3468
|
+
async cancel(reason) {
|
|
3469
|
+
await reader.cancel(reason);
|
|
3470
|
+
}
|
|
3471
|
+
});
|
|
3472
|
+
return new Response(stream, {
|
|
3473
|
+
status: response.status,
|
|
3474
|
+
statusText: response.statusText,
|
|
3475
|
+
headers: response.headers
|
|
3476
|
+
});
|
|
3477
|
+
}
|
|
3478
|
+
|
|
2608
3479
|
// src/proactive-refresh.ts
|
|
2609
3480
|
var ProactiveRefreshQueue = createProactiveRefreshQueueForProvider({
|
|
3481
|
+
providerAuthId: "anthropic",
|
|
2610
3482
|
getConfig,
|
|
2611
3483
|
isTokenExpired,
|
|
2612
3484
|
refreshToken,
|
|
@@ -2614,13 +3486,11 @@ var ProactiveRefreshQueue = createProactiveRefreshQueueForProvider({
|
|
|
2614
3486
|
});
|
|
2615
3487
|
|
|
2616
3488
|
// src/runtime-factory.ts
|
|
2617
|
-
|
|
3489
|
+
var TOKEN_REFRESH_PERMANENT_FAILURE_STATUS = 401;
|
|
2618
3490
|
var AccountRuntimeFactory = class {
|
|
2619
|
-
constructor(
|
|
2620
|
-
this.pluginCtx = pluginCtx;
|
|
3491
|
+
constructor(store, client) {
|
|
2621
3492
|
this.store = store;
|
|
2622
3493
|
this.client = client;
|
|
2623
|
-
this.provider = provider;
|
|
2624
3494
|
}
|
|
2625
3495
|
runtimes = /* @__PURE__ */ new Map();
|
|
2626
3496
|
initLocks = /* @__PURE__ */ new Map();
|
|
@@ -2645,77 +3515,116 @@ var AccountRuntimeFactory = class {
|
|
|
2645
3515
|
invalidateAll() {
|
|
2646
3516
|
this.runtimes.clear();
|
|
2647
3517
|
}
|
|
2648
|
-
async
|
|
2649
|
-
const
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
const auth = hooks.auth;
|
|
2656
|
-
if (!auth?.loader) {
|
|
2657
|
-
throw new Error(`Base plugin loader unavailable for account ${uuid}`);
|
|
2658
|
-
}
|
|
2659
|
-
const scopedGetAuth = this.createScopedGetAuth(uuid);
|
|
2660
|
-
const result = await auth.loader(scopedGetAuth, this.provider);
|
|
2661
|
-
if (!result?.fetch) {
|
|
2662
|
-
throw new Error(`Base plugin returned no fetch for account ${uuid}`);
|
|
3518
|
+
async ensureFreshToken(storedAccount, uuid) {
|
|
3519
|
+
const refreshed = await refreshToken(storedAccount.refreshToken, uuid, this.client);
|
|
3520
|
+
if (!refreshed.ok) {
|
|
3521
|
+
throw new TokenRefreshError(
|
|
3522
|
+
refreshed.permanent,
|
|
3523
|
+
refreshed.permanent ? TOKEN_REFRESH_PERMANENT_FAILURE_STATUS : void 0
|
|
3524
|
+
);
|
|
2663
3525
|
}
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
3526
|
+
await this.store.mutateAccount(uuid, (account) => {
|
|
3527
|
+
account.accessToken = refreshed.patch.accessToken;
|
|
3528
|
+
account.expiresAt = refreshed.patch.expiresAt;
|
|
3529
|
+
if (refreshed.patch.refreshToken) account.refreshToken = refreshed.patch.refreshToken;
|
|
3530
|
+
if (refreshed.patch.uuid) account.uuid = refreshed.patch.uuid;
|
|
3531
|
+
if (refreshed.patch.email) account.email = refreshed.patch.email;
|
|
3532
|
+
account.consecutiveAuthFailures = 0;
|
|
3533
|
+
account.isAuthDisabled = false;
|
|
3534
|
+
account.authDisabledReason = void 0;
|
|
3535
|
+
});
|
|
3536
|
+
this.client.auth.set({
|
|
3537
|
+
path: { id: ANTHROPIC_OAUTH_ADAPTER.authProviderId },
|
|
3538
|
+
body: {
|
|
2675
3539
|
type: "oauth",
|
|
2676
|
-
refresh:
|
|
2677
|
-
access:
|
|
2678
|
-
expires:
|
|
2679
|
-
}
|
|
2680
|
-
}
|
|
3540
|
+
refresh: refreshed.patch.refreshToken ?? storedAccount.refreshToken,
|
|
3541
|
+
access: refreshed.patch.accessToken,
|
|
3542
|
+
expires: refreshed.patch.expiresAt
|
|
3543
|
+
}
|
|
3544
|
+
}).catch(() => {
|
|
3545
|
+
});
|
|
3546
|
+
return { accessToken: refreshed.patch.accessToken, expiresAt: refreshed.patch.expiresAt };
|
|
3547
|
+
}
|
|
3548
|
+
async executeTransformedFetch(input, init, accessToken) {
|
|
3549
|
+
const transformedInput = transformRequestUrl(input);
|
|
3550
|
+
const headers = buildRequestHeaders(transformedInput, init, accessToken);
|
|
3551
|
+
const transformedBody = typeof init?.body === "string" ? transformRequestBody(init.body) : init?.body;
|
|
3552
|
+
const response = await fetch(transformedInput, {
|
|
3553
|
+
...init,
|
|
3554
|
+
headers,
|
|
3555
|
+
body: transformedBody
|
|
3556
|
+
});
|
|
3557
|
+
return createResponseStreamTransform(response);
|
|
2681
3558
|
}
|
|
2682
|
-
|
|
2683
|
-
const
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
});
|
|
2699
|
-
}
|
|
2700
|
-
},
|
|
2701
|
-
tui: originalClient.tui,
|
|
2702
|
-
app: originalClient.app
|
|
3559
|
+
async createRuntime(uuid) {
|
|
3560
|
+
const fetchWithAccount = async (input, init) => {
|
|
3561
|
+
const storage = await this.store.load();
|
|
3562
|
+
const storedAccount = storage.accounts.find((account) => account.uuid === uuid);
|
|
3563
|
+
if (!storedAccount) {
|
|
3564
|
+
throw new Error(`No credentials found for account ${uuid}`);
|
|
3565
|
+
}
|
|
3566
|
+
let accessToken = storedAccount.accessToken;
|
|
3567
|
+
let expiresAt = storedAccount.expiresAt;
|
|
3568
|
+
if (!accessToken || !expiresAt || isTokenExpired({ accessToken, expiresAt })) {
|
|
3569
|
+
({ accessToken, expiresAt } = await this.ensureFreshToken(storedAccount, uuid));
|
|
3570
|
+
}
|
|
3571
|
+
if (!accessToken) {
|
|
3572
|
+
throw new Error(`No access token available for account ${uuid}`);
|
|
3573
|
+
}
|
|
3574
|
+
return this.executeTransformedFetch(input, init, accessToken);
|
|
2703
3575
|
};
|
|
3576
|
+
debugLog(this.client, `Runtime created for account ${uuid.slice(0, 8)}`);
|
|
3577
|
+
return { fetch: fetchWithAccount };
|
|
2704
3578
|
}
|
|
2705
3579
|
};
|
|
2706
3580
|
|
|
2707
3581
|
// src/index.ts
|
|
3582
|
+
function extractFirstUserText(input) {
|
|
3583
|
+
try {
|
|
3584
|
+
const raw = input;
|
|
3585
|
+
const messages = raw.messages ?? raw.request?.messages;
|
|
3586
|
+
if (!Array.isArray(messages)) return "";
|
|
3587
|
+
for (const msg of messages) {
|
|
3588
|
+
if (msg.role !== "user") continue;
|
|
3589
|
+
if (typeof msg.content === "string") return msg.content;
|
|
3590
|
+
if (Array.isArray(msg.content)) {
|
|
3591
|
+
for (const block of msg.content) {
|
|
3592
|
+
if (block.type === "text" && block.text) return block.text;
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
} catch {
|
|
3597
|
+
}
|
|
3598
|
+
return "";
|
|
3599
|
+
}
|
|
3600
|
+
function injectSystemPrompt(output) {
|
|
3601
|
+
const systemPrompt = getSystemPrompt();
|
|
3602
|
+
if (!Array.isArray(output.system)) {
|
|
3603
|
+
output.system = [systemPrompt];
|
|
3604
|
+
return;
|
|
3605
|
+
}
|
|
3606
|
+
if (!output.system.includes(systemPrompt)) {
|
|
3607
|
+
output.system.unshift(systemPrompt);
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
2708
3610
|
var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
2709
3611
|
const { client } = ctx;
|
|
2710
3612
|
await loadConfig();
|
|
2711
|
-
const originalHooks = await AnthropicAuthPlugin2(ctx);
|
|
2712
|
-
const originalAuth = originalHooks.auth;
|
|
2713
3613
|
const store = new AccountStore();
|
|
2714
3614
|
let manager = null;
|
|
2715
3615
|
let runtimeFactory = null;
|
|
2716
3616
|
let refreshQueue = null;
|
|
3617
|
+
let poolManager = null;
|
|
3618
|
+
let cascadeStateManager = null;
|
|
3619
|
+
let poolChainConfig = { pools: [], chains: [] };
|
|
2717
3620
|
return {
|
|
2718
|
-
"experimental.chat.system.transform":
|
|
3621
|
+
"experimental.chat.system.transform": (input, output) => {
|
|
3622
|
+
injectSystemPrompt(output);
|
|
3623
|
+
const billingHeader = buildBillingHeader(extractFirstUserText(input));
|
|
3624
|
+
if (billingHeader && !output.system?.includes(billingHeader)) {
|
|
3625
|
+
output.system?.unshift(billingHeader);
|
|
3626
|
+
}
|
|
3627
|
+
},
|
|
2719
3628
|
tool: {
|
|
2720
3629
|
[ANTHROPIC_OAUTH_ADAPTER.statusToolName]: tool({
|
|
2721
3630
|
description: "Show status of all multi-auth accounts including rate limits and usage.",
|
|
@@ -2743,9 +3652,13 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
|
2743
3652
|
if (account.isAuthDisabled) statusParts.push(`AUTH DISABLED: ${account.authDisabledReason}`);
|
|
2744
3653
|
else if (!account.enabled) statusParts.push("disabled");
|
|
2745
3654
|
else statusParts.push("enabled");
|
|
2746
|
-
if (account.rateLimitResetAt
|
|
2747
|
-
|
|
2748
|
-
|
|
3655
|
+
if (account.rateLimitResetAt) {
|
|
3656
|
+
if (account.rateLimitResetAt > Date.now()) {
|
|
3657
|
+
const remaining = formatWaitTime(account.rateLimitResetAt - Date.now());
|
|
3658
|
+
statusParts.push(`RATE LIMITED (resets in ${remaining})`);
|
|
3659
|
+
} else {
|
|
3660
|
+
statusParts.push("RATE LIMIT RESET");
|
|
3661
|
+
}
|
|
2749
3662
|
}
|
|
2750
3663
|
if (account.cachedUsage) {
|
|
2751
3664
|
const now = Date.now();
|
|
@@ -2773,7 +3686,7 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
|
2773
3686
|
type: "oauth",
|
|
2774
3687
|
async authorize() {
|
|
2775
3688
|
const inputs = arguments.length > 0 ? arguments[0] : void 0;
|
|
2776
|
-
return handleAuthorize(
|
|
3689
|
+
return handleAuthorize(manager, inputs, client);
|
|
2777
3690
|
}
|
|
2778
3691
|
},
|
|
2779
3692
|
{ type: "api", label: "Create an API Key" },
|
|
@@ -2782,7 +3695,7 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
|
2782
3695
|
async loader(getAuth, provider) {
|
|
2783
3696
|
const auth = await getAuth();
|
|
2784
3697
|
if (auth.type !== "oauth") {
|
|
2785
|
-
return
|
|
3698
|
+
return { apiKey: "", fetch };
|
|
2786
3699
|
}
|
|
2787
3700
|
for (const model of Object.values(provider.models ?? {})) {
|
|
2788
3701
|
if (model) {
|
|
@@ -2792,8 +3705,12 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
|
2792
3705
|
const credentials = auth;
|
|
2793
3706
|
await migrateFromAuthJson("anthropic", store);
|
|
2794
3707
|
manager = await AccountManager.create(store, credentials, client);
|
|
2795
|
-
runtimeFactory = new AccountRuntimeFactory(
|
|
3708
|
+
runtimeFactory = new AccountRuntimeFactory(store, client);
|
|
2796
3709
|
manager.setRuntimeFactory(runtimeFactory);
|
|
3710
|
+
poolChainConfig = await loadPoolChainConfig();
|
|
3711
|
+
poolManager = new PoolManager();
|
|
3712
|
+
poolManager.loadPools(poolChainConfig.pools);
|
|
3713
|
+
cascadeStateManager = new CascadeStateManager();
|
|
2797
3714
|
if (manager.getAccountCount() > 0) {
|
|
2798
3715
|
const activeLabel = manager.getActiveAccount() ? getAccountLabel(manager.getActiveAccount()) : "none";
|
|
2799
3716
|
void showToast(
|
|
@@ -2816,12 +3733,21 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
|
2816
3733
|
refreshQueue = new ProactiveRefreshQueue(
|
|
2817
3734
|
client,
|
|
2818
3735
|
store,
|
|
2819
|
-
(uuid) =>
|
|
3736
|
+
(uuid) => {
|
|
3737
|
+
runtimeFactory?.invalidate(uuid);
|
|
3738
|
+
void manager?.refresh();
|
|
3739
|
+
}
|
|
2820
3740
|
);
|
|
2821
3741
|
refreshQueue.start();
|
|
2822
3742
|
}
|
|
2823
3743
|
return {
|
|
2824
3744
|
apiKey: "",
|
|
3745
|
+
"chat.headers": async (input, output) => {
|
|
3746
|
+
if (input.provider?.info?.id !== ANTHROPIC_OAUTH_ADAPTER.authProviderId) return;
|
|
3747
|
+
output.headers["user-agent"] = CLAUDE_CLI_USER_AGENT;
|
|
3748
|
+
output.headers["anthropic-beta"] = ANTHROPIC_BETA_HEADER;
|
|
3749
|
+
output.headers["x-app"] = "cli";
|
|
3750
|
+
},
|
|
2825
3751
|
async fetch(input, init) {
|
|
2826
3752
|
if (!manager || !runtimeFactory) {
|
|
2827
3753
|
return fetch(input, init);
|
|
@@ -2831,7 +3757,23 @@ var ClaudeMultiAuthPlugin = async (ctx) => {
|
|
|
2831
3757
|
"No Anthropic accounts configured. Run `opencode auth login` to add an account."
|
|
2832
3758
|
);
|
|
2833
3759
|
}
|
|
2834
|
-
|
|
3760
|
+
if (!poolManager || !cascadeStateManager) {
|
|
3761
|
+
poolManager = new PoolManager();
|
|
3762
|
+
poolManager.loadPools(poolChainConfig.pools);
|
|
3763
|
+
cascadeStateManager = new CascadeStateManager();
|
|
3764
|
+
}
|
|
3765
|
+
return executeWithAccountRotation(
|
|
3766
|
+
manager,
|
|
3767
|
+
runtimeFactory,
|
|
3768
|
+
client,
|
|
3769
|
+
input,
|
|
3770
|
+
init,
|
|
3771
|
+
{
|
|
3772
|
+
poolManager,
|
|
3773
|
+
cascadeStateManager,
|
|
3774
|
+
poolChainConfig
|
|
3775
|
+
}
|
|
3776
|
+
);
|
|
2835
3777
|
}
|
|
2836
3778
|
};
|
|
2837
3779
|
}
|