dsh-codex-subscription 1.12.2 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -4
- package/README.en.md +4 -4
- package/README.md +4 -4
- package/lib/client.js +341 -262
- package/lib/index.js +119 -30
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -21,6 +21,40 @@ import { dirname, join, resolve } from "node:path";
|
|
|
21
21
|
const VERSION = 1;
|
|
22
22
|
const DEFAULT_LABEL = "Account 1";
|
|
23
23
|
const clone$1 = (value) => value === void 0 ? void 0 : structuredClone(value);
|
|
24
|
+
const EMAIL_MAX_LENGTH = 254;
|
|
25
|
+
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
|
|
26
|
+
/** Keep only a bounded, display-safe email address from a trusted OAuth result. */
|
|
27
|
+
function normalizeAccountEmail(value) {
|
|
28
|
+
if (typeof value !== "string") return void 0;
|
|
29
|
+
const email = value.trim();
|
|
30
|
+
return email.length > 0 && email.length <= EMAIL_MAX_LENGTH && EMAIL_PATTERN.test(email) ? email : void 0;
|
|
31
|
+
}
|
|
32
|
+
function decodeJwtPayload(access) {
|
|
33
|
+
if (typeof access !== "string") return void 0;
|
|
34
|
+
const encoded = access.split(".")[1];
|
|
35
|
+
if (typeof encoded !== "string" || encoded.length === 0) return void 0;
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
38
|
+
} catch {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function emailFromAccessToken(access) {
|
|
43
|
+
return normalizeAccountEmail(decodeJwtPayload(access)?.email);
|
|
44
|
+
}
|
|
45
|
+
/** Normalize the one non-secret account attribute that may cross the UI boundary. */
|
|
46
|
+
function sanitizeOAuthCredential(value) {
|
|
47
|
+
const credential = assertOAuthCredential$1(value);
|
|
48
|
+
const email = emailFromAccessToken(credential.access) ?? normalizeAccountEmail(credential.email);
|
|
49
|
+
if (email === void 0) {
|
|
50
|
+
delete credential.email;
|
|
51
|
+
return credential;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
...credential,
|
|
55
|
+
email
|
|
56
|
+
};
|
|
57
|
+
}
|
|
24
58
|
function assertOAuthCredential$1(value) {
|
|
25
59
|
if (value === null || typeof value !== "object" || value.type !== "oauth" || typeof value.access !== "string" || value.access.length === 0 || typeof value.refresh !== "string" || value.refresh.length === 0 || typeof value.expires !== "number" || !Number.isFinite(value.expires)) throw new Error("Codex account vault received a malformed OAuth credential");
|
|
26
60
|
return clone$1(value);
|
|
@@ -48,7 +82,7 @@ function assertVaultRecord(record) {
|
|
|
48
82
|
return {
|
|
49
83
|
id: account.id,
|
|
50
84
|
label: normalizeLabel(account.label),
|
|
51
|
-
credential:
|
|
85
|
+
credential: sanitizeOAuthCredential(account.credential)
|
|
52
86
|
};
|
|
53
87
|
});
|
|
54
88
|
if (!ids.has(record.payload.activeId)) throw new Error("Codex account vault active account is missing");
|
|
@@ -80,7 +114,7 @@ var PendingOAuthCredentialStore = class {
|
|
|
80
114
|
async modify(providerId, update) {
|
|
81
115
|
if (providerId !== "openai-codex") throw new Error("Pending Codex login received an unknown provider");
|
|
82
116
|
const next = await update(clone$1(this.#credential));
|
|
83
|
-
if (next !== void 0) this.#credential =
|
|
117
|
+
if (next !== void 0) this.#credential = sanitizeOAuthCredential(next);
|
|
84
118
|
return clone$1(this.#credential);
|
|
85
119
|
}
|
|
86
120
|
async delete(providerId) {
|
|
@@ -170,7 +204,8 @@ var DshOAuthAccountVault = class {
|
|
|
170
204
|
id: account.id,
|
|
171
205
|
label: account.label,
|
|
172
206
|
active: account.id === payload.activeId,
|
|
173
|
-
expiresAt: account.credential.expires
|
|
207
|
+
expiresAt: account.credential.expires,
|
|
208
|
+
...account.credential.email === void 0 ? {} : { email: account.credential.email }
|
|
174
209
|
}));
|
|
175
210
|
});
|
|
176
211
|
}
|
|
@@ -186,7 +221,7 @@ var DshOAuthAccountVault = class {
|
|
|
186
221
|
add(label, credential) {
|
|
187
222
|
return this.#enqueue(async () => {
|
|
188
223
|
const normalizedLabel = normalizeLabel(label);
|
|
189
|
-
const validated =
|
|
224
|
+
const validated = sanitizeOAuthCredential(credential);
|
|
190
225
|
await this.#ensurePayload();
|
|
191
226
|
const id = this.createId();
|
|
192
227
|
const account = (await this.#modifyPayload((current) => ({
|
|
@@ -202,7 +237,8 @@ var DshOAuthAccountVault = class {
|
|
|
202
237
|
id,
|
|
203
238
|
label: account.label,
|
|
204
239
|
active: true,
|
|
205
|
-
expiresAt: account.credential.expires
|
|
240
|
+
expiresAt: account.credential.expires,
|
|
241
|
+
...account.credential.email === void 0 ? {} : { email: account.credential.email }
|
|
206
242
|
};
|
|
207
243
|
});
|
|
208
244
|
}
|
|
@@ -222,7 +258,7 @@ var DshOAuthAccountVault = class {
|
|
|
222
258
|
if (await this.#ensurePayload() === void 0) {
|
|
223
259
|
const initial = await update(void 0);
|
|
224
260
|
if (initial === void 0) return void 0;
|
|
225
|
-
const credential =
|
|
261
|
+
const credential = sanitizeOAuthCredential(initial);
|
|
226
262
|
await this.credentials.set(this.legacyRef, JSON.stringify(credential));
|
|
227
263
|
await this.#ensurePayload();
|
|
228
264
|
return clone$1(credential);
|
|
@@ -236,7 +272,8 @@ var DshOAuthAccountVault = class {
|
|
|
236
272
|
result = previous;
|
|
237
273
|
return current;
|
|
238
274
|
}
|
|
239
|
-
const credential =
|
|
275
|
+
const credential = sanitizeOAuthCredential(next);
|
|
276
|
+
if (credential.email === void 0 && previous.email !== void 0) credential.email = previous.email;
|
|
240
277
|
const accounts = [...current.accounts];
|
|
241
278
|
accounts[index] = {
|
|
242
279
|
...accounts[index],
|
|
@@ -1265,7 +1302,7 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
|
|
|
1265
1302
|
}
|
|
1266
1303
|
//#endregion
|
|
1267
1304
|
//#region src/version.js
|
|
1268
|
-
const PACKAGE_VERSION = "1.
|
|
1305
|
+
const PACKAGE_VERSION = "1.13.0";
|
|
1269
1306
|
const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
|
|
1270
1307
|
//#endregion
|
|
1271
1308
|
//#region src/model-catalog.js
|
|
@@ -2244,15 +2281,23 @@ function resetCreditsOf(value) {
|
|
|
2244
2281
|
if (value === void 0 || value === null) return void 0;
|
|
2245
2282
|
if (!record$1(value) || !Number.isSafeInteger(value.available_count) || value.available_count < 0) throw new Error("Codex returned malformed reset credit details");
|
|
2246
2283
|
if (value.credits !== void 0 && value.credits !== null && !Array.isArray(value.credits)) throw new Error("Codex returned malformed reset credit details");
|
|
2247
|
-
const
|
|
2248
|
-
|
|
2249
|
-
|
|
2284
|
+
const available = (value.credits ?? []).filter((credit) => record$1(credit) && credit.status?.toLowerCase?.() === "available").map((credit) => {
|
|
2285
|
+
const name = typeof credit.title === "string" && credit.title.trim().length > 0 ? credit.title.trim().slice(0, 120) : void 0;
|
|
2286
|
+
let expiresAt;
|
|
2287
|
+
if (Number.isSafeInteger(credit.expires_at) && credit.expires_at > 0) expiresAt = credit.expires_at * 1e3;
|
|
2288
|
+
else if (typeof credit.expires_at === "string" && credit.expires_at.length <= 64) {
|
|
2250
2289
|
const parsed = Date.parse(credit.expires_at);
|
|
2251
|
-
if (Number.isFinite(parsed) && parsed > 0)
|
|
2290
|
+
if (Number.isFinite(parsed) && parsed > 0) expiresAt = parsed;
|
|
2252
2291
|
}
|
|
2253
|
-
|
|
2292
|
+
return {
|
|
2293
|
+
...name === void 0 ? {} : { name },
|
|
2294
|
+
...expiresAt === void 0 ? {} : { expiresAt }
|
|
2295
|
+
};
|
|
2296
|
+
});
|
|
2297
|
+
const expirations = available.map((credit) => credit.expiresAt).filter((expiration) => expiration !== void 0);
|
|
2254
2298
|
return {
|
|
2255
2299
|
availableCount: value.available_count,
|
|
2300
|
+
...available.length === 0 ? {} : { credits: available },
|
|
2256
2301
|
...expirations.length === 0 ? {} : { nextExpiresAt: Math.min(...expirations) }
|
|
2257
2302
|
};
|
|
2258
2303
|
}
|
|
@@ -2691,10 +2736,13 @@ function parseDetails(value, now) {
|
|
|
2691
2736
|
if (available.length === 0) throw new Error("No usable quota reset is available");
|
|
2692
2737
|
return {
|
|
2693
2738
|
availableCount: value.available_count,
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2739
|
+
credits: available.map(({ credit, expiresAt }, index) => ({
|
|
2740
|
+
creditId: credit.id,
|
|
2741
|
+
title: safeCopy(credit.title),
|
|
2742
|
+
description: safeCopy(credit.description),
|
|
2743
|
+
creditExpiresAt: expiresAt,
|
|
2744
|
+
index
|
|
2745
|
+
}))
|
|
2698
2746
|
};
|
|
2699
2747
|
}
|
|
2700
2748
|
function parseConsumeResult(value) {
|
|
@@ -2728,6 +2776,23 @@ function createCodexResetCreditService(options) {
|
|
|
2728
2776
|
const challengeTtlMs = options.challengeTtlMs ?? DEFAULT_CHALLENGE_TTL_MS;
|
|
2729
2777
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
2730
2778
|
const challenges = /* @__PURE__ */ new Map();
|
|
2779
|
+
const creditRefs = /* @__PURE__ */ new Map();
|
|
2780
|
+
let creditRefSequence = 0;
|
|
2781
|
+
const rememberCreditRef = (accountId, credit) => {
|
|
2782
|
+
const ref = `${randomUUID$1()}-${++creditRefSequence}`;
|
|
2783
|
+
creditRefs.set(ref, {
|
|
2784
|
+
accountId,
|
|
2785
|
+
creditId: credit.creditId,
|
|
2786
|
+
index: credit.index
|
|
2787
|
+
});
|
|
2788
|
+
while (creditRefs.size > 64) creditRefs.delete(creditRefs.keys().next().value);
|
|
2789
|
+
return ref;
|
|
2790
|
+
};
|
|
2791
|
+
const publicCredit = (accountId, credit) => ({
|
|
2792
|
+
ref: rememberCreditRef(accountId, credit),
|
|
2793
|
+
...credit.title === void 0 ? {} : { name: credit.title },
|
|
2794
|
+
...credit.creditExpiresAt === void 0 ? {} : { expiresAt: credit.creditExpiresAt }
|
|
2795
|
+
});
|
|
2731
2796
|
const resolveCredentials = async (signal) => credentialsOf(await getAuth({ signal }), await readCredential({ signal }));
|
|
2732
2797
|
const readDetails = async (signal, credentials) => {
|
|
2733
2798
|
const { access, accountId } = credentials ?? await resolveCredentials(signal);
|
|
@@ -2757,16 +2822,27 @@ function createCodexResetCreditService(options) {
|
|
|
2757
2822
|
};
|
|
2758
2823
|
return Object.freeze({
|
|
2759
2824
|
async inspect({ signal } = {}) {
|
|
2760
|
-
const { details } = await readDetails(signal);
|
|
2825
|
+
const { accountId, details } = await readDetails(signal);
|
|
2761
2826
|
return {
|
|
2762
2827
|
availableCount: details.availableCount,
|
|
2763
|
-
|
|
2828
|
+
credits: details.credits.map((credit) => publicCredit(accountId, credit)),
|
|
2829
|
+
...details.credits[0]?.creditExpiresAt === void 0 ? {} : { nextExpiresAt: details.credits[0].creditExpiresAt }
|
|
2764
2830
|
};
|
|
2765
2831
|
},
|
|
2766
|
-
async prepare({ signal } = {}) {
|
|
2832
|
+
async prepare({ creditRef, signal } = {}) {
|
|
2767
2833
|
const credentials = await resolveCredentials(signal);
|
|
2768
2834
|
for (const [challengeId, challenge] of challenges) {
|
|
2769
|
-
if (challenge.accountId
|
|
2835
|
+
if (challenge.accountId === credentials.accountId && challenge.uncertain === false && challenge.creditRef === creditRef) if (now() > challenge.expiresAt) challenges.delete(challengeId);
|
|
2836
|
+
else return {
|
|
2837
|
+
challengeId,
|
|
2838
|
+
availableCount: challenge.availableCount,
|
|
2839
|
+
readyAt: challenge.readyAt,
|
|
2840
|
+
expiresAt: challenge.expiresAt,
|
|
2841
|
+
...challenge.creditExpiresAt === void 0 ? {} : { creditExpiresAt: challenge.creditExpiresAt },
|
|
2842
|
+
...challenge.title === void 0 ? {} : { title: challenge.title },
|
|
2843
|
+
...challenge.description === void 0 ? {} : { description: challenge.description }
|
|
2844
|
+
};
|
|
2845
|
+
if (challenge.accountId !== credentials.accountId || challenge.uncertain !== true || creditRef !== void 0 && challenge.creditRef !== creditRef) continue;
|
|
2770
2846
|
if (now() > challenge.expiresAt) {
|
|
2771
2847
|
challenges.delete(challengeId);
|
|
2772
2848
|
continue;
|
|
@@ -2782,22 +2858,31 @@ function createCodexResetCreditService(options) {
|
|
|
2782
2858
|
};
|
|
2783
2859
|
}
|
|
2784
2860
|
const { accountId, details } = await readDetails(signal, credentials);
|
|
2861
|
+
let selected = details.credits[0];
|
|
2862
|
+
if (creditRef !== void 0) {
|
|
2863
|
+
const reference = typeof creditRef === "string" ? creditRefs.get(creditRef) : void 0;
|
|
2864
|
+
if (reference === void 0 || reference.accountId !== accountId) throw new Error("This quota reset confirmation is no longer valid");
|
|
2865
|
+
selected = details.credits.find((credit) => credit.creditId === reference.creditId && credit.index === reference.index);
|
|
2866
|
+
if (selected === void 0) throw new Error("This quota reset confirmation is no longer valid");
|
|
2867
|
+
}
|
|
2868
|
+
if (selected === void 0) throw new Error("No usable quota reset is available");
|
|
2785
2869
|
const preparedAt = now();
|
|
2786
2870
|
const readyAt = preparedAt + confirmDelayMs;
|
|
2787
|
-
const expiresAt = Math.min(preparedAt + challengeTtlMs,
|
|
2871
|
+
const expiresAt = Math.min(preparedAt + challengeTtlMs, selected.creditExpiresAt ?? Number.MAX_SAFE_INTEGER);
|
|
2788
2872
|
if (expiresAt <= readyAt) throw new Error("The available quota reset expires too soon");
|
|
2789
2873
|
const challengeId = randomUUID$1();
|
|
2790
2874
|
challenges.set(challengeId, {
|
|
2791
2875
|
state: "prepared",
|
|
2792
2876
|
accountId,
|
|
2793
|
-
|
|
2877
|
+
creditRef,
|
|
2878
|
+
creditId: selected.creditId,
|
|
2794
2879
|
redeemRequestId: randomUUID$1(),
|
|
2795
2880
|
readyAt,
|
|
2796
2881
|
expiresAt,
|
|
2797
2882
|
availableCount: details.availableCount,
|
|
2798
|
-
creditExpiresAt:
|
|
2799
|
-
title:
|
|
2800
|
-
description:
|
|
2883
|
+
creditExpiresAt: selected.creditExpiresAt,
|
|
2884
|
+
title: selected.title,
|
|
2885
|
+
description: selected.description,
|
|
2801
2886
|
uncertain: false
|
|
2802
2887
|
});
|
|
2803
2888
|
return {
|
|
@@ -2805,9 +2890,9 @@ function createCodexResetCreditService(options) {
|
|
|
2805
2890
|
availableCount: details.availableCount,
|
|
2806
2891
|
readyAt,
|
|
2807
2892
|
expiresAt,
|
|
2808
|
-
...
|
|
2809
|
-
...
|
|
2810
|
-
...
|
|
2893
|
+
...selected.creditExpiresAt === void 0 ? {} : { creditExpiresAt: selected.creditExpiresAt },
|
|
2894
|
+
...selected.title === void 0 ? {} : { title: selected.title },
|
|
2895
|
+
...selected.description === void 0 ? {} : { description: selected.description }
|
|
2811
2896
|
};
|
|
2812
2897
|
},
|
|
2813
2898
|
async consume({ challengeId, acknowledged, signal } = {}) {
|
|
@@ -2885,6 +2970,7 @@ function createCodexResetCreditService(options) {
|
|
|
2885
2970
|
},
|
|
2886
2971
|
clear() {
|
|
2887
2972
|
challenges.clear();
|
|
2973
|
+
creditRefs.clear();
|
|
2888
2974
|
}
|
|
2889
2975
|
});
|
|
2890
2976
|
}
|
|
@@ -3026,7 +3112,10 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
|
|
|
3026
3112
|
signal.throwIfAborted();
|
|
3027
3113
|
return {
|
|
3028
3114
|
ok: true,
|
|
3029
|
-
value: endpoint === "reset-credit/inspect" ? await resetCreditService.inspect({ signal }) : endpoint === "reset-credit/prepare" ? await resetCreditService.prepare({
|
|
3115
|
+
value: endpoint === "reset-credit/inspect" ? await resetCreditService.inspect({ signal }) : endpoint === "reset-credit/prepare" ? await resetCreditService.prepare({
|
|
3116
|
+
creditRef: payload?.creditRef,
|
|
3117
|
+
signal
|
|
3118
|
+
}) : await resetCreditService.consume({
|
|
3030
3119
|
challengeId: payload?.challengeId,
|
|
3031
3120
|
acknowledged: payload?.acknowledged,
|
|
3032
3121
|
signal
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-codex-subscription",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Use ChatGPT and Codex subscriptions in DeepSeek Harness with OAuth, quota, safe resets, web search, images, and Fast mode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|