@eddyskywalker/dsh-chatgpt-subscription 0.10.10 → 0.10.12
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/CHANGELOG.md +31 -0
- package/README.md +10 -6
- package/lib/client.js +1 -1
- package/lib/client.js.map +1 -1
- package/lib/index.js +614 -356
- package/lib/types/client/claude/ClaudeSection.d.ts.map +1 -1
- package/lib/types/host/claude/account-pool.d.ts +4 -21
- package/lib/types/host/claude/account-pool.d.ts.map +1 -1
- package/lib/types/host/claude/adapter.d.ts.map +1 -1
- package/lib/types/host/claude/client.d.ts +6 -1
- package/lib/types/host/claude/client.d.ts.map +1 -1
- package/lib/types/host/claude/mapper.d.ts +33 -12
- package/lib/types/host/claude/mapper.d.ts.map +1 -1
- package/lib/types/host/claude/model-catalog.d.ts +19 -4
- package/lib/types/host/claude/model-catalog.d.ts.map +1 -1
- package/lib/types/host/claude/oauth.d.ts.map +1 -1
- package/lib/types/host/claude/types.d.ts +18 -1
- package/lib/types/host/claude/types.d.ts.map +1 -1
- package/lib/types/host/codex-account-pool.d.ts.map +1 -1
- package/lib/types/host/common/account-pool.d.ts +93 -6
- package/lib/types/host/common/account-pool.d.ts.map +1 -1
- package/lib/types/host/minimax-code/account-pool.d.ts +2 -2
- package/lib/types/host/minimax-code/account-pool.d.ts.map +1 -1
- package/lib/types/host/oauth-service.d.ts.map +1 -1
- package/lib/types/host/reasoning-collapse-guard/index.d.ts +26 -11
- package/lib/types/host/reasoning-collapse-guard/index.d.ts.map +1 -1
- package/lib/types/host/workbuddy/account-pool.d.ts +8 -8
- package/lib/types/host/workbuddy/account-pool.d.ts.map +1 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -905,6 +905,16 @@ function poolCredentialAccount$1(filePath) {
|
|
|
905
905
|
return createHash("sha256").update(path.resolve(filePath)).digest("hex");
|
|
906
906
|
}
|
|
907
907
|
const poolOperations = /* @__PURE__ */ new Map();
|
|
908
|
+
const poolIdentityRevisions = /* @__PURE__ */ new Map();
|
|
909
|
+
const poolRefreshes = /* @__PURE__ */ new Map();
|
|
910
|
+
/**
|
|
911
|
+
* A selected row was deleted, or became unusable, while a caller awaited it.
|
|
912
|
+
*
|
|
913
|
+
* Distinct from every other credential error because a MODEL REQUEST can still
|
|
914
|
+
* be served by another account: routing excludes the row and tries the next
|
|
915
|
+
* one, while a caller that named the account still gets the plain refusal.
|
|
916
|
+
*/
|
|
917
|
+
var PoolAccountUnavailableError = class extends LlmError {};
|
|
908
918
|
/**
|
|
909
919
|
* The shared multi-account pool: encrypted storage, eligibility filtering,
|
|
910
920
|
* rotation strategy, 429 cooldowns and per-account auth failures.
|
|
@@ -926,11 +936,14 @@ var AccountPoolCore = class {
|
|
|
926
936
|
* can therefore invalidate on identity change without paying a credential read
|
|
927
937
|
* to discover it.
|
|
928
938
|
*/
|
|
929
|
-
|
|
939
|
+
/** Pool-file identity of this instance: locks, revisions and flights key on it. */
|
|
940
|
+
operationKey;
|
|
930
941
|
constructor(hooks) {
|
|
931
942
|
this.hooks = hooks;
|
|
932
943
|
this.filePath = hooks.poolFile;
|
|
933
944
|
this.backend = hooks.backend ?? createPoolBackend(hooks);
|
|
945
|
+
const resolved = path.resolve(this.filePath);
|
|
946
|
+
this.operationKey = process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
934
947
|
}
|
|
935
948
|
/**
|
|
936
949
|
* A cheap, in-memory revision of the pool's serving identity.
|
|
@@ -946,15 +959,21 @@ var AccountPoolCore = class {
|
|
|
946
959
|
* would report one account's usage under another account's name.
|
|
947
960
|
*/
|
|
948
961
|
currentIdentityRevision() {
|
|
949
|
-
return this.
|
|
962
|
+
return poolIdentityRevisions.get(this.operationKey) ?? 0;
|
|
950
963
|
}
|
|
951
964
|
/** Human description of where this pool's credentials live. */
|
|
952
965
|
path() {
|
|
953
966
|
if (process.platform === "win32") return `${this.filePath}.dpapi`;
|
|
954
967
|
return `${process.platform === "darwin" ? "Keychain" : "Secret Service"}: ${this.hooks.keychainService}/${poolCredentialAccount$1(this.filePath)}`;
|
|
955
968
|
}
|
|
969
|
+
/**
|
|
970
|
+
* Queue one storage transaction for this pool file.
|
|
971
|
+
*
|
|
972
|
+
* NOT reentrant: a transaction must use {@link loadPoolData} and
|
|
973
|
+
* {@link updatePool} rather than calling the public read/write methods.
|
|
974
|
+
*/
|
|
956
975
|
serialize(operation) {
|
|
957
|
-
const key =
|
|
976
|
+
const key = this.operationKey;
|
|
958
977
|
const result = (poolOperations.get(key) || Promise.resolve()).then(operation);
|
|
959
978
|
const settled = result.then(() => void 0, () => void 0);
|
|
960
979
|
poolOperations.set(key, settled);
|
|
@@ -978,31 +997,74 @@ var AccountPoolCore = class {
|
|
|
978
997
|
* getter write to disk.
|
|
979
998
|
*/
|
|
980
999
|
read() {
|
|
981
|
-
return this.serialize(
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1000
|
+
return this.serialize(() => this.loadPoolData());
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Load the pool without holding the file lock.
|
|
1004
|
+
*
|
|
1005
|
+
* Providers override this to maintain their own in-memory indexes, so every
|
|
1006
|
+
* load — including one inside {@link updatePool} — refreshes them.
|
|
1007
|
+
*/
|
|
1008
|
+
async loadPoolData() {
|
|
1009
|
+
let current = null;
|
|
1010
|
+
try {
|
|
1011
|
+
current = await this.backend.load();
|
|
1012
|
+
} catch {}
|
|
1013
|
+
if (current !== null && Array.isArray(current.accounts) && current.accounts.length > 0) return current;
|
|
1014
|
+
try {
|
|
1015
|
+
const legacy = await this.hooks.legacyAccount?.();
|
|
1016
|
+
if (legacy) return {
|
|
997
1017
|
version: 1,
|
|
998
|
-
|
|
999
|
-
|
|
1018
|
+
activeAccountId: legacy.id,
|
|
1019
|
+
rotationStrategy: current?.rotationStrategy || "sequential",
|
|
1020
|
+
accounts: [legacy]
|
|
1000
1021
|
};
|
|
1001
|
-
}
|
|
1022
|
+
} catch {}
|
|
1023
|
+
return current || {
|
|
1024
|
+
version: 1,
|
|
1025
|
+
rotationStrategy: "sequential",
|
|
1026
|
+
accounts: []
|
|
1027
|
+
};
|
|
1002
1028
|
}
|
|
1029
|
+
async persistPoolData(data) {
|
|
1030
|
+
poolIdentityRevisions.set(this.operationKey, this.currentIdentityRevision() + 1);
|
|
1031
|
+
await this.saveVerified(data);
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Replace the whole document (initialization, import), NOT a read-modify-write.
|
|
1035
|
+
* Prefer {@link updatePool} for mutations so the draft is read under the lock.
|
|
1036
|
+
*/
|
|
1003
1037
|
write(data) {
|
|
1004
|
-
this.
|
|
1005
|
-
|
|
1038
|
+
return this.serialize(() => this.persistPoolData(data));
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Read the LATEST document, mutate it and persist it as one file transaction.
|
|
1042
|
+
*
|
|
1043
|
+
* This is the whole fix for the read-modify-write race: the old shape read a
|
|
1044
|
+
* snapshot, awaited (a token refresh, a settings write, an encrypted round
|
|
1045
|
+
* trip) and then wrote that whole stale snapshot back, erasing whatever landed
|
|
1046
|
+
* in between — including another account's freshly rotated token.
|
|
1047
|
+
*
|
|
1048
|
+
* The callback runs under the file lock, so it must not call read, write or
|
|
1049
|
+
* updatePool (the lock is not reentrant) and must not do network I/O.
|
|
1050
|
+
* Provider-local stores belong in `afterCommit`, which runs only once the
|
|
1051
|
+
* pool write is verified, and must not re-enter the pool either.
|
|
1052
|
+
*/
|
|
1053
|
+
updatePool(mutate, afterCommit) {
|
|
1054
|
+
return this.serialize(async () => {
|
|
1055
|
+
const data = await this.loadPoolData();
|
|
1056
|
+
const before = structuredClone(data);
|
|
1057
|
+
const result = await mutate(data);
|
|
1058
|
+
if (!isDeepStrictEqual(before, data)) {
|
|
1059
|
+
await this.persistPoolData(data);
|
|
1060
|
+
await afterCommit?.(data, result).catch(() => void 0);
|
|
1061
|
+
for (const account of data.accounts) if (!isDeepStrictEqual(before.accounts.find((entry) => entry.id === account.id)?.credentials, account.credentials)) await this.hooks.credentialsCommitted?.(account).catch(() => void 0);
|
|
1062
|
+
const previousPrimary = before.accounts.find((account) => account.isPrimary);
|
|
1063
|
+
const primary = data.accounts.find((account) => account.isPrimary);
|
|
1064
|
+
if (previousPrimary?.id !== primary?.id || !isDeepStrictEqual(previousPrimary?.credentials, primary?.credentials)) await this.hooks.mirrorPrimary?.(primary?.credentials ?? null).catch(() => void 0);
|
|
1065
|
+
}
|
|
1066
|
+
return result;
|
|
1067
|
+
});
|
|
1006
1068
|
}
|
|
1007
1069
|
/** Public summaries, with cooldowns that already expired reported as absent. */
|
|
1008
1070
|
async listAccounts() {
|
|
@@ -1031,8 +1093,17 @@ var AccountPoolCore = class {
|
|
|
1031
1093
|
return this.hooks.extendSummary ? this.hooks.extendSummary(account, summary, now) : summary;
|
|
1032
1094
|
}
|
|
1033
1095
|
/** Add or re-authorize one account; an existing dedupe key updates in place. */
|
|
1034
|
-
|
|
1035
|
-
|
|
1096
|
+
addAccount(credentials, alias) {
|
|
1097
|
+
return this.updatePool((data) => this.addAccountToPool(data, credentials, alias));
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Add or re-authorize inside an existing transaction.
|
|
1101
|
+
*
|
|
1102
|
+
* Capacity, dedupe and the primary decision are all taken against the live
|
|
1103
|
+
* document, so two simultaneous sign-ins cannot each pass a check the other
|
|
1104
|
+
* has already invalidated.
|
|
1105
|
+
*/
|
|
1106
|
+
addAccountToPool(data, credentials, alias) {
|
|
1036
1107
|
const key = this.hooks.dedupeKey?.(credentials);
|
|
1037
1108
|
const existingIndex = key === void 0 ? -1 : data.accounts.findIndex((account) => this.hooks.dedupeKey?.(account.credentials) === key);
|
|
1038
1109
|
const maxAccounts = this.hooks.maxAccounts ?? 20;
|
|
@@ -1058,77 +1129,79 @@ var AccountPoolCore = class {
|
|
|
1058
1129
|
if (existingIndex >= 0) data.accounts[existingIndex] = account;
|
|
1059
1130
|
else data.accounts.push(account);
|
|
1060
1131
|
if (!data.activeAccountId || account.isPrimary) data.activeAccountId = account.id;
|
|
1061
|
-
await this.write(data);
|
|
1062
|
-
if (account.isPrimary) this.hooks.mirrorPrimary?.(credentials).catch(() => void 0);
|
|
1063
1132
|
return account;
|
|
1064
1133
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
this.hooks.mirrorPrimary?.(primary.credentials).catch(() => void 0);
|
|
1072
|
-
}
|
|
1073
|
-
await this.write(data);
|
|
1134
|
+
setPrimary(accountId) {
|
|
1135
|
+
return this.updatePool((data) => {
|
|
1136
|
+
for (const account of data.accounts) account.isPrimary = account.id === accountId;
|
|
1137
|
+
const primary = data.accounts.find((account) => account.isPrimary);
|
|
1138
|
+
if (primary) data.activeAccountId = primary.id;
|
|
1139
|
+
});
|
|
1074
1140
|
}
|
|
1075
1141
|
/**
|
|
1076
1142
|
* Persist a refreshed credential for one account.
|
|
1077
1143
|
*
|
|
1144
|
+
* `expected` makes this a conditional write: when the stored credential is
|
|
1145
|
+
* no longer the one the caller started from, something newer won the race (a
|
|
1146
|
+
* re-login, a rotation committed while this call was in flight) and the
|
|
1147
|
+
* latest row is returned WITHOUT being overwritten. A deleted account is
|
|
1148
|
+
* never brought back.
|
|
1149
|
+
*
|
|
1078
1150
|
* Used by a forced refresh (a 401 mid-request) rather than by the rotating
|
|
1079
|
-
* getEffectiveAccount path, which
|
|
1151
|
+
* getEffectiveAccount path, which refreshes through its own single-flight.
|
|
1080
1152
|
*/
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
if (target.isPrimary) this.hooks.mirrorPrimary?.(credentials).catch(() => void 0);
|
|
1088
|
-
return target;
|
|
1153
|
+
updateAccountCredentials(accountId, credentials, expected) {
|
|
1154
|
+
return this.updatePool((data) => {
|
|
1155
|
+
const target = data.accounts.find((account) => account.id === accountId);
|
|
1156
|
+
if (target && (expected === void 0 || isDeepStrictEqual(target.credentials, expected))) target.credentials = credentials;
|
|
1157
|
+
return target;
|
|
1158
|
+
});
|
|
1089
1159
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
await this.write(data);
|
|
1096
|
-
}
|
|
1160
|
+
setAlias(accountId, alias) {
|
|
1161
|
+
return this.updatePool((data) => {
|
|
1162
|
+
const target = data.accounts.find((account) => account.id === accountId);
|
|
1163
|
+
if (target && alias.trim()) target.alias = alias.trim();
|
|
1164
|
+
});
|
|
1097
1165
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1166
|
+
deleteAccount(accountId) {
|
|
1167
|
+
return this.updatePool((data) => this.deleteAccountFromPool(data, accountId));
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Remove one account inside a transaction.
|
|
1171
|
+
*
|
|
1172
|
+
* Provider refusal rules (a snapshot this plugin does not own, a desktop
|
|
1173
|
+
* account it may only hide) belong in an override of this, so the check and
|
|
1174
|
+
* the removal cannot be separated by a concurrent change.
|
|
1175
|
+
*/
|
|
1176
|
+
deleteAccountFromPool(data, accountId) {
|
|
1100
1177
|
const wasPrimary = data.accounts.find((account) => account.id === accountId)?.isPrimary;
|
|
1101
1178
|
data.accounts = data.accounts.filter((account) => account.id !== accountId);
|
|
1102
|
-
if (wasPrimary && data.accounts.length > 0)
|
|
1103
|
-
data.accounts[0].isPrimary = true;
|
|
1104
|
-
this.hooks.mirrorPrimary?.(data.accounts[0].credentials).catch(() => void 0);
|
|
1105
|
-
} else if (data.accounts.length === 0) this.hooks.mirrorPrimary?.(null).catch(() => void 0);
|
|
1179
|
+
if (wasPrimary && data.accounts.length > 0) data.accounts[0].isPrimary = true;
|
|
1106
1180
|
if (data.activeAccountId === accountId) data.activeAccountId = data.accounts.find((account) => account.isPrimary)?.id ?? data.accounts[0]?.id;
|
|
1107
|
-
await this.write(data);
|
|
1108
1181
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1182
|
+
setStrategy(strategy) {
|
|
1183
|
+
return this.updatePool((data) => {
|
|
1184
|
+
data.rotationStrategy = strategy;
|
|
1185
|
+
});
|
|
1113
1186
|
}
|
|
1114
1187
|
/** Cool one account down after an upstream 429. */
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
}
|
|
1188
|
+
markCooldown(accountId, durationMs, reason) {
|
|
1189
|
+
return this.updatePool((data) => {
|
|
1190
|
+
const target = data.accounts.find((account) => account.id === accountId);
|
|
1191
|
+
if (target) {
|
|
1192
|
+
target.cooldownUntil = Date.now() + Math.max(MIN_COOLDOWN_MS, durationMs);
|
|
1193
|
+
target.cooldownReason = reason;
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1123
1196
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
}
|
|
1197
|
+
clearCooldown(accountId) {
|
|
1198
|
+
return this.updatePool((data) => {
|
|
1199
|
+
const target = data.accounts.find((account) => account.id === accountId);
|
|
1200
|
+
if (target) {
|
|
1201
|
+
target.cooldownUntil = void 0;
|
|
1202
|
+
target.cooldownReason = void 0;
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1132
1205
|
}
|
|
1133
1206
|
/**
|
|
1134
1207
|
* Record that an account can no longer authenticate.
|
|
@@ -1136,23 +1209,23 @@ var AccountPoolCore = class {
|
|
|
1136
1209
|
* The account stays in the pool: signing in again is what restores it, and a
|
|
1137
1210
|
* deleted account would take the user's alias and ordering with it.
|
|
1138
1211
|
*/
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
}
|
|
1212
|
+
markAuthFailed(accountId, reason, status = "expired") {
|
|
1213
|
+
return this.updatePool((data) => {
|
|
1214
|
+
const target = data.accounts.find((account) => account.id === accountId);
|
|
1215
|
+
if (target) {
|
|
1216
|
+
target.authStatus = status;
|
|
1217
|
+
target.authFailedReason = reason;
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1147
1220
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
}
|
|
1221
|
+
clearAuthFailed(accountId) {
|
|
1222
|
+
return this.updatePool((data) => {
|
|
1223
|
+
const target = data.accounts.find((account) => account.id === accountId);
|
|
1224
|
+
if (target) {
|
|
1225
|
+
target.authStatus = void 0;
|
|
1226
|
+
target.authFailedReason = void 0;
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1156
1229
|
}
|
|
1157
1230
|
/** Whether an account may serve a request right now. */
|
|
1158
1231
|
isEligible(account, now, triedAccountIds) {
|
|
@@ -1192,24 +1265,16 @@ var AccountPoolCore = class {
|
|
|
1192
1265
|
*
|
|
1193
1266
|
* @param fetchFn - fetch used if the credential is close to expiry.
|
|
1194
1267
|
*/
|
|
1195
|
-
async getCredentialAccount(fetchFn = fetch) {
|
|
1268
|
+
async getCredentialAccount(fetchFn = fetch, forceRefresh = false) {
|
|
1196
1269
|
const data = await this.read();
|
|
1197
1270
|
if (data.accounts.length === 0) throw new Error(this.hooks.emptyMessage ?? `未登录 ${this.hooks.displayName} 账号,请先在设置页添加账号。`);
|
|
1198
|
-
const now = Date.now();
|
|
1199
1271
|
const usable = data.accounts.filter((account) => this.isCredentialUsable(account));
|
|
1200
1272
|
if (usable.length === 0) throw new LlmError(`全部 ${data.accounts.length} 个 ${this.hooks.displayName} 账号均需要重新登录。`, "AUTH", { status: 401 });
|
|
1201
1273
|
const selected = this.selectAccount(usable, data);
|
|
1202
|
-
|
|
1203
|
-
const refreshed = await this.hooks.refresh(selected.credentials, fetchFn);
|
|
1204
|
-
await this.updateAccountCredentials(selected.id, refreshed);
|
|
1205
|
-
return {
|
|
1206
|
-
account: selected,
|
|
1207
|
-
credentials: refreshed
|
|
1208
|
-
};
|
|
1209
|
-
}
|
|
1274
|
+
const account = await this.freshAccount(selected, fetchFn, forceRefresh);
|
|
1210
1275
|
return {
|
|
1211
|
-
account
|
|
1212
|
-
credentials:
|
|
1276
|
+
account,
|
|
1277
|
+
credentials: account.credentials
|
|
1213
1278
|
};
|
|
1214
1279
|
}
|
|
1215
1280
|
/**
|
|
@@ -1244,8 +1309,8 @@ var AccountPoolCore = class {
|
|
|
1244
1309
|
* looks unexpired, and only the service's own refusal says so. Renewing it is
|
|
1245
1310
|
* what separates that from a credential that genuinely needs a new sign-in.
|
|
1246
1311
|
*/
|
|
1247
|
-
async renewCredential(accountId, fetchFn = fetch) {
|
|
1248
|
-
return this.credentialFor(accountId, fetchFn, true);
|
|
1312
|
+
async renewCredential(accountId, fetchFn = fetch, expected) {
|
|
1313
|
+
return this.credentialFor(accountId, fetchFn, true, expected);
|
|
1249
1314
|
}
|
|
1250
1315
|
/**
|
|
1251
1316
|
* Resolve, and if needed renew, one account's credential.
|
|
@@ -1255,29 +1320,106 @@ var AccountPoolCore = class {
|
|
|
1255
1320
|
* not name an account (the catalog loader) still has to present the
|
|
1256
1321
|
* credential that is actually in play.
|
|
1257
1322
|
*/
|
|
1258
|
-
async credentialFor(accountId, fetchFn, force) {
|
|
1323
|
+
async credentialFor(accountId, fetchFn, force, expected) {
|
|
1259
1324
|
const data = await this.read();
|
|
1260
1325
|
const target = accountId === void 0 ? data.accounts.find((account) => account.id === data.activeAccountId) ?? data.accounts.find((account) => account.isPrimary) ?? data.accounts[0] : data.accounts.find((account) => account.id === accountId);
|
|
1261
1326
|
if (target === void 0) throw new LlmError(this.hooks.emptyMessage ?? `未登录 ${this.hooks.displayName} 账号,请先在设置页添加账号。`, "AUTH");
|
|
1262
1327
|
if (!this.isCredentialUsable(target)) throw new LlmError(`${this.hooks.displayName} 账号的凭据已被拒绝,需要重新登录。`, "AUTH", { status: 401 });
|
|
1263
|
-
if (
|
|
1264
|
-
|
|
1328
|
+
if (expected !== void 0 && !isDeepStrictEqual(expected, target.credentials)) return target.credentials;
|
|
1329
|
+
return (await this.freshAccount(target, fetchFn, force)).credentials;
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Return one account's current row, refreshing it at most once per generation.
|
|
1333
|
+
*
|
|
1334
|
+
* Shared by every entry point — routing, auxiliary tools, the settings card
|
|
1335
|
+
* and forced renewals — across all instances of the pool file, because a
|
|
1336
|
+
* rotating refresh token may be spent once: two parallel exchanges both fail,
|
|
1337
|
+
* and the losing one gets recorded as a dead sign-in.
|
|
1338
|
+
*
|
|
1339
|
+
* Reading the current generation and registering/joining its flight happen in
|
|
1340
|
+
* one short transaction, so a commit cannot slip between the two decisions.
|
|
1341
|
+
* The network call is fired AFTER the lock is released, and the plan returned
|
|
1342
|
+
* from the lock is a plain value: awaiting a flight while holding the lock
|
|
1343
|
+
* would deadlock that flight's own commit.
|
|
1344
|
+
*/
|
|
1345
|
+
async freshAccount(target, fetchFn, force = false) {
|
|
1346
|
+
let launch;
|
|
1347
|
+
const plan = await this.serialize(async () => {
|
|
1348
|
+
const current = (await this.loadPoolData()).accounts.find((account) => account.id === target.id);
|
|
1349
|
+
if (current === void 0 || !this.isCredentialUsable(current)) throw new PoolAccountUnavailableError(this.hooks.emptyMessage ?? "未登录 " + this.hooks.displayName + " 账号,请先在设置页添加账号。", "AUTH");
|
|
1350
|
+
if (!isDeepStrictEqual(current.credentials, target.credentials)) return { account: current };
|
|
1351
|
+
if (!this.hooks.refresh || !force && !this.shouldRefreshCredential(current, Date.now())) return { account: current };
|
|
1352
|
+
const key = JSON.stringify([this.operationKey, current.id]);
|
|
1353
|
+
const pending = poolRefreshes.get(key);
|
|
1354
|
+
if (pending !== void 0 && isDeepStrictEqual(pending.credentials, current.credentials)) return { result: pending.result };
|
|
1355
|
+
const result = new Promise((resolve, reject) => {
|
|
1356
|
+
launch = () => {
|
|
1357
|
+
this.refreshAccount(current, fetchFn).then(resolve, reject);
|
|
1358
|
+
};
|
|
1359
|
+
});
|
|
1360
|
+
const flight = {
|
|
1361
|
+
credentials: structuredClone(current.credentials),
|
|
1362
|
+
result
|
|
1363
|
+
};
|
|
1364
|
+
poolRefreshes.set(key, flight);
|
|
1365
|
+
const release = () => {
|
|
1366
|
+
if (poolRefreshes.get(key) === flight) poolRefreshes.delete(key);
|
|
1367
|
+
};
|
|
1368
|
+
result.then(release, release);
|
|
1369
|
+
return { result };
|
|
1370
|
+
});
|
|
1371
|
+
launch?.();
|
|
1372
|
+
return plan.result ?? plan.account;
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Refresh one account: network outside the lock, then a conditional commit.
|
|
1376
|
+
*
|
|
1377
|
+
* Every write-back is checked against the credential the refresh started from,
|
|
1378
|
+
* which is what keeps a late response from restoring a spent token over a
|
|
1379
|
+
* re-login, and keeps a deleted account from coming back.
|
|
1380
|
+
*/
|
|
1381
|
+
async refreshAccount(target, fetchFn) {
|
|
1382
|
+
const expected = structuredClone(target.credentials);
|
|
1265
1383
|
const live = await this.hooks.liveCredentialsFor?.(target).catch(() => null);
|
|
1266
1384
|
if (live !== null && live !== void 0 && live.advanced) {
|
|
1267
|
-
target.
|
|
1268
|
-
|
|
1269
|
-
|
|
1385
|
+
const account = await this.updateAccountCredentials(target.id, live.credentials, expected).catch(() => ({
|
|
1386
|
+
...target,
|
|
1387
|
+
credentials: live.credentials
|
|
1388
|
+
}));
|
|
1389
|
+
this.assertUsableAccount(account);
|
|
1390
|
+
return account;
|
|
1270
1391
|
}
|
|
1271
1392
|
let refreshed;
|
|
1272
1393
|
try {
|
|
1273
1394
|
refreshed = await this.hooks.refresh(target.credentials, fetchFn);
|
|
1274
1395
|
} catch (error) {
|
|
1275
1396
|
const status = this.hooks.refreshFailureStatus?.(error);
|
|
1276
|
-
|
|
1397
|
+
let superseded;
|
|
1398
|
+
await this.updatePool((data) => {
|
|
1399
|
+
const current = data.accounts.find((account) => account.id === target.id);
|
|
1400
|
+
if (current === void 0) return;
|
|
1401
|
+
if (!isDeepStrictEqual(current.credentials, expected)) {
|
|
1402
|
+
superseded = current;
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
if (status !== void 0) {
|
|
1406
|
+
current.authStatus = status;
|
|
1407
|
+
current.authFailedReason = error instanceof Error ? error.message : String(error);
|
|
1408
|
+
}
|
|
1409
|
+
}).catch(() => void 0);
|
|
1410
|
+
if (superseded !== void 0) {
|
|
1411
|
+
this.assertUsableAccount(superseded);
|
|
1412
|
+
return superseded;
|
|
1413
|
+
}
|
|
1277
1414
|
throw error;
|
|
1278
1415
|
}
|
|
1279
|
-
await this.updateAccountCredentials(target.id, refreshed);
|
|
1280
|
-
|
|
1416
|
+
const account = await this.updateAccountCredentials(target.id, refreshed, expected);
|
|
1417
|
+
this.assertUsableAccount(account);
|
|
1418
|
+
return account;
|
|
1419
|
+
}
|
|
1420
|
+
assertUsableAccount(account) {
|
|
1421
|
+
if (account === void 0) throw new PoolAccountUnavailableError(this.hooks.emptyMessage ?? "未登录 " + this.hooks.displayName + " 账号,请先在设置页添加账号。", "AUTH");
|
|
1422
|
+
if (!this.isCredentialUsable(account)) throw new PoolAccountUnavailableError(this.hooks.displayName + " 账号的凭据已被拒绝,需要重新登录。", "AUTH", { status: 401 });
|
|
1281
1423
|
}
|
|
1282
1424
|
/** Whether one account's credential must be refreshed before it is handed out. */
|
|
1283
1425
|
shouldRefreshCredential(account, now) {
|
|
@@ -1321,37 +1463,34 @@ var AccountPoolCore = class {
|
|
|
1321
1463
|
* @param fetchFn - fetch used by the provider's token refresh.
|
|
1322
1464
|
* @throws LlmError with `RATE_LIMIT` when every account is cooling down.
|
|
1323
1465
|
*/
|
|
1324
|
-
async getEffectiveAccount(excludeIds, fetchFn = fetch) {
|
|
1466
|
+
async getEffectiveAccount(excludeIds, fetchFn = fetch, forceRefresh = false) {
|
|
1325
1467
|
const data = await this.read();
|
|
1326
1468
|
if (data.accounts.length === 0) throw new Error(this.hooks.emptyMessage ?? `未登录 ${this.hooks.displayName} 账号,请先在设置页添加账号。`);
|
|
1327
1469
|
const now = Date.now();
|
|
1328
1470
|
const eligible = data.accounts.filter((account) => this.isEligible(account, now, excludeIds));
|
|
1329
1471
|
if (eligible.length === 0) throw this.allUnavailableError(data, now);
|
|
1330
1472
|
const selected = this.selectAccount(eligible, data);
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
selected.lastUsedAt = now;
|
|
1350
|
-
data.activeAccountId = selected.id;
|
|
1351
|
-
await this.write(data).catch(() => void 0);
|
|
1473
|
+
let refreshed;
|
|
1474
|
+
try {
|
|
1475
|
+
refreshed = await this.freshAccount(selected, fetchFn, forceRefresh);
|
|
1476
|
+
} catch (error) {
|
|
1477
|
+
if (this.hooks.refreshFailureStatus?.(error) === void 0 && !(error instanceof PoolAccountUnavailableError)) throw error;
|
|
1478
|
+
const nextExclude = new Set(excludeIds ?? []);
|
|
1479
|
+
nextExclude.add(selected.id);
|
|
1480
|
+
if (await this.hasAnotherAvailableAccount(nextExclude)) return this.getEffectiveAccount(nextExclude, fetchFn, forceRefresh);
|
|
1481
|
+
throw error;
|
|
1482
|
+
}
|
|
1483
|
+
const account = await this.updatePool((current) => {
|
|
1484
|
+
const latest = current.accounts.find((entry) => entry.id === refreshed.id);
|
|
1485
|
+
if (latest === void 0 || !isDeepStrictEqual(latest.credentials, refreshed.credentials)) return latest;
|
|
1486
|
+
latest.lastUsedAt = now;
|
|
1487
|
+
if (current.activeAccountId === data.activeAccountId) current.activeAccountId = latest.id;
|
|
1488
|
+
return latest;
|
|
1489
|
+
}).catch(() => refreshed);
|
|
1490
|
+
this.assertUsableAccount(account);
|
|
1352
1491
|
return {
|
|
1353
|
-
account
|
|
1354
|
-
credentials:
|
|
1492
|
+
account,
|
|
1493
|
+
credentials: account.credentials
|
|
1355
1494
|
};
|
|
1356
1495
|
}
|
|
1357
1496
|
};
|
|
@@ -1724,8 +1863,7 @@ var OAuthService = class {
|
|
|
1724
1863
|
async refresh() {
|
|
1725
1864
|
this.assertAvailable();
|
|
1726
1865
|
if (this.pool !== null) {
|
|
1727
|
-
|
|
1728
|
-
await this.refreshAccount(credentials).then((refreshed) => this.pool.updateAccountCredentials(account.id, refreshed));
|
|
1866
|
+
await this.pool.getEffectiveAccount(void 0, this.fetchFn, true);
|
|
1729
1867
|
return this.status();
|
|
1730
1868
|
}
|
|
1731
1869
|
const stored = await this.loadAuthenticated();
|
|
@@ -1787,18 +1925,8 @@ var OAuthService = class {
|
|
|
1787
1925
|
*/
|
|
1788
1926
|
async credentials(forceRefresh = false, access = {}) {
|
|
1789
1927
|
if (this.pool !== null) {
|
|
1790
|
-
if (access.purpose === "tool")
|
|
1791
|
-
|
|
1792
|
-
if (!forceRefresh) return credentials;
|
|
1793
|
-
const refreshed = await this.refreshAccount(credentials, this.fetchFn);
|
|
1794
|
-
await this.pool.updateAccountCredentials(account.id, refreshed);
|
|
1795
|
-
return refreshed;
|
|
1796
|
-
}
|
|
1797
|
-
const { account, credentials } = await this.pool.getEffectiveAccount(void 0, this.fetchFn);
|
|
1798
|
-
if (!forceRefresh) return credentials;
|
|
1799
|
-
const refreshed = await this.refreshAccount(credentials, this.fetchFn);
|
|
1800
|
-
await this.pool.updateAccountCredentials(account.id, refreshed);
|
|
1801
|
-
return refreshed;
|
|
1928
|
+
if (access.purpose === "tool") return (await this.pool.getCredentialAccount(this.fetchFn, forceRefresh)).credentials;
|
|
1929
|
+
return (await this.pool.getEffectiveAccount(void 0, this.fetchFn, forceRefresh)).credentials;
|
|
1802
1930
|
}
|
|
1803
1931
|
const stored = await this.loadAuthenticated();
|
|
1804
1932
|
if (forceRefresh || stored.expiresAt - this.now() <= 6e4) return this.refreshCredentials(stored);
|
|
@@ -2251,11 +2379,8 @@ var CodexAccountPool = class extends AccountPoolCore {
|
|
|
2251
2379
|
async refreshAccountNow(accountId) {
|
|
2252
2380
|
const account = (await this.read()).accounts.find((entry) => entry.id === accountId);
|
|
2253
2381
|
if (account === void 0) throw new Error("ChatGPT 账号已不在号池中");
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
const refreshed = await refresher.refreshAccount(account.credentials);
|
|
2257
|
-
await this.updateAccountCredentials(accountId, refreshed);
|
|
2258
|
-
return refreshed;
|
|
2382
|
+
if (this.refresherRef.current === void 0) throw new Error("ChatGPT 凭据刷新服务尚未就绪");
|
|
2383
|
+
return this.renewCredential(accountId, fetch, account.credentials);
|
|
2259
2384
|
}
|
|
2260
2385
|
/** The single-credential store this pool mirrors its primary account into. */
|
|
2261
2386
|
mirrorStore() {
|
|
@@ -21552,23 +21677,30 @@ var MinimaxCodeAccountPool = class extends AccountPoolCore {
|
|
|
21552
21677
|
* because that one genuinely needs a sign-in.
|
|
21553
21678
|
*/
|
|
21554
21679
|
async healStaleAuthFailures(current) {
|
|
21555
|
-
const
|
|
21556
|
-
if (
|
|
21680
|
+
const failedAccounts = (current ?? await this.read()).accounts.filter(isMarkedFailed);
|
|
21681
|
+
if (failedAccounts.length === 0) return false;
|
|
21682
|
+
const targets = new Map(failedAccounts.map((account) => [account.id, {
|
|
21683
|
+
credentials: account.credentials,
|
|
21684
|
+
authStatus: account.authStatus
|
|
21685
|
+
}]));
|
|
21557
21686
|
const live = await this.store.read().catch(() => null);
|
|
21558
21687
|
if (live === null) return false;
|
|
21559
|
-
|
|
21560
|
-
|
|
21561
|
-
|
|
21562
|
-
|
|
21563
|
-
|
|
21564
|
-
|
|
21565
|
-
|
|
21566
|
-
|
|
21567
|
-
|
|
21568
|
-
|
|
21569
|
-
|
|
21570
|
-
|
|
21571
|
-
|
|
21688
|
+
return await this.updatePool((draft) => {
|
|
21689
|
+
let changed = false;
|
|
21690
|
+
for (const account of draft.accounts) {
|
|
21691
|
+
const expected = targets.get(account.id);
|
|
21692
|
+
if (!expected) continue;
|
|
21693
|
+
if (account.authStatus !== expected.authStatus) continue;
|
|
21694
|
+
if (!isDeepStrictEqual(account.credentials, expected.credentials)) continue;
|
|
21695
|
+
if (!minimaxCodeSameCredentialSession(live, account.credentials)) continue;
|
|
21696
|
+
if (!minimaxCodeCredentialAdvancedPast(live, account.credentials)) continue;
|
|
21697
|
+
account.authStatus = void 0;
|
|
21698
|
+
account.authFailedReason = void 0;
|
|
21699
|
+
account.credentials = live;
|
|
21700
|
+
changed = true;
|
|
21701
|
+
}
|
|
21702
|
+
return changed;
|
|
21703
|
+
});
|
|
21572
21704
|
}
|
|
21573
21705
|
/**
|
|
21574
21706
|
* Pick the account for the next request.
|
|
@@ -21582,7 +21714,13 @@ var MinimaxCodeAccountPool = class extends AccountPoolCore {
|
|
|
21582
21714
|
const effective = await this.getEffectiveAccount(excludeIds, fetchFn);
|
|
21583
21715
|
const live = await this.liveNativeCredential(effective.account);
|
|
21584
21716
|
if (live === null) return effective;
|
|
21585
|
-
if (live.accessToken !== effective.credentials.accessToken)
|
|
21717
|
+
if (live.accessToken !== effective.credentials.accessToken) {
|
|
21718
|
+
const updated = await this.updateAccountCredentials(effective.account.id, live, effective.credentials).catch(() => void 0);
|
|
21719
|
+
if (updated) return {
|
|
21720
|
+
account: updated,
|
|
21721
|
+
credentials: updated.credentials
|
|
21722
|
+
};
|
|
21723
|
+
}
|
|
21586
21724
|
return {
|
|
21587
21725
|
account: effective.account,
|
|
21588
21726
|
credentials: live
|
|
@@ -21644,7 +21782,7 @@ var MinimaxCodeAccountPool = class extends AccountPoolCore {
|
|
|
21644
21782
|
return this.addAccount(credentials, alias);
|
|
21645
21783
|
}
|
|
21646
21784
|
/**
|
|
21647
|
-
*
|
|
21785
|
+
* Remove one account inside a transaction.
|
|
21648
21786
|
*
|
|
21649
21787
|
* An account whose credential is the desktop app's own \`auth.json\` is refused:
|
|
21650
21788
|
* this plugin does not own that file, and removing the account would either
|
|
@@ -21652,9 +21790,9 @@ var MinimaxCodeAccountPool = class extends AccountPoolCore {
|
|
|
21652
21790
|
* file — sign the user out of MiniMax Code itself. Signing out there is the
|
|
21653
21791
|
* supported way to end it, and the card is told so by \`removable: false\`.
|
|
21654
21792
|
*/
|
|
21655
|
-
|
|
21656
|
-
if (
|
|
21657
|
-
|
|
21793
|
+
deleteAccountFromPool(data, accountId) {
|
|
21794
|
+
if (data.accounts.find((account) => account.id === accountId)?.source === "minimax-native") throw new Error("该账号来自 MiniMax Code 桌面端(~/.minimax/auth),本插件只能读取和续期,不能删除。请在 MiniMax Code 应用中退出登录。");
|
|
21795
|
+
super.deleteAccountFromPool(data, accountId);
|
|
21658
21796
|
}
|
|
21659
21797
|
/** The credential store this pool mirrors its primary account into. */
|
|
21660
21798
|
mirrorStore() {
|
|
@@ -26157,15 +26295,15 @@ var WorkBuddyAccountPool = class extends AccountPoolCore {
|
|
|
26157
26295
|
this.options_selection = options.selection;
|
|
26158
26296
|
}
|
|
26159
26297
|
/**
|
|
26160
|
-
*
|
|
26298
|
+
* Load the pool, refreshing the legacy-key index on the way.
|
|
26161
26299
|
*
|
|
26162
|
-
* Every routing decision goes through this method before it
|
|
26163
|
-
* settings selection, so building the index here is what keeps
|
|
26164
|
-
* before the identity fix resolvable when
|
|
26165
|
-
* for it.
|
|
26300
|
+
* Every routing decision and mutation goes through this method before it
|
|
26301
|
+
* consults the settings selection, so building the index here is what keeps
|
|
26302
|
+
* a pin written before the identity fix resolvable when
|
|
26303
|
+
* {@link resolveSelectionId} is asked for it.
|
|
26166
26304
|
*/
|
|
26167
|
-
async
|
|
26168
|
-
const data = await super.
|
|
26305
|
+
async loadPoolData() {
|
|
26306
|
+
const data = await super.loadPoolData();
|
|
26169
26307
|
this.legacyIds.clear();
|
|
26170
26308
|
for (const account of data.accounts) for (const alias of workBuddyAccountIdAliases(account.credentials)) if (alias !== account.id) this.legacyIds.set(alias, account.id);
|
|
26171
26309
|
return data;
|
|
@@ -26185,20 +26323,34 @@ var WorkBuddyAccountPool = class extends AccountPoolCore {
|
|
|
26185
26323
|
*/
|
|
26186
26324
|
async syncDesktopAccounts() {
|
|
26187
26325
|
const discovered = await this.store.discoverDesktopCredentials();
|
|
26188
|
-
|
|
26189
|
-
|
|
26190
|
-
|
|
26191
|
-
const
|
|
26192
|
-
|
|
26193
|
-
|
|
26194
|
-
|
|
26195
|
-
|
|
26196
|
-
|
|
26197
|
-
|
|
26198
|
-
|
|
26199
|
-
|
|
26326
|
+
const candidates = [];
|
|
26327
|
+
const seen = /* @__PURE__ */ new Set();
|
|
26328
|
+
for (const raw of discovered) {
|
|
26329
|
+
const credentials = withResolvedIdentity({
|
|
26330
|
+
...raw,
|
|
26331
|
+
source: "desktop"
|
|
26332
|
+
});
|
|
26333
|
+
const id = workBuddyAccountId(credentials);
|
|
26334
|
+
if (seen.has(id)) continue;
|
|
26335
|
+
seen.add(id);
|
|
26336
|
+
candidates.push(credentials);
|
|
26337
|
+
}
|
|
26338
|
+
if (candidates.length > 0) await this.updatePool((data) => {
|
|
26339
|
+
const existingKeys = /* @__PURE__ */ new Set();
|
|
26340
|
+
for (const account of data.accounts) {
|
|
26341
|
+
existingKeys.add(account.id);
|
|
26342
|
+
const key = this.hooks.dedupeKey?.(account.credentials);
|
|
26343
|
+
if (key !== void 0) existingKeys.add(key);
|
|
26200
26344
|
}
|
|
26201
|
-
|
|
26345
|
+
for (const credentials of candidates) {
|
|
26346
|
+
const key = this.hooks.dedupeKey?.(credentials);
|
|
26347
|
+
const id = this.hooks.accountId?.(credentials);
|
|
26348
|
+
if (key !== void 0 && existingKeys.has(key) || id !== void 0 && existingKeys.has(id)) continue;
|
|
26349
|
+
this.addAccountToPool(data, credentials);
|
|
26350
|
+
if (key !== void 0) existingKeys.add(key);
|
|
26351
|
+
if (id !== void 0) existingKeys.add(id);
|
|
26352
|
+
}
|
|
26353
|
+
});
|
|
26202
26354
|
return this.listAccounts();
|
|
26203
26355
|
}
|
|
26204
26356
|
/**
|
|
@@ -26214,7 +26366,13 @@ var WorkBuddyAccountPool = class extends AccountPoolCore {
|
|
|
26214
26366
|
const live = await this.store.readDesktopFile(effective.account.sourceFile);
|
|
26215
26367
|
if (live === null) return effective;
|
|
26216
26368
|
if (isExpired(live)) return effective;
|
|
26217
|
-
if (live.accessToken !== effective.credentials.accessToken)
|
|
26369
|
+
if (live.accessToken !== effective.credentials.accessToken) {
|
|
26370
|
+
const updated = await this.updateAccountCredentials(effective.account.id, live, effective.credentials).catch(() => void 0);
|
|
26371
|
+
if (updated) return {
|
|
26372
|
+
account: updated,
|
|
26373
|
+
credentials: updated.credentials
|
|
26374
|
+
};
|
|
26375
|
+
}
|
|
26218
26376
|
return {
|
|
26219
26377
|
account: effective.account,
|
|
26220
26378
|
credentials: live
|
|
@@ -26251,15 +26409,15 @@ var WorkBuddyAccountPool = class extends AccountPoolCore {
|
|
|
26251
26409
|
return super.addAccount(withResolvedIdentity(credentials), alias);
|
|
26252
26410
|
}
|
|
26253
26411
|
/**
|
|
26254
|
-
*
|
|
26412
|
+
* Remove one account inside a transaction.
|
|
26255
26413
|
*
|
|
26256
26414
|
* A desktop account is refused: the plugin does not own that credential file,
|
|
26257
26415
|
* so removing it from the pool would strand the IDE's session while telling
|
|
26258
26416
|
* the user their account was deleted. Hiding is the supported action there.
|
|
26259
26417
|
*/
|
|
26260
|
-
|
|
26261
|
-
if (
|
|
26262
|
-
|
|
26418
|
+
deleteAccountFromPool(data, accountId) {
|
|
26419
|
+
if (data.accounts.find((account) => account.id === accountId)?.source === "desktop") throw new Error("桌面账号由 CodeBuddy 客户端所有,只能隐藏,不能删除。");
|
|
26420
|
+
super.deleteAccountFromPool(data, accountId);
|
|
26263
26421
|
}
|
|
26264
26422
|
};
|
|
26265
26423
|
//#endregion
|
|
@@ -26351,6 +26509,17 @@ const OAUTH_BETA = "oauth-2025-04-20";
|
|
|
26351
26509
|
const CLAUDE_CODE_BETA = "claude-code-20250219";
|
|
26352
26510
|
/** Lets thinking blocks interleave with tool use across a multi-turn loop. */
|
|
26353
26511
|
const INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
|
|
26512
|
+
/**
|
|
26513
|
+
* Licenses `thinking.block_binding` in a request body.
|
|
26514
|
+
*
|
|
26515
|
+
* The field is not optional to license: the vendor documents that a body
|
|
26516
|
+
* carrying `block_binding` WITHOUT this beta is a 400 whose message ends
|
|
26517
|
+
* `block_binding: Extra inputs are not permitted` — every request, every turn.
|
|
26518
|
+
* The mid-convo thinking form always sends `block_binding`, so a request that
|
|
26519
|
+
* uses that form must carry this value. The reference sends it for exactly those
|
|
26520
|
+
* models (`THINKING_BINDING_CONTROLS_BETA`, pushed when `supportsMidConvoEffort`).
|
|
26521
|
+
*/
|
|
26522
|
+
const THINKING_BINDING_CONTROLS_BETA = "thinking-binding-controls-2026-08-01";
|
|
26354
26523
|
/** Anthropic protocol revision sent as `anthropic-version`. */
|
|
26355
26524
|
const ANTHROPIC_VERSION = "2023-06-01";
|
|
26356
26525
|
/**
|
|
@@ -26380,12 +26549,18 @@ const ANTHROPIC_VERSION = "2023-06-01";
|
|
|
26380
26549
|
* trades a clear refusal for an unpredictable one. Note that npm's `stable`
|
|
26381
26550
|
* dist-tag can itself sit below a model's floor, so `latest` is the tag to read.
|
|
26382
26551
|
*
|
|
26552
|
+
* It must also be at or above the release that FIRST SHIPPED each model in the
|
|
26553
|
+
* table, whether or not a floor for that model has been observed: upstream has
|
|
26554
|
+
* so far gated a new model on the release that added it (Opus 5.5 arrived in
|
|
26555
|
+
* 2.1.280 and is refused below 2.1.280). Claude Sonnet 5.5 arrived in 2.1.284,
|
|
26556
|
+
* so 2.1.283 could not be relied on to reach it; 2.1.285 is npm `latest`.
|
|
26557
|
+
*
|
|
26383
26558
|
* It is still not a literal that only moves when this package is republished: a
|
|
26384
26559
|
* user whose installed baseline has aged past the server's floor has to be able
|
|
26385
26560
|
* to raise it without waiting for a release. Read the effective value with
|
|
26386
26561
|
* {@link claudeCliVersion}, pin one with {@link setClaudeCliVersion}.
|
|
26387
26562
|
*/
|
|
26388
|
-
const CLAUDE_CLI_VERSION = "2.1.
|
|
26563
|
+
const CLAUDE_CLI_VERSION = "2.1.285";
|
|
26389
26564
|
/**
|
|
26390
26565
|
* Loopback callback port the subscription flow defaults to.
|
|
26391
26566
|
*
|
|
@@ -26743,8 +26918,10 @@ function loopbackRedirectUri(port = DEFAULT_CALLBACK_PORT) {
|
|
|
26743
26918
|
* LOCAL ADDITIONS — the rows the reference snapshot predates
|
|
26744
26919
|
* ---------------------------------------------------------------------------
|
|
26745
26920
|
*
|
|
26746
|
-
* `claude-opus-5-5`
|
|
26747
|
-
* `LOCALLY_CURATED_MODEL_IDS`; the two must be
|
|
26921
|
+
* `claude-opus-5-5` and `claude-sonnet-5-5` are the two such rows today. The
|
|
26922
|
+
* mirror list in the test is `LOCALLY_CURATED_MODEL_IDS`; the two must be
|
|
26923
|
+
* changed together. Sonnet 5.5's own reasons are recorded at its row; what
|
|
26924
|
+
* follows below is Opus 5.5's.
|
|
26748
26925
|
*
|
|
26749
26926
|
* It is CURATED rather than transcribed because the snapshot this table copies
|
|
26750
26927
|
* has no entry for it, and the one thing a transcription table must never do is
|
|
@@ -26796,8 +26973,8 @@ function loopbackRedirectUri(port = DEFAULT_CALLBACK_PORT) {
|
|
|
26796
26973
|
* the user has to reach.
|
|
26797
26974
|
*/
|
|
26798
26975
|
/**
|
|
26799
|
-
*
|
|
26800
|
-
* followed by the curated
|
|
26976
|
+
* Sixteen rows: the snapshot's own 14, in the snapshot's own declaration order,
|
|
26977
|
+
* followed by the curated rows the snapshot predates (see LOCAL ADDITIONS above).
|
|
26801
26978
|
*
|
|
26802
26979
|
* The order matters and is not cosmetic. The test asserts the snapshot's ids
|
|
26803
26980
|
* appear in the table as a SUBSEQUENCE, so a curated row may be appended or
|
|
@@ -27065,7 +27242,26 @@ const CLAUDE_MODELS = Object.freeze([
|
|
|
27065
27242
|
"max"
|
|
27066
27243
|
],
|
|
27067
27244
|
canDisableThinking: false,
|
|
27068
|
-
minCliVersion: "2.1.280"
|
|
27245
|
+
minCliVersion: "2.1.280",
|
|
27246
|
+
bindsThinkingToPrefix: true
|
|
27247
|
+
},
|
|
27248
|
+
{
|
|
27249
|
+
id: "claude-sonnet-5-5",
|
|
27250
|
+
name: "Claude Sonnet 5.5",
|
|
27251
|
+
contextWindow: 1e6,
|
|
27252
|
+
maxTokens: 128e3,
|
|
27253
|
+
supportsImage: true,
|
|
27254
|
+
supportsTemperature: false,
|
|
27255
|
+
thinkingMode: "mid-convo",
|
|
27256
|
+
reasoningEfforts: [
|
|
27257
|
+
"low",
|
|
27258
|
+
"medium",
|
|
27259
|
+
"high",
|
|
27260
|
+
"xhigh",
|
|
27261
|
+
"max"
|
|
27262
|
+
],
|
|
27263
|
+
canDisableThinking: false,
|
|
27264
|
+
bindsThinkingToPrefix: true
|
|
27069
27265
|
}
|
|
27070
27266
|
]);
|
|
27071
27267
|
/** Every id the fallback table knows, in declaration order. */
|
|
@@ -27165,6 +27361,10 @@ function claudeThinkingMode(modelId, catalog) {
|
|
|
27165
27361
|
function claudeModelSupportsTemperature(modelId, catalog) {
|
|
27166
27362
|
return resolveClaudeModel(modelId, catalog ?? CLAUDE_MODELS).supportsTemperature;
|
|
27167
27363
|
}
|
|
27364
|
+
/** Whether one model runs the documented preserved-thinking prefix check. */
|
|
27365
|
+
function claudeModelBindsThinkingToPrefix(modelId, catalog) {
|
|
27366
|
+
return resolveClaudeModel(modelId, catalog ?? CLAUDE_MODELS).bindsThinkingToPrefix === true;
|
|
27367
|
+
}
|
|
27168
27368
|
/** Whether thinking can be turned off for one model. */
|
|
27169
27369
|
function claudeModelCanDisableThinking(modelId, catalog) {
|
|
27170
27370
|
return resolveClaudeModel(modelId, catalog ?? CLAUDE_MODELS).canDisableThinking;
|
|
@@ -27990,6 +28190,7 @@ function claudeBetas(options = {}) {
|
|
|
27990
28190
|
if (options.model === void 0 || !isHaikuModel(options.model)) betas.push(CLAUDE_CODE_BETA);
|
|
27991
28191
|
betas.push(OAUTH_BETA);
|
|
27992
28192
|
if (options.thinking === true) betas.push(INTERLEAVED_THINKING_BETA);
|
|
28193
|
+
if (options.thinkingBinding === true) betas.push(THINKING_BINDING_CONTROLS_BETA);
|
|
27993
28194
|
return betas;
|
|
27994
28195
|
}
|
|
27995
28196
|
/** The `User-Agent` value this line reports for one surface. */
|
|
@@ -28136,7 +28337,7 @@ async function performCatalogLoad(credentials, options) {
|
|
|
28136
28337
|
let models;
|
|
28137
28338
|
let live = false;
|
|
28138
28339
|
try {
|
|
28139
|
-
const response = await fetchFn("https://api.anthropic.com/v1/models", {
|
|
28340
|
+
const response = await fetchFn("https://api.anthropic.com/v1/models?limit=1000", {
|
|
28140
28341
|
method: "GET",
|
|
28141
28342
|
headers: buildClaudeHeaders(credentials.accessToken, { method: "GET" }),
|
|
28142
28343
|
signal: timeoutSignal(options.signal, DISCOVERY_TIMEOUT_MS)
|
|
@@ -28603,8 +28804,15 @@ function classifyFailure(status, bodyText, headers = {}) {
|
|
|
28603
28804
|
retryAfterMs: retryAfter
|
|
28604
28805
|
};
|
|
28605
28806
|
switch (envelope.type) {
|
|
28606
|
-
case ERROR_TYPE.AUTHENTICATION:
|
|
28607
28807
|
case ERROR_TYPE.PERMISSION: return {
|
|
28808
|
+
...base,
|
|
28809
|
+
kind: "request",
|
|
28810
|
+
message: envelope.message ?? "This Claude account is not permitted to use the requested resource.",
|
|
28811
|
+
retryable: false,
|
|
28812
|
+
accountScoped: false,
|
|
28813
|
+
resetsAt: null
|
|
28814
|
+
};
|
|
28815
|
+
case ERROR_TYPE.AUTHENTICATION: return {
|
|
28608
28816
|
...base,
|
|
28609
28817
|
kind: "credential",
|
|
28610
28818
|
message: envelope.message ?? "Claude rejected the stored credential. Sign in again.",
|
|
@@ -28762,7 +28970,9 @@ function classifyFailure(status, bodyText, headers = {}) {
|
|
|
28762
28970
|
* everything, including a caller asking for thinking off: the
|
|
28763
28971
|
* block_binding exists so a prefix mismatch is dropped instead of
|
|
28764
28972
|
* surfacing as a persistent 400, and suppressing it is exactly
|
|
28765
|
-
* how that 400 comes back.
|
|
28973
|
+
* how that 400 comes back. The field is itself a 400 unless the
|
|
28974
|
+
* request carries the thinking-binding-controls beta, so the
|
|
28975
|
+
* adapter reads claudeBodyBindsThinking(body) to add it.
|
|
28766
28976
|
* adaptive -> { type: 'adaptive', display: 'summarized' }, plus
|
|
28767
28977
|
* output_config = { effort } when an effort was named. The budget
|
|
28768
28978
|
* form is rejected by these models.
|
|
@@ -28855,11 +29065,12 @@ function classifyFailure(status, bodyText, headers = {}) {
|
|
|
28855
29065
|
* 'error' event THROWS, because a severed reply flushed as a clean stop is a
|
|
28856
29066
|
* wrong answer rather than a visible failure.
|
|
28857
29067
|
*
|
|
28858
|
-
* Reasoning deltas are
|
|
28859
|
-
*
|
|
28860
|
-
* block may be replayed
|
|
28861
|
-
*
|
|
28862
|
-
*
|
|
29068
|
+
* Reasoning deltas are STREAMED as they arrive (see openThinkingBlock); only the
|
|
29069
|
+
* replay entry waits for content_block_stop, because the signature that decides
|
|
29070
|
+
* whether the block may be replayed arrives at the END of the block. They used
|
|
29071
|
+
* to be buffered until the stop, which made DSH start the step's first-token
|
|
29072
|
+
* clock after all thinking was done - so TPS divided every thinking token by the
|
|
29073
|
+
* answer's time alone, and TTFT absorbed the whole thinking phase.
|
|
28863
29074
|
*
|
|
28864
29075
|
* ---------------------------------------------------------------------------
|
|
28865
29076
|
* 7. PROMPT CACHING IS ON BY DEFAULT, AND IT IS THE REQUEST THAT TURNS IT ON
|
|
@@ -28958,9 +29169,17 @@ function jsonSafeValue(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
28958
29169
|
seen.delete(value);
|
|
28959
29170
|
}
|
|
28960
29171
|
}
|
|
28961
|
-
/**
|
|
29172
|
+
/**
|
|
29173
|
+
* Strip what the wire cannot carry: a NUL, which would truncate the string
|
|
29174
|
+
* inside a JSON encoder, and an UNPAIRED surrogate. JSON.stringify writes the
|
|
29175
|
+
* latter as a lone `\uD83D` escape, which upstream rejects as invalid JSON ("no
|
|
29176
|
+
* low surrogate in string") - and since the text sits in history, every later
|
|
29177
|
+
* request fails the same way. A tool output sliced mid-emoji is enough to
|
|
29178
|
+
* produce one. Paired surrogates (emoji) are untouched. Same rule as the
|
|
29179
|
+
* reference's sanitizeSurrogates.
|
|
29180
|
+
*/
|
|
28962
29181
|
function sanitizeText(text) {
|
|
28963
|
-
return text.replace(/\0/g, "");
|
|
29182
|
+
return text.replace(/\0|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
|
|
28964
29183
|
}
|
|
28965
29184
|
/**
|
|
28966
29185
|
* Whether one failure is a cancellation rather than a real read failure.
|
|
@@ -29175,7 +29394,8 @@ function claudeThinking(model, effort, level, maxTokens, customBudgets) {
|
|
|
29175
29394
|
if (mode === "adaptive") return {
|
|
29176
29395
|
thinking: {
|
|
29177
29396
|
type: "adaptive",
|
|
29178
|
-
display: "summarized"
|
|
29397
|
+
display: "summarized",
|
|
29398
|
+
...claudeModelBindsThinkingToPrefix(model) ? { block_binding: { prefix_mismatch_behavior: "drop_block" } } : {}
|
|
29179
29399
|
},
|
|
29180
29400
|
...namedEffort === void 0 ? {} : { outputConfig: { effort: namedEffort } }
|
|
29181
29401
|
};
|
|
@@ -29738,9 +29958,10 @@ function toolResultContent(blocks, images) {
|
|
|
29738
29958
|
for (const block of blocks) {
|
|
29739
29959
|
if (!isRecord$5(block)) continue;
|
|
29740
29960
|
if (block.type === "text" && typeof block.text === "string") {
|
|
29741
|
-
|
|
29961
|
+
const text = sanitizeText(block.text);
|
|
29962
|
+
if (text !== "") out.push({
|
|
29742
29963
|
type: "text",
|
|
29743
|
-
text
|
|
29964
|
+
text
|
|
29744
29965
|
});
|
|
29745
29966
|
continue;
|
|
29746
29967
|
}
|
|
@@ -29760,7 +29981,7 @@ function toolResultContent(blocks, images) {
|
|
|
29760
29981
|
if (!hasImage) return void 0;
|
|
29761
29982
|
if (out[0]?.type !== "text") out.unshift({
|
|
29762
29983
|
type: "text",
|
|
29763
|
-
text: ""
|
|
29984
|
+
text: "(see attached image)"
|
|
29764
29985
|
});
|
|
29765
29986
|
return out;
|
|
29766
29987
|
}
|
|
@@ -29889,6 +30110,17 @@ function buildClaudeRequestBody(options, images = NO_RESOLVED_IMAGES, request =
|
|
|
29889
30110
|
return body;
|
|
29890
30111
|
}
|
|
29891
30112
|
/**
|
|
30113
|
+
* Whether one built body carries `thinking.block_binding`.
|
|
30114
|
+
*
|
|
30115
|
+
* Read off the BODY rather than re-derived from the catalog, because the header
|
|
30116
|
+
* this answers for (THINKING_BINDING_CONTROLS_BETA) exists to license that one
|
|
30117
|
+
* field: sent without it the field is a 400 on every request, so the header must
|
|
30118
|
+
* follow the bytes, not a second copy of the rule that produced them.
|
|
30119
|
+
*/
|
|
30120
|
+
function claudeBodyBindsThinking(body) {
|
|
30121
|
+
return isRecord$5(body.thinking) && body.thinking.block_binding !== void 0;
|
|
30122
|
+
}
|
|
30123
|
+
/**
|
|
29892
30124
|
* Fresh stream state.
|
|
29893
30125
|
*
|
|
29894
30126
|
* @param toolNames - the SAME ClaudeToolNames the request was built with, so a
|
|
@@ -29913,6 +30145,7 @@ function createStreamState$1(toolNames) {
|
|
|
29913
30145
|
cacheReadTokens: 0,
|
|
29914
30146
|
cacheWriteTokens: 0,
|
|
29915
30147
|
reasoningTokens: 0,
|
|
30148
|
+
reportedReasoningTokens: void 0,
|
|
29916
30149
|
sawUsage: false,
|
|
29917
30150
|
finished: false
|
|
29918
30151
|
};
|
|
@@ -30009,14 +30242,15 @@ function closeToolCall(state, contentIndex) {
|
|
|
30009
30242
|
}];
|
|
30010
30243
|
}
|
|
30011
30244
|
/**
|
|
30012
|
-
*
|
|
30245
|
+
* Close one thinking block: end it for the caller and record its replay entry.
|
|
30013
30246
|
*
|
|
30014
|
-
*
|
|
30015
|
-
*
|
|
30016
|
-
*
|
|
30017
|
-
*
|
|
30247
|
+
* Its text already reached the caller as it streamed (see openThinkingBlock);
|
|
30248
|
+
* what waits for the close is the replay entry, because the signature arrives
|
|
30249
|
+
* last. A signed block is replayed verbatim afterwards. An unsigned one is
|
|
30250
|
+
* still shown (the model did think) but the request builder drops it on the
|
|
30251
|
+
* next turn, since it cannot be sent back.
|
|
30018
30252
|
*/
|
|
30019
|
-
function
|
|
30253
|
+
function closeThinkingBlock(state, contentIndex) {
|
|
30020
30254
|
const pending = state.pending.get(contentIndex);
|
|
30021
30255
|
if (pending === void 0) return [];
|
|
30022
30256
|
state.pending.delete(contentIndex);
|
|
@@ -30036,20 +30270,45 @@ function emitPendingThinking(state, contentIndex) {
|
|
|
30036
30270
|
...pending.signature === "" ? {} : { signature: pending.signature }
|
|
30037
30271
|
};
|
|
30038
30272
|
state.reasoningTokens += estimateReasoningTokens(text);
|
|
30273
|
+
return [{
|
|
30274
|
+
type: "block-end",
|
|
30275
|
+
index: pending.index,
|
|
30276
|
+
block
|
|
30277
|
+
}];
|
|
30278
|
+
}
|
|
30279
|
+
/**
|
|
30280
|
+
* Open one thinking block and stream whatever text its start already carries.
|
|
30281
|
+
*
|
|
30282
|
+
* STREAMED, not held to content_block_stop: DSH times a step's first token from
|
|
30283
|
+
* the first non-empty delta, and the usage it divides by that window counts every
|
|
30284
|
+
* thinking token. Holding the text until the block closed started the clock
|
|
30285
|
+
* after all the thinking was done - inflating TPS and TTFT alike - and kept the
|
|
30286
|
+
* UI blank for the whole thinking phase. Only the replay entry waits for the
|
|
30287
|
+
* close, because the signature arrives last.
|
|
30288
|
+
*/
|
|
30289
|
+
function openThinkingBlock(state, contentIndex, block, kind) {
|
|
30290
|
+
const index = state.blocks.length;
|
|
30291
|
+
state.blocks.push({
|
|
30292
|
+
type: "reasoning",
|
|
30293
|
+
text: ""
|
|
30294
|
+
});
|
|
30295
|
+
const seed = kind === "thinking" ? asString$2(block.thinking) ?? "" : "";
|
|
30296
|
+
state.pending.set(contentIndex, {
|
|
30297
|
+
index,
|
|
30298
|
+
kind,
|
|
30299
|
+
text: seed,
|
|
30300
|
+
signature: asString$2(block.signature) ?? "",
|
|
30301
|
+
data: asString$2(block.data) ?? ""
|
|
30302
|
+
});
|
|
30039
30303
|
const out = [{
|
|
30040
30304
|
type: "block-start",
|
|
30041
|
-
index
|
|
30305
|
+
index,
|
|
30042
30306
|
blockType: "reasoning"
|
|
30043
30307
|
}];
|
|
30044
|
-
if (
|
|
30308
|
+
if (seed !== "") out.push({
|
|
30045
30309
|
type: "reasoning-delta",
|
|
30046
|
-
index
|
|
30047
|
-
text
|
|
30048
|
-
});
|
|
30049
|
-
out.push({
|
|
30050
|
-
type: "block-end",
|
|
30051
|
-
index: pending.index,
|
|
30052
|
-
block
|
|
30310
|
+
index,
|
|
30311
|
+
text: seed
|
|
30053
30312
|
});
|
|
30054
30313
|
return out;
|
|
30055
30314
|
}
|
|
@@ -30066,7 +30325,7 @@ function flushOpenBlocks(state) {
|
|
|
30066
30325
|
});
|
|
30067
30326
|
for (const [contentIndex, pending] of state.pending) closers.push({
|
|
30068
30327
|
index: pending.index,
|
|
30069
|
-
close: () =>
|
|
30328
|
+
close: () => closeThinkingBlock(state, contentIndex)
|
|
30070
30329
|
});
|
|
30071
30330
|
closers.sort((left, right) => left.index - right.index);
|
|
30072
30331
|
const out = [];
|
|
@@ -30085,17 +30344,25 @@ function flushOpenBlocks(state) {
|
|
|
30085
30344
|
* the billed-input total unaccountable.
|
|
30086
30345
|
*/
|
|
30087
30346
|
function tokenUsage(state) {
|
|
30347
|
+
const reasoningTokens = Math.min(state.reportedReasoningTokens ?? state.reasoningTokens, state.outputTokens);
|
|
30088
30348
|
return {
|
|
30089
30349
|
inputTokens: state.inputTokens,
|
|
30090
30350
|
outputTokens: state.outputTokens,
|
|
30091
30351
|
...state.cacheReadTokens > 0 ? { cacheReadTokens: state.cacheReadTokens } : {},
|
|
30092
30352
|
...state.cacheWriteTokens > 0 ? { cacheWriteTokens: state.cacheWriteTokens } : {},
|
|
30093
|
-
...
|
|
30353
|
+
...reasoningTokens > 0 ? { reasoningTokens } : {}
|
|
30094
30354
|
};
|
|
30095
30355
|
}
|
|
30096
30356
|
function finishReasonFor(state) {
|
|
30097
30357
|
const reason = state.finishReason ?? "";
|
|
30098
|
-
if (reason === "
|
|
30358
|
+
if (reason === "refusal" || reason === "sensitive") return {
|
|
30359
|
+
kind: "error",
|
|
30360
|
+
failure: {
|
|
30361
|
+
message: state.stopExplanation ?? "Claude declined to respond (stop_reason: " + reason + ").",
|
|
30362
|
+
code: "PROVIDER_ERROR"
|
|
30363
|
+
}
|
|
30364
|
+
};
|
|
30365
|
+
if (reason === "max_tokens" || reason === "model_context_window_exceeded") return { kind: "max-tokens" };
|
|
30099
30366
|
if (state.hasToolCall || reason === "tool_use") return { kind: "tool-calls" };
|
|
30100
30367
|
return { kind: "stop" };
|
|
30101
30368
|
}
|
|
@@ -30143,6 +30410,8 @@ function applyUsage(state, usage, cumulativeOutput) {
|
|
|
30143
30410
|
if (usage.cache_creation_input_tokens !== void 0) state.cacheWriteTokens = numberOr(usage.cache_creation_input_tokens, state.cacheWriteTokens);
|
|
30144
30411
|
if (usage.cache_read_input_tokens !== void 0) state.cacheReadTokens = numberOr(usage.cache_read_input_tokens, state.cacheReadTokens);
|
|
30145
30412
|
if (usage.output_tokens !== void 0) state.outputTokens = cumulativeOutput ? numberOr(usage.output_tokens, state.outputTokens) : state.outputTokens + numberOr(usage.output_tokens, 0);
|
|
30413
|
+
const details = isRecord$5(usage.output_tokens_details) ? usage.output_tokens_details : void 0;
|
|
30414
|
+
if (details !== void 0 && details.thinking_tokens !== void 0) state.reportedReasoningTokens = numberOr(details.thinking_tokens, state.reportedReasoningTokens ?? 0);
|
|
30146
30415
|
}
|
|
30147
30416
|
/**
|
|
30148
30417
|
* Feed one SSE line of a Messages response into the state.
|
|
@@ -30213,19 +30482,8 @@ function processStreamLine(line, state) {
|
|
|
30213
30482
|
return out;
|
|
30214
30483
|
}
|
|
30215
30484
|
if (blockType === "thinking" || blockType === "redacted_thinking") {
|
|
30216
|
-
|
|
30217
|
-
|
|
30218
|
-
state.blocks.push({
|
|
30219
|
-
type: "reasoning",
|
|
30220
|
-
text: ""
|
|
30221
|
-
});
|
|
30222
|
-
state.pending.set(contentIndex, {
|
|
30223
|
-
index,
|
|
30224
|
-
kind: blockType === "redacted_thinking" ? "redacted_thinking" : "thinking",
|
|
30225
|
-
text: asString$2(block.thinking) ?? "",
|
|
30226
|
-
signature: asString$2(block.signature) ?? "",
|
|
30227
|
-
data: asString$2(block.data) ?? ""
|
|
30228
|
-
});
|
|
30485
|
+
out.push(...closeThinkingBlock(state, contentIndex));
|
|
30486
|
+
out.push(...openThinkingBlock(state, contentIndex, block, blockType));
|
|
30229
30487
|
return out;
|
|
30230
30488
|
}
|
|
30231
30489
|
out.push(...openTextAt(state, contentIndex).chunks);
|
|
@@ -30258,8 +30516,14 @@ function processStreamLine(line, state) {
|
|
|
30258
30516
|
}
|
|
30259
30517
|
if (deltaType === "thinking_delta") {
|
|
30260
30518
|
const pending = state.pending.get(contentIndex);
|
|
30261
|
-
const text = asString$2(delta.thinking) ?? "";
|
|
30262
|
-
if (pending
|
|
30519
|
+
const text = sanitizeText(asString$2(delta.thinking) ?? "");
|
|
30520
|
+
if (pending === void 0 || pending.kind !== "thinking" || text === "") return out;
|
|
30521
|
+
pending.text += text;
|
|
30522
|
+
out.push({
|
|
30523
|
+
type: "reasoning-delta",
|
|
30524
|
+
index: pending.index,
|
|
30525
|
+
text
|
|
30526
|
+
});
|
|
30263
30527
|
return out;
|
|
30264
30528
|
}
|
|
30265
30529
|
const text = asString$2(delta.text) ?? "";
|
|
@@ -30278,13 +30542,16 @@ function processStreamLine(line, state) {
|
|
|
30278
30542
|
if (type === "content_block_stop") {
|
|
30279
30543
|
const contentIndex = numberOr(event.index, -1);
|
|
30280
30544
|
if (state.toolCalls.has(contentIndex)) return closeToolCall(state, contentIndex);
|
|
30281
|
-
if (state.pending.has(contentIndex)) return
|
|
30545
|
+
if (state.pending.has(contentIndex)) return closeThinkingBlock(state, contentIndex);
|
|
30282
30546
|
return closeTextBlock(state, contentIndex);
|
|
30283
30547
|
}
|
|
30284
30548
|
if (type === "message_delta") {
|
|
30285
30549
|
const delta = isRecord$5(event.delta) ? event.delta : void 0;
|
|
30286
30550
|
const stop = delta === void 0 ? void 0 : asString$2(delta.stop_reason);
|
|
30287
30551
|
if (stop !== void 0 && stop !== "") state.finishReason = stop;
|
|
30552
|
+
const details = delta !== void 0 && isRecord$5(delta.stop_details) ? delta.stop_details : void 0;
|
|
30553
|
+
const explanation = details === void 0 ? void 0 : asString$2(details.explanation);
|
|
30554
|
+
if (explanation !== void 0 && explanation !== "") state.stopExplanation = explanation;
|
|
30288
30555
|
const usage = isRecord$5(event.usage) ? event.usage : void 0;
|
|
30289
30556
|
if (usage !== void 0) applyUsage(state, usage, true);
|
|
30290
30557
|
return out;
|
|
@@ -30719,7 +30986,7 @@ async function refreshAccessToken(refreshToken, options = {}) {
|
|
|
30719
30986
|
try {
|
|
30720
30987
|
return await postTokenGrant(body, options, false, [refreshToken]);
|
|
30721
30988
|
} catch (error) {
|
|
30722
|
-
if (error instanceof
|
|
30989
|
+
if (!(error instanceof ClaudeRetryableError)) throw error;
|
|
30723
30990
|
lastError = error;
|
|
30724
30991
|
}
|
|
30725
30992
|
if (attempt < REFRESH_MAX_ATTEMPTS - 1) await sleep(REFRESH_BACKOFF_BASE_MS * 2 ** attempt, options.signal);
|
|
@@ -31804,19 +32071,20 @@ var ClaudeAdapter = class extends LlmAdapter {
|
|
|
31804
32071
|
* no credentials were resolved for it.
|
|
31805
32072
|
*/
|
|
31806
32073
|
async attemptRequest(credentials, requestOptions, images, toolNames, thinking, signal, fetchFn) {
|
|
31807
|
-
const
|
|
32074
|
+
const payload = buildClaudeRequestBody(requestOptions, images, {
|
|
31808
32075
|
toolNames,
|
|
31809
32076
|
cacheControl: true
|
|
31810
|
-
})
|
|
32077
|
+
});
|
|
31811
32078
|
try {
|
|
31812
32079
|
return await fetchFn("https://api.anthropic.com/v1/messages?beta=true", {
|
|
31813
32080
|
method: "POST",
|
|
31814
32081
|
headers: buildClaudeHeaders(credentials.accessToken, {
|
|
31815
32082
|
model: requestOptions.model,
|
|
31816
32083
|
thinking,
|
|
32084
|
+
thinkingBinding: claudeBodyBindsThinking(payload),
|
|
31817
32085
|
method: "POST"
|
|
31818
32086
|
}),
|
|
31819
|
-
body,
|
|
32087
|
+
body: JSON.stringify(payload),
|
|
31820
32088
|
signal
|
|
31821
32089
|
});
|
|
31822
32090
|
} catch (error) {
|
|
@@ -32467,7 +32735,7 @@ function isAdoptedCredentialExpired(credential, now = Date.now()) {
|
|
|
32467
32735
|
* caller in the process, so with a pool it would hand the second account the
|
|
32468
32736
|
* first account's freshly rotated credential — and, through the adapter above,
|
|
32469
32737
|
* write it into the second account's record. The pool therefore keeps its OWN
|
|
32470
|
-
* single-flight, per
|
|
32738
|
+
* single-flight, per pool file and account, which preserves the property
|
|
32471
32739
|
* the frozen module wanted (one rotation per expiry, no two callers spending the
|
|
32472
32740
|
* same rotating token) without the cross-account contamination.
|
|
32473
32741
|
*
|
|
@@ -32853,7 +33121,6 @@ var ClaudeAccountPool = class extends AccountPoolCore {
|
|
|
32853
33121
|
* marked expired immediately after a refresh that in fact succeeded. Keyed by
|
|
32854
33122
|
* the record's id — never by a token — so it cannot mix two accounts up.
|
|
32855
33123
|
*/
|
|
32856
|
-
inFlight = /* @__PURE__ */ new Map();
|
|
32857
33124
|
constructor(options = {}) {
|
|
32858
33125
|
const store = options.store ?? new FileCredentialStore();
|
|
32859
33126
|
const refreshSeam = options.refreshCredentials ?? ((credentials, fetchFn) => refreshClaudeCredential(credentials, fetchFn));
|
|
@@ -32900,6 +33167,10 @@ var ClaudeAccountPool = class extends AccountPoolCore {
|
|
|
32900
33167
|
if (record === void 0) return null;
|
|
32901
33168
|
return poolAccountForRecord(record, true);
|
|
32902
33169
|
},
|
|
33170
|
+
credentialsCommitted: async (account) => {
|
|
33171
|
+
if (isAdoptedPoolCredential(account.credentials)) return;
|
|
33172
|
+
await store.saveAccount(account.credentials, { internalId: account.id });
|
|
33173
|
+
},
|
|
32903
33174
|
mirrorPrimary: async (credentials) => {
|
|
32904
33175
|
if (credentials === null) {
|
|
32905
33176
|
await store.delete();
|
|
@@ -32961,8 +33232,8 @@ var ClaudeAccountPool = class extends AccountPoolCore {
|
|
|
32961
33232
|
* {@link recordIdsFor} reads is kept in step with the rows that actually exist.
|
|
32962
33233
|
* See the recordIds field for why entries are not pruned.
|
|
32963
33234
|
*/
|
|
32964
|
-
async
|
|
32965
|
-
const data = await super.
|
|
33235
|
+
async loadPoolData() {
|
|
33236
|
+
const data = await super.loadPoolData();
|
|
32966
33237
|
for (const account of data.accounts) this.indexAccount(account);
|
|
32967
33238
|
return data;
|
|
32968
33239
|
}
|
|
@@ -32975,10 +33246,27 @@ var ClaudeAccountPool = class extends AccountPoolCore {
|
|
|
32975
33246
|
* 'one account, one key' section.
|
|
32976
33247
|
*/
|
|
32977
33248
|
async addAccount(credentials, alias) {
|
|
32978
|
-
|
|
33249
|
+
return this.updatePool((data) => this.addManagedAccount(data, credentials, alias)).catch((error) => {
|
|
33250
|
+
this.recordIds.clear();
|
|
33251
|
+
throw error;
|
|
33252
|
+
});
|
|
33253
|
+
}
|
|
33254
|
+
async addManagedAccount(data, credentials, alias) {
|
|
32979
33255
|
if (isAdoptedPoolCredential(credentials)) return this.addAdoptedAccount(credentials, data, alias);
|
|
33256
|
+
parseClaudeCredentials(credentials);
|
|
32980
33257
|
const knownId = await this.recordIdFor(credentials);
|
|
32981
|
-
const
|
|
33258
|
+
const document = await this.store.read();
|
|
33259
|
+
const keys = identityKeysFor(credentials);
|
|
33260
|
+
const stored = document?.accounts.find((record) => record.identityKeys.some((key) => keys.includes(key)));
|
|
33261
|
+
const record = stored === void 0 ? {
|
|
33262
|
+
internalId: data.accounts.find((account) => account.id === knownId)?.adopted === false ? knownId ?? createInternalId() : createInternalId(),
|
|
33263
|
+
identityKeys: keys,
|
|
33264
|
+
credentials
|
|
33265
|
+
} : {
|
|
33266
|
+
...stored,
|
|
33267
|
+
identityKeys: mergeIdentityKeys(stored.identityKeys, keys),
|
|
33268
|
+
credentials
|
|
33269
|
+
};
|
|
32982
33270
|
this.indexRecord(record);
|
|
32983
33271
|
if (knownId !== void 0 && knownId !== record.internalId) {
|
|
32984
33272
|
const row = data.accounts.find((account) => account.id === knownId);
|
|
@@ -32990,10 +33278,9 @@ var ClaudeAccountPool = class extends AccountPoolCore {
|
|
|
32990
33278
|
delete row.sourcePath;
|
|
32991
33279
|
row.identityKeys = mergeIdentityKeys(row.identityKeys, record.identityKeys);
|
|
32992
33280
|
if (data.activeAccountId === knownId) data.activeAccountId = record.internalId;
|
|
32993
|
-
await this.write(data);
|
|
32994
33281
|
}
|
|
32995
33282
|
}
|
|
32996
|
-
return
|
|
33283
|
+
return this.addAccountToPool(data, credentials, alias);
|
|
32997
33284
|
}
|
|
32998
33285
|
/**
|
|
32999
33286
|
* Add, or re-import, one borrowed Claude Code snapshot.
|
|
@@ -33022,9 +33309,7 @@ var ClaudeAccountPool = class extends AccountPoolCore {
|
|
|
33022
33309
|
const candidateId = byIdentity ?? this.recordIds.get(sourceKey);
|
|
33023
33310
|
const existing = candidateId === void 0 ? void 0 : data.accounts.find((account) => account.id === candidateId);
|
|
33024
33311
|
if (existing !== void 0 && existing.adopted !== true) {
|
|
33025
|
-
const before = existing.identityKeys.length;
|
|
33026
33312
|
existing.identityKeys = mergeIdentityKeys(existing.identityKeys, identityKeysFor(credentials));
|
|
33027
|
-
if (existing.identityKeys.length !== before) await this.write(data);
|
|
33028
33313
|
return existing;
|
|
33029
33314
|
}
|
|
33030
33315
|
if (candidateId === void 0) {
|
|
@@ -33035,7 +33320,7 @@ var ClaudeAccountPool = class extends AccountPoolCore {
|
|
|
33035
33320
|
this.recordIds.set(sourceKey, candidateId);
|
|
33036
33321
|
this.indexCredential(credentials, candidateId);
|
|
33037
33322
|
}
|
|
33038
|
-
return
|
|
33323
|
+
return this.addAccountToPool(data, credentials, alias);
|
|
33039
33324
|
}
|
|
33040
33325
|
/**
|
|
33041
33326
|
* Merge two rows that are one account.
|
|
@@ -33058,7 +33343,21 @@ var ClaudeAccountPool = class extends AccountPoolCore {
|
|
|
33058
33343
|
*/
|
|
33059
33344
|
async mergeAccounts(keepAccountId, dropAccountId) {
|
|
33060
33345
|
if (keepAccountId === dropAccountId) throw new Error("A Claude pool merge needs two different accounts.");
|
|
33061
|
-
|
|
33346
|
+
let originals;
|
|
33347
|
+
return await this.updatePool(async (data) => {
|
|
33348
|
+
const keep = data.accounts.find((account) => account.id === keepAccountId);
|
|
33349
|
+
const drop = data.accounts.find((account) => account.id === dropAccountId);
|
|
33350
|
+
if (keep === void 0 || drop === void 0) throw new Error("The Claude pool merge addressed an account the pool does not have.");
|
|
33351
|
+
originals = {
|
|
33352
|
+
keep,
|
|
33353
|
+
drop
|
|
33354
|
+
};
|
|
33355
|
+
return this.mergeAccountsInPool(data, keepAccountId, dropAccountId);
|
|
33356
|
+
}, async (_data, result) => {
|
|
33357
|
+
if (originals !== void 0) await this.reconcileMergedDocument(originals.keep, originals.drop, result);
|
|
33358
|
+
});
|
|
33359
|
+
}
|
|
33360
|
+
async mergeAccountsInPool(data, keepAccountId, dropAccountId) {
|
|
33062
33361
|
const keep = data.accounts.find((account) => account.id === keepAccountId);
|
|
33063
33362
|
const drop = data.accounts.find((account) => account.id === dropAccountId);
|
|
33064
33363
|
if (keep === void 0 || drop === void 0) throw new Error("The Claude pool merge addressed an account the pool does not have.");
|
|
@@ -33107,9 +33406,6 @@ var ClaudeAccountPool = class extends AccountPoolCore {
|
|
|
33107
33406
|
data.accounts[index] = merged;
|
|
33108
33407
|
data.accounts = data.accounts.filter((account) => account.id !== dropAccountId);
|
|
33109
33408
|
if (data.activeAccountId === dropAccountId) data.activeAccountId = merged.id;
|
|
33110
|
-
await this.write(data);
|
|
33111
|
-
if (merged.isPrimary) this.hooks.mirrorPrimary?.(merged.credentials).catch(() => void 0);
|
|
33112
|
-
await this.reconcileMergedDocument(keep, drop, merged);
|
|
33113
33409
|
return merged;
|
|
33114
33410
|
}
|
|
33115
33411
|
/**
|
|
@@ -33149,10 +33445,12 @@ var ClaudeAccountPool = class extends AccountPoolCore {
|
|
|
33149
33445
|
* neither path.
|
|
33150
33446
|
*/
|
|
33151
33447
|
async removeImportedAccount(accountId) {
|
|
33152
|
-
|
|
33153
|
-
|
|
33154
|
-
|
|
33155
|
-
|
|
33448
|
+
return this.updatePool((data) => {
|
|
33449
|
+
const target = data.accounts.find((account) => account.id === accountId);
|
|
33450
|
+
if (target === void 0) return;
|
|
33451
|
+
if (target.adopted !== true) throw new Error("That account was signed in through this plugin; delete it instead of removing an import.");
|
|
33452
|
+
super.deleteAccountFromPool(data, accountId);
|
|
33453
|
+
});
|
|
33156
33454
|
}
|
|
33157
33455
|
/**
|
|
33158
33456
|
* Delete one plugin-owned account.
|
|
@@ -33165,9 +33463,12 @@ var ClaudeAccountPool = class extends AccountPoolCore {
|
|
|
33165
33463
|
* back a credential the user just deleted.
|
|
33166
33464
|
*/
|
|
33167
33465
|
async deleteAccount(accountId) {
|
|
33168
|
-
|
|
33169
|
-
|
|
33170
|
-
|
|
33466
|
+
return this.updatePool((data) => {
|
|
33467
|
+
if (data.accounts.find((account) => account.id === accountId)?.adopted === true) throw new Error("The sign-in imported from Claude Code is not an account this plugin owns; remove the import instead.");
|
|
33468
|
+
super.deleteAccountFromPool(data, accountId);
|
|
33469
|
+
}, async () => {
|
|
33470
|
+
await this.store.deleteAccount(accountId);
|
|
33471
|
+
});
|
|
33171
33472
|
}
|
|
33172
33473
|
/**
|
|
33173
33474
|
* The immutable id of the record that owns a credential, or nothing.
|
|
@@ -33247,44 +33548,7 @@ var ClaudeAccountPool = class extends AccountPoolCore {
|
|
|
33247
33548
|
*/
|
|
33248
33549
|
refreshCredentials(credentials, fetchFn) {
|
|
33249
33550
|
if (isAdoptedPoolCredential(credentials)) return Promise.reject(new ClaudeAdoptedCredentialError(adoptedCredentialExpired(credentials) ? ADOPTED_CREDENTIAL_EXPIRED_HINT : ADOPTED_NEVER_REFRESHED_MESSAGE));
|
|
33250
|
-
return this.
|
|
33251
|
-
}
|
|
33252
|
-
/**
|
|
33253
|
-
* The per-account single-flight, resolving the account's id first.
|
|
33254
|
-
*
|
|
33255
|
-
* Separated from the hook above only because resolving the id consults the
|
|
33256
|
-
* credential document, and a hook cannot await: the guard stays synchronous so
|
|
33257
|
-
* it can never be skipped by a caller that forgets to wait.
|
|
33258
|
-
*/
|
|
33259
|
-
async singleFlightRefresh(credentials, fetchFn) {
|
|
33260
|
-
const key = await this.recordIdFor(credentials);
|
|
33261
|
-
if (key !== void 0) {
|
|
33262
|
-
const shared = this.inFlight.get(key);
|
|
33263
|
-
if (shared !== void 0) return shared;
|
|
33264
|
-
}
|
|
33265
|
-
const pending = this.performRefresh(credentials, fetchFn);
|
|
33266
|
-
if (key !== void 0) {
|
|
33267
|
-
this.inFlight.set(key, pending);
|
|
33268
|
-
pending.then(() => void 0, () => void 0).then(() => {
|
|
33269
|
-
if (this.inFlight.get(key) === pending) this.inFlight.delete(key);
|
|
33270
|
-
});
|
|
33271
|
-
}
|
|
33272
|
-
return pending;
|
|
33273
|
-
}
|
|
33274
|
-
/**
|
|
33275
|
-
* Rotate one managed credential and keep the credential document in step.
|
|
33276
|
-
*
|
|
33277
|
-
* The document write is what stops the pre-pool projection from handing out a
|
|
33278
|
-
* token the rotation has already spent. Its failure is swallowed on purpose:
|
|
33279
|
-
* the credential WAS refreshed, the pool's own record holds it, and a mirror
|
|
33280
|
-
* that could not be written must not fail a request that can still be served —
|
|
33281
|
-
* the same rule the core applies to its own bookkeeping writes.
|
|
33282
|
-
*/
|
|
33283
|
-
async performRefresh(credentials, fetchFn) {
|
|
33284
|
-
const recordId = await this.recordIdFor(credentials);
|
|
33285
|
-
const refreshed = await this.refreshSeam(credentials, fetchFn);
|
|
33286
|
-
if (recordId !== void 0) await new ClaudeCredentialStoreAdapter(this.store, recordId).write(refreshed).catch(() => void 0);
|
|
33287
|
-
return refreshed;
|
|
33551
|
+
return this.refreshSeam(credentials, fetchFn);
|
|
33288
33552
|
}
|
|
33289
33553
|
};
|
|
33290
33554
|
//#endregion
|
|
@@ -33586,24 +33850,18 @@ function registerClaudeRoutes(ctx, store, modelSettings, preferences, options =
|
|
|
33586
33850
|
* is addressed at THAT account, which is what makes a re-login repair the row
|
|
33587
33851
|
* the user clicked instead of adding a second one.
|
|
33588
33852
|
*/
|
|
33589
|
-
const loginStore = (accountId) => {
|
|
33590
|
-
|
|
33591
|
-
|
|
33592
|
-
|
|
33593
|
-
|
|
33594
|
-
|
|
33595
|
-
|
|
33596
|
-
};
|
|
33597
|
-
return {
|
|
33598
|
-
read: () => base.read(),
|
|
33599
|
-
write: async (credentials) => {
|
|
33600
|
-
await base.write(credentials);
|
|
33601
|
-
if (pool !== void 0 && accountId !== void 0) await pool.clearAuthFailed(accountId).catch(() => void 0);
|
|
33602
|
-
clearCachedQuota$1();
|
|
33603
|
-
clearCachedCatalog();
|
|
33853
|
+
const loginStore = (accountId) => ({
|
|
33854
|
+
read: async () => pool !== void 0 && accountId !== void 0 ? pool.credentialStoreFor(accountId).read() : (await activeAccount()).credentials ?? null,
|
|
33855
|
+
write: async (credentials) => {
|
|
33856
|
+
if (pool === void 0) await store.saveAccount(credentials);
|
|
33857
|
+
else {
|
|
33858
|
+
const landed = await pool.addAccount(credentials);
|
|
33859
|
+
if (accountId !== void 0 && landed.id === accountId) await pool.clearAuthFailed(accountId).catch(() => void 0);
|
|
33604
33860
|
}
|
|
33605
|
-
|
|
33606
|
-
|
|
33861
|
+
clearCachedQuota$1();
|
|
33862
|
+
clearCachedCatalog();
|
|
33863
|
+
}
|
|
33864
|
+
});
|
|
33607
33865
|
const readStatus = (quotaError = null) => activeAccount().then((active) => getClaudeWebStatus(store, modelSettings, preferences, options, quotaError, active));
|
|
33608
33866
|
return ctx.webServer.register({
|
|
33609
33867
|
kind: "prefix",
|
|
@@ -37008,9 +37266,9 @@ function appendWindow(current, addition, limit) {
|
|
|
37008
37266
|
/**
|
|
37009
37267
|
* Install the reasoning-collapse guard on a Context.
|
|
37010
37268
|
*
|
|
37011
|
-
* The guard is inert unless the host exposes the
|
|
37012
|
-
*
|
|
37013
|
-
*
|
|
37269
|
+
* The guard is inert unless the host exposes the event bus, so a harness
|
|
37270
|
+
* without it keeps the built-in behaviour instead of failing this plugin's
|
|
37271
|
+
* load.
|
|
37014
37272
|
*
|
|
37015
37273
|
* Resuming needs the live Agent for `cancel` and `steer`, and no supported
|
|
37016
37274
|
* generation hands the agent to `llm/stream`. `agent/created` reports it on
|
|
@@ -37020,19 +37278,19 @@ function appendWindow(current, addition, limit) {
|
|
|
37020
37278
|
*
|
|
37021
37279
|
* @param ctx - plugin context.
|
|
37022
37280
|
* @param options - see {@link GuardOptions}; validated fail-loud.
|
|
37023
|
-
* @returns a disposer, or undefined when the
|
|
37281
|
+
* @returns a disposer removing both listeners, or undefined when the host
|
|
37282
|
+
* exposes no event bus.
|
|
37024
37283
|
*/
|
|
37025
37284
|
function installReasoningCollapseGuard(ctx, options = {}) {
|
|
37026
37285
|
const resolved = resolveGuardOptions(options);
|
|
37027
|
-
|
|
37028
|
-
if (runtime === void 0 || typeof runtime.stream !== "function") return void 0;
|
|
37029
|
-
const original = runtime.stream;
|
|
37286
|
+
if (typeof ctx.on !== "function") return void 0;
|
|
37030
37287
|
const watched = new Set(resolved.includeModels);
|
|
37031
37288
|
const breakers = /* @__PURE__ */ new WeakMap();
|
|
37032
37289
|
let currentAgent;
|
|
37033
|
-
const releaseAgent =
|
|
37034
|
-
currentAgent = agent;
|
|
37035
|
-
})
|
|
37290
|
+
const releaseAgent = ctx.on("agent/created", (payload) => {
|
|
37291
|
+
currentAgent = payload.agent;
|
|
37292
|
+
});
|
|
37293
|
+
const releaseStream = ctx.on("llm/stream", guardStream, { global: true });
|
|
37036
37294
|
function breakerFor(key) {
|
|
37037
37295
|
let state = breakers.get(key);
|
|
37038
37296
|
if (state === void 0) {
|
|
@@ -37070,7 +37328,7 @@ function installReasoningCollapseGuard(ctx, options = {}) {
|
|
|
37070
37328
|
agent.steer?.(createResumeMessage(text));
|
|
37071
37329
|
});
|
|
37072
37330
|
}
|
|
37073
|
-
|
|
37331
|
+
function guardStream(request, next) {
|
|
37074
37332
|
const state = breakerFor(request.signal ?? STATIC_KEY);
|
|
37075
37333
|
if (state.exhausted || !watchedModel(request.model)) return next();
|
|
37076
37334
|
async function* guarded() {
|
|
@@ -37092,9 +37350,9 @@ function installReasoningCollapseGuard(ctx, options = {}) {
|
|
|
37092
37350
|
}
|
|
37093
37351
|
}
|
|
37094
37352
|
return guarded();
|
|
37095
|
-
}
|
|
37353
|
+
}
|
|
37096
37354
|
return () => {
|
|
37097
|
-
|
|
37355
|
+
releaseListener(releaseStream);
|
|
37098
37356
|
releaseListener(releaseAgent);
|
|
37099
37357
|
};
|
|
37100
37358
|
}
|
|
@@ -37186,7 +37444,7 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
37186
37444
|
ctx.effect(() => {
|
|
37187
37445
|
const dispose = installReasoningCollapseGuard(ctx, guardOptions);
|
|
37188
37446
|
if (dispose === void 0) {
|
|
37189
|
-
ctx.logger.warn("[dsh-chatgpt-subscription] reasoning-collapse guard skipped: this harness exposes no
|
|
37447
|
+
ctx.logger.warn("[dsh-chatgpt-subscription] reasoning-collapse guard skipped: this harness exposes no event bus");
|
|
37190
37448
|
return () => void 0;
|
|
37191
37449
|
}
|
|
37192
37450
|
return dispose;
|