@young1lin/dsh-gpt-sub 0.1.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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +268 -0
- package/client.js +908 -0
- package/cordis.patch.yml +44 -0
- package/lib/index.d.ts +52 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +581 -0
- package/lib/index.js.map +1 -0
- package/lib/jwt.d.ts +15 -0
- package/lib/jwt.d.ts.map +1 -0
- package/lib/jwt.js +29 -0
- package/lib/jwt.js.map +1 -0
- package/lib/proxy-config.d.ts +91 -0
- package/lib/proxy-config.d.ts.map +1 -0
- package/lib/proxy-config.js +213 -0
- package/lib/proxy-config.js.map +1 -0
- package/lib/proxy-probe.d.ts +47 -0
- package/lib/proxy-probe.d.ts.map +1 -0
- package/lib/proxy-probe.js +51 -0
- package/lib/proxy-probe.js.map +1 -0
- package/lib/proxy-routing.d.ts +111 -0
- package/lib/proxy-routing.d.ts.map +1 -0
- package/lib/proxy-routing.js +171 -0
- package/lib/proxy-routing.js.map +1 -0
- package/lib/quota-route.d.ts +85 -0
- package/lib/quota-route.d.ts.map +1 -0
- package/lib/quota-route.js +105 -0
- package/lib/quota-route.js.map +1 -0
- package/lib/reset-credits.d.ts +64 -0
- package/lib/reset-credits.d.ts.map +1 -0
- package/lib/reset-credits.js +84 -0
- package/lib/reset-credits.js.map +1 -0
- package/lib/token-store.d.ts +107 -0
- package/lib/token-store.d.ts.map +1 -0
- package/lib/token-store.js +228 -0
- package/lib/token-store.js.map +1 -0
- package/lib/types.d.ts +18 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/lib/usage.d.ts +92 -0
- package/lib/usage.d.ts.map +1 -0
- package/lib/usage.js +106 -0
- package/lib/usage.js.map +1 -0
- package/package.json +82 -0
- package/src/index.ts +685 -0
- package/src/jwt.ts +26 -0
- package/src/proxy-config.ts +217 -0
- package/src/proxy-probe.ts +81 -0
- package/src/proxy-routing.ts +205 -0
- package/src/quota-route.ts +163 -0
- package/src/reset-credits.ts +128 -0
- package/src/token-store.ts +309 -0
- package/src/types.ts +17 -0
- package/src/usage.ts +154 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex OAuth credential access. Reads the Codex CLI's own auth.json so both
|
|
3
|
+
* tools share one login, and refreshes the access token before it expires.
|
|
4
|
+
*
|
|
5
|
+
* Expiry is judged solely from the access_token's exp claim. The id_token
|
|
6
|
+
* carries identity claims, lives one hour, and is routinely expired while the
|
|
7
|
+
* access token remains valid for days -- treating it as an expiry signal would
|
|
8
|
+
* trigger a refresh on nearly every request.
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-gpt-sub/token-store
|
|
11
|
+
*/
|
|
12
|
+
import { readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
13
|
+
import { fetch as undiciFetch } from 'undici';
|
|
14
|
+
import { jwtExpiryMs } from "./jwt.js";
|
|
15
|
+
/** Codex CLI's public OAuth client, read from the aud claim of a real id_token. */
|
|
16
|
+
const CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';
|
|
17
|
+
/** Issuer-derived token endpoint. */
|
|
18
|
+
const TOKEN_URL = 'https://auth.openai.com/oauth/token';
|
|
19
|
+
export class TokenStore {
|
|
20
|
+
#authFile;
|
|
21
|
+
#refreshMarginMs;
|
|
22
|
+
#now;
|
|
23
|
+
#dispatcher;
|
|
24
|
+
#fetch;
|
|
25
|
+
/** The in-flight exchange, shared by every caller that arrives during it. */
|
|
26
|
+
#inFlight;
|
|
27
|
+
constructor(options) {
|
|
28
|
+
this.#authFile = options.authFile;
|
|
29
|
+
this.#refreshMarginMs = options.refreshMarginMs;
|
|
30
|
+
this.#now = options.now ?? (() => Date.now());
|
|
31
|
+
this.#dispatcher = options.dispatcher;
|
|
32
|
+
// The same cast index.ts uses for undici's `request`: the real signature is
|
|
33
|
+
// wider than the seam, and the seam is what the tests stub.
|
|
34
|
+
this.#fetch = options.fetchImpl ?? undiciFetch;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Return usable credentials, refreshing first when the access token is
|
|
38
|
+
* inside the configured margin.
|
|
39
|
+
*
|
|
40
|
+
* @returns credentials valid at the moment of the call.
|
|
41
|
+
*/
|
|
42
|
+
async getTokens() {
|
|
43
|
+
const file = await this.#read();
|
|
44
|
+
const tokens = this.#extract(file);
|
|
45
|
+
const expiry = jwtExpiryMs(tokens.accessToken);
|
|
46
|
+
if (expiry !== undefined && expiry - this.#now() < this.#refreshMarginMs) {
|
|
47
|
+
return await this.#refresh(file, tokens);
|
|
48
|
+
}
|
|
49
|
+
return tokens;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Report the current access token's expiry without touching the network.
|
|
53
|
+
*
|
|
54
|
+
* Reads and parses the file only -- no refresh, no matter how close to the
|
|
55
|
+
* margin the token is -- so the status endpoint can show when the next
|
|
56
|
+
* automatic refresh will happen without triggering it.
|
|
57
|
+
*
|
|
58
|
+
* @returns file-derived facts about the stored credentials.
|
|
59
|
+
*/
|
|
60
|
+
async inspect() {
|
|
61
|
+
const tokens = this.#extract(await this.#read());
|
|
62
|
+
const expiry = jwtExpiryMs(tokens.accessToken);
|
|
63
|
+
return expiry === undefined ? {} : { accessTokenExpiresAt: expiry };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Point later reads at a different credential file, as a page-side switch
|
|
67
|
+
* does. The caller validates the new path before switching.
|
|
68
|
+
*
|
|
69
|
+
* @param authFile - the replacement credential file's path.
|
|
70
|
+
*/
|
|
71
|
+
setAuthFile(authFile) {
|
|
72
|
+
this.#authFile = authFile;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Point later refreshes at a different dispatcher, as a proxy switch does.
|
|
76
|
+
*
|
|
77
|
+
* @param dispatcher - the dispatcher future refresh calls use; undefined for the global.
|
|
78
|
+
*/
|
|
79
|
+
setDispatcher(dispatcher) {
|
|
80
|
+
this.#dispatcher = dispatcher;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check that the credential file is readable and carries the fields the shim
|
|
84
|
+
* needs, without spending a refresh on it.
|
|
85
|
+
*
|
|
86
|
+
* Called at plugin start so a missing or unparseable auth.json fails there
|
|
87
|
+
* naming the path, as the spec's error table requires, instead of surfacing
|
|
88
|
+
* on the first request. Deliberately does not call getTokens(): that would
|
|
89
|
+
* put a network refresh on the startup path.
|
|
90
|
+
*/
|
|
91
|
+
async verify() {
|
|
92
|
+
this.#extract(await this.#read());
|
|
93
|
+
}
|
|
94
|
+
/** Read and parse auth.json, naming the path in every failure. */
|
|
95
|
+
async #read() {
|
|
96
|
+
let raw;
|
|
97
|
+
try {
|
|
98
|
+
raw = await readFile(this.#authFile, 'utf8');
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
throw new Error(`dsh-gpt-sub: cannot read Codex credentials at ${this.#authFile}`, { cause: error });
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
return JSON.parse(raw);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
throw new Error(`dsh-gpt-sub: cannot parse Codex credentials at ${this.#authFile}`, { cause: error });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Pull the required fields, naming whichever one is absent. */
|
|
111
|
+
#extract(file) {
|
|
112
|
+
const accessToken = file.tokens?.access_token;
|
|
113
|
+
const refreshToken = file.tokens?.refresh_token;
|
|
114
|
+
const accountId = file.tokens?.account_id;
|
|
115
|
+
if (accessToken === undefined || refreshToken === undefined || accountId === undefined) {
|
|
116
|
+
throw new Error(`dsh-gpt-sub: ${this.#authFile} is missing tokens.access_token, tokens.refresh_token, or tokens.account_id; run 'codex' to sign in again`);
|
|
117
|
+
}
|
|
118
|
+
return { accessToken, refreshToken, accountId };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Refresh regardless of remaining lifetime. Used when the upstream rejects a
|
|
122
|
+
* token that local expiry math believed was still good.
|
|
123
|
+
*
|
|
124
|
+
* @returns freshly issued credentials.
|
|
125
|
+
*/
|
|
126
|
+
async forceRefresh() {
|
|
127
|
+
const file = await this.#read();
|
|
128
|
+
return await this.#refresh(file, this.#extract(file));
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Exchange the refresh token, coalescing callers that arrive together.
|
|
132
|
+
*
|
|
133
|
+
* The refresh token is single-use and rotates, so two concurrent exchanges
|
|
134
|
+
* would spend the same token: the loser gets `invalid_grant` and tells the
|
|
135
|
+
* user to sign in again in the middle of entirely normal operation. DSH
|
|
136
|
+
* issues parallel LLM calls, so that is reachable, not theoretical.
|
|
137
|
+
*
|
|
138
|
+
* Only concurrent callers share a result. Once the exchange settles the slot
|
|
139
|
+
* is cleared, so a later refresh performs a new one.
|
|
140
|
+
*
|
|
141
|
+
* @param file - the parsed auth.json, whose unrelated fields are preserved.
|
|
142
|
+
* @param tokens - the credentials whose refresh token is being spent.
|
|
143
|
+
* @returns the newly issued credentials.
|
|
144
|
+
*/
|
|
145
|
+
async #refresh(file, tokens) {
|
|
146
|
+
this.#inFlight ??= this.#exchange(file, tokens).finally(() => {
|
|
147
|
+
this.#inFlight = undefined;
|
|
148
|
+
});
|
|
149
|
+
return await this.#inFlight;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Perform one token exchange and persist the result.
|
|
153
|
+
*
|
|
154
|
+
* @param file - the parsed auth.json, whose unrelated fields are preserved.
|
|
155
|
+
* @param tokens - the credentials whose refresh token is being spent.
|
|
156
|
+
* @returns the newly issued credentials.
|
|
157
|
+
*/
|
|
158
|
+
async #exchange(file, tokens) {
|
|
159
|
+
let response;
|
|
160
|
+
try {
|
|
161
|
+
response = await this.#fetch(TOKEN_URL, {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: { 'content-type': 'application/json' },
|
|
164
|
+
body: JSON.stringify({
|
|
165
|
+
client_id: CLIENT_ID,
|
|
166
|
+
grant_type: 'refresh_token',
|
|
167
|
+
refresh_token: tokens.refreshToken,
|
|
168
|
+
}),
|
|
169
|
+
...(this.#dispatcher === undefined ? {} : { dispatcher: this.#dispatcher }),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
// A transport failure here is almost always the proxy, not the account.
|
|
174
|
+
// Say so, and still name the recovery the spec asks for.
|
|
175
|
+
throw new Error(`dsh-gpt-sub: cannot reach the Codex token endpoint at ${TOKEN_URL}; check the plugin's proxyUrl, then run 'codex' to sign in again`, { cause: error });
|
|
176
|
+
}
|
|
177
|
+
if (!response.ok) {
|
|
178
|
+
const detail = await response.text();
|
|
179
|
+
throw new Error(`dsh-gpt-sub: refreshing the Codex token failed (${response.status}: ${detail}); run 'codex' to sign in again`);
|
|
180
|
+
}
|
|
181
|
+
const payload = (await response.json());
|
|
182
|
+
if (payload.access_token === undefined) {
|
|
183
|
+
throw new Error(`dsh-gpt-sub: the token endpoint returned no access_token; run 'codex' to sign in again`);
|
|
184
|
+
}
|
|
185
|
+
const refreshed = {
|
|
186
|
+
accessToken: payload.access_token,
|
|
187
|
+
// A rotated refresh token must be kept; reusing a spent one fails next time.
|
|
188
|
+
refreshToken: payload.refresh_token ?? tokens.refreshToken,
|
|
189
|
+
accountId: tokens.accountId,
|
|
190
|
+
};
|
|
191
|
+
await this.#persist(file, refreshed, payload.id_token);
|
|
192
|
+
return refreshed;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Write the credential file atomically so a concurrent Codex CLI read never
|
|
196
|
+
* observes a partial file. Fields this plugin does not own are preserved.
|
|
197
|
+
*
|
|
198
|
+
* @param file - the previously parsed file contents.
|
|
199
|
+
* @param tokens - the credentials to store.
|
|
200
|
+
* @param idToken - a refreshed id_token when the endpoint returned one.
|
|
201
|
+
*/
|
|
202
|
+
async #persist(file, tokens, idToken) {
|
|
203
|
+
const next = {
|
|
204
|
+
...file,
|
|
205
|
+
tokens: {
|
|
206
|
+
...file.tokens,
|
|
207
|
+
...(idToken === undefined ? {} : { id_token: idToken }),
|
|
208
|
+
access_token: tokens.accessToken,
|
|
209
|
+
refresh_token: tokens.refreshToken,
|
|
210
|
+
account_id: tokens.accountId,
|
|
211
|
+
},
|
|
212
|
+
last_refresh: new Date(this.#now()).toISOString(),
|
|
213
|
+
};
|
|
214
|
+
const temporary = `${this.#authFile}.tmp-${String(process.pid)}`;
|
|
215
|
+
// For as long as it exists this file holds live credentials, so create it
|
|
216
|
+
// private to the user, and never leave one on disk if the rename that
|
|
217
|
+
// should have consumed it fails.
|
|
218
|
+
await writeFile(temporary, JSON.stringify(next, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
219
|
+
try {
|
|
220
|
+
await rename(temporary, this.#authFile);
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
await rm(temporary, { force: true });
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=token-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-store.js","sourceRoot":"","sources":["../src/token-store.ts"],"names":[],"mappings":"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"}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Plugin configuration shape, validated by the schemastery schema in index.ts. */
|
|
2
|
+
export interface Config {
|
|
3
|
+
/** Proxy URL for reaching the token endpoint, e.g. http://127.0.0.1:7890; empty means direct. */
|
|
4
|
+
proxyUrl: string;
|
|
5
|
+
/** Path to the Codex CLI credential file. */
|
|
6
|
+
authFile: string;
|
|
7
|
+
/** Refresh the access token when less than this many minutes remain. */
|
|
8
|
+
refreshMarginMinutes: number;
|
|
9
|
+
/** Credential the provider route names, and this plugin keeps populated. */
|
|
10
|
+
tokenRef: string;
|
|
11
|
+
/** How often to re-read the credential file and republish the token. */
|
|
12
|
+
syncIntervalMinutes: number;
|
|
13
|
+
/** Extra attempts per request after a dropped connection; 0 disables retry. */
|
|
14
|
+
bootstrapRetries: number;
|
|
15
|
+
/** Where the panel's runtime overrides (proxy, credential file) are persisted. */
|
|
16
|
+
stateFile: string;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,MAAM,WAAW,MAAM;IACrB,iGAAiG;IACjG,QAAQ,EAAE,MAAM,CAAA;IAChB,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAA;IAChB,wEAAwE;IACxE,oBAAoB,EAAE,MAAM,CAAA;IAC5B,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAA;IAChB,wEAAwE;IACxE,mBAAmB,EAAE,MAAM,CAAA;IAC3B,+EAA+E;IAC/E,gBAAgB,EAAE,MAAM,CAAA;IACxB,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAA;CAClB"}
|
package/lib/types.js
ADDED
package/lib/types.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/lib/usage.d.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscription usage lookup for the Codex account.
|
|
3
|
+
*
|
|
4
|
+
* One GET against the same host the model calls use, authorized with the same
|
|
5
|
+
* access token. Used at startup as a reachability probe -- an unproxied egress
|
|
6
|
+
* answers 403 with a Cloudflare block page rather than an API error, and that
|
|
7
|
+
* failure is otherwise invisible until the first model call.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-gpt-sub/usage
|
|
10
|
+
*/
|
|
11
|
+
import { fetch as undiciFetch, type Dispatcher } from 'undici';
|
|
12
|
+
/** One rate-limit window as the usage endpoint reports it. */
|
|
13
|
+
export interface UsageWindow {
|
|
14
|
+
used_percent: number;
|
|
15
|
+
limit_window_seconds: number;
|
|
16
|
+
reset_after_seconds?: number;
|
|
17
|
+
reset_at?: number;
|
|
18
|
+
}
|
|
19
|
+
/** The subset of the usage reply this plugin reads. */
|
|
20
|
+
export interface Usage {
|
|
21
|
+
plan_type?: string;
|
|
22
|
+
rate_limit?: {
|
|
23
|
+
primary_window?: UsageWindow | null;
|
|
24
|
+
secondary_window?: UsageWindow | null;
|
|
25
|
+
};
|
|
26
|
+
/** On-demand rate-limit resets the plan grants, when it reports them. */
|
|
27
|
+
rate_limit_reset_credits?: {
|
|
28
|
+
/** Resets the account owns and can still spend. */
|
|
29
|
+
available_count?: number;
|
|
30
|
+
/** Resets applicable to the current windows -- zero while none is capped. */
|
|
31
|
+
applicable_available_count?: number;
|
|
32
|
+
} | null;
|
|
33
|
+
}
|
|
34
|
+
/** Thrown when the usage endpoint answers with something other than JSON usage. */
|
|
35
|
+
export declare class UsageError extends Error {
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Read the account's current usage.
|
|
39
|
+
*
|
|
40
|
+
* @param accessToken - a live Codex access token.
|
|
41
|
+
* @param dispatcher - undici dispatcher; omit to use the global one.
|
|
42
|
+
* @returns the parsed usage reply.
|
|
43
|
+
* @throws UsageError when the endpoint refuses, or answers with a non-JSON
|
|
44
|
+
* body -- which is what an unproxied egress produces, and the message says so.
|
|
45
|
+
*/
|
|
46
|
+
export declare function fetchUsage(accessToken: string, dispatcher?: Dispatcher, fetchImpl?: typeof undiciFetch, signal?: AbortSignal): Promise<Usage>;
|
|
47
|
+
/**
|
|
48
|
+
* List every window the reply carries, primary slot first.
|
|
49
|
+
*
|
|
50
|
+
* Accounts differ here -- a plus account reports `secondary_window: null` and
|
|
51
|
+
* carries its weekly allowance on the primary, and a pro account currently
|
|
52
|
+
* reports only a weekly window with no 5-hour rolling limit at all -- so
|
|
53
|
+
* reading one chosen slot hides the 5-hour window on the accounts that have
|
|
54
|
+
* one. The panel renders whichever windows exist.
|
|
55
|
+
*
|
|
56
|
+
* @param usage - a usage reply.
|
|
57
|
+
* @returns the windows worth showing, primary slot first; empty when the reply
|
|
58
|
+
* carries none.
|
|
59
|
+
*/
|
|
60
|
+
export declare function reportedWindows(usage: Usage): UsageWindow[];
|
|
61
|
+
/**
|
|
62
|
+
* Count the on-demand usage resets the account can still spend -- what the
|
|
63
|
+
* usage endpoint reports as `rate_limit_reset_credits`, each one clearing a
|
|
64
|
+
* capped window without waiting out its timer.
|
|
65
|
+
*
|
|
66
|
+
* @param usage - a usage reply.
|
|
67
|
+
* @returns the resets remaining, or undefined when the reply carries none --
|
|
68
|
+
* which plans without the feature answer.
|
|
69
|
+
*/
|
|
70
|
+
export declare function resetsRemaining(usage: Usage): number | undefined;
|
|
71
|
+
/** A rate-limit window normalized for per-window rendering. */
|
|
72
|
+
export interface ReportedWindow {
|
|
73
|
+
/** Percent of this window consumed. */
|
|
74
|
+
usedPercent: number;
|
|
75
|
+
/** Length of the window, in hours. */
|
|
76
|
+
windowHours: number;
|
|
77
|
+
/** Epoch seconds at which the window resets. */
|
|
78
|
+
resetAt?: number;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Every window the reply reports, primary first.
|
|
82
|
+
*
|
|
83
|
+
* The panel renders one row per window -- the 5-hour and the weekly allowance
|
|
84
|
+
* on accounts that report both -- so it needs all of them, not the single one
|
|
85
|
+
* {@link activeWindow} picks for the startup log. Window identity comes from
|
|
86
|
+
* the reported length, because which slot an account uses differs.
|
|
87
|
+
*
|
|
88
|
+
* @param usage - a usage reply.
|
|
89
|
+
* @returns the reported windows, primary before secondary.
|
|
90
|
+
*/
|
|
91
|
+
export declare function reportWindows(usage: Usage): ReportedWindow[];
|
|
92
|
+
//# sourceMappingURL=usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,KAAK,IAAI,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAA;AAE9D,8DAA8D;AAC9D,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAA;IACpB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,uDAAuD;AACvD,MAAM,WAAW,KAAK;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE;QACX,cAAc,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;QACnC,gBAAgB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;KACtC,CAAA;IACD,yEAAyE;IACzE,wBAAwB,CAAC,EAAE;QACzB,mDAAmD;QACnD,eAAe,CAAC,EAAE,MAAM,CAAA;QACxB,6EAA6E;QAC7E,0BAA0B,CAAC,EAAE,MAAM,CAAA;KACpC,GAAG,IAAI,CAAA;CACT;AAED,mFAAmF;AACnF,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAKxC;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAC9B,WAAW,EAAE,MAAM,EACnB,UAAU,CAAC,EAAE,UAAU,EACvB,SAAS,GAAE,OAAO,WAAyB,EAC3C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,KAAK,CAAC,CAsBhB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,WAAW,EAAE,CAI3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,CAGhE;AAED,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAA;IACnB,sCAAsC;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAcD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,cAAc,EAAE,CAM5D"}
|
package/lib/usage.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscription usage lookup for the Codex account.
|
|
3
|
+
*
|
|
4
|
+
* One GET against the same host the model calls use, authorized with the same
|
|
5
|
+
* access token. Used at startup as a reachability probe -- an unproxied egress
|
|
6
|
+
* answers 403 with a Cloudflare block page rather than an API error, and that
|
|
7
|
+
* failure is otherwise invisible until the first model call.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-gpt-sub/usage
|
|
10
|
+
*/
|
|
11
|
+
import { fetch as undiciFetch } from 'undici';
|
|
12
|
+
/** Thrown when the usage endpoint answers with something other than JSON usage. */
|
|
13
|
+
export class UsageError extends Error {
|
|
14
|
+
}
|
|
15
|
+
/** The usage endpoint, on the same host as the model calls. */
|
|
16
|
+
const USAGE_URL = 'https://chatgpt.com/backend-api/wham/usage';
|
|
17
|
+
/**
|
|
18
|
+
* Read the account's current usage.
|
|
19
|
+
*
|
|
20
|
+
* @param accessToken - a live Codex access token.
|
|
21
|
+
* @param dispatcher - undici dispatcher; omit to use the global one.
|
|
22
|
+
* @returns the parsed usage reply.
|
|
23
|
+
* @throws UsageError when the endpoint refuses, or answers with a non-JSON
|
|
24
|
+
* body -- which is what an unproxied egress produces, and the message says so.
|
|
25
|
+
*/
|
|
26
|
+
export async function fetchUsage(accessToken, dispatcher, fetchImpl = undiciFetch, signal) {
|
|
27
|
+
const response = await fetchImpl(USAGE_URL, {
|
|
28
|
+
headers: { authorization: `Bearer ${accessToken}`, accept: 'application/json' },
|
|
29
|
+
...(dispatcher === undefined ? {} : { dispatcher }),
|
|
30
|
+
...(signal === undefined ? {} : { signal }),
|
|
31
|
+
});
|
|
32
|
+
const body = await response.text();
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
const blocked = body.includes('Unable to load site') || body.includes('blocked-icon');
|
|
35
|
+
throw new UsageError(blocked
|
|
36
|
+
? `usage endpoint answered ${String(response.status)} with a Cloudflare block page; this address cannot reach chatgpt.com, so the request did not egress through the proxy`
|
|
37
|
+
: `usage endpoint answered ${String(response.status)}`);
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(body);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
throw new UsageError('usage endpoint did not answer with JSON');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* List every window the reply carries, primary slot first.
|
|
48
|
+
*
|
|
49
|
+
* Accounts differ here -- a plus account reports `secondary_window: null` and
|
|
50
|
+
* carries its weekly allowance on the primary, and a pro account currently
|
|
51
|
+
* reports only a weekly window with no 5-hour rolling limit at all -- so
|
|
52
|
+
* reading one chosen slot hides the 5-hour window on the accounts that have
|
|
53
|
+
* one. The panel renders whichever windows exist.
|
|
54
|
+
*
|
|
55
|
+
* @param usage - a usage reply.
|
|
56
|
+
* @returns the windows worth showing, primary slot first; empty when the reply
|
|
57
|
+
* carries none.
|
|
58
|
+
*/
|
|
59
|
+
export function reportedWindows(usage) {
|
|
60
|
+
const primary = usage.rate_limit?.primary_window;
|
|
61
|
+
const secondary = usage.rate_limit?.secondary_window;
|
|
62
|
+
return [primary, secondary].filter((window) => window != null);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Count the on-demand usage resets the account can still spend -- what the
|
|
66
|
+
* usage endpoint reports as `rate_limit_reset_credits`, each one clearing a
|
|
67
|
+
* capped window without waiting out its timer.
|
|
68
|
+
*
|
|
69
|
+
* @param usage - a usage reply.
|
|
70
|
+
* @returns the resets remaining, or undefined when the reply carries none --
|
|
71
|
+
* which plans without the feature answer.
|
|
72
|
+
*/
|
|
73
|
+
export function resetsRemaining(usage) {
|
|
74
|
+
const count = usage.rate_limit_reset_credits?.available_count;
|
|
75
|
+
return typeof count === 'number' ? count : undefined;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Normalize one raw window for the panel.
|
|
79
|
+
*
|
|
80
|
+
* @param window - the window as the usage endpoint reports it.
|
|
81
|
+
* @returns the normalized window.
|
|
82
|
+
*/
|
|
83
|
+
const toReported = (window) => ({
|
|
84
|
+
usedPercent: window.used_percent,
|
|
85
|
+
windowHours: Math.round(window.limit_window_seconds / 3600),
|
|
86
|
+
...(window.reset_at === undefined ? {} : { resetAt: window.reset_at }),
|
|
87
|
+
});
|
|
88
|
+
/**
|
|
89
|
+
* Every window the reply reports, primary first.
|
|
90
|
+
*
|
|
91
|
+
* The panel renders one row per window -- the 5-hour and the weekly allowance
|
|
92
|
+
* on accounts that report both -- so it needs all of them, not the single one
|
|
93
|
+
* {@link activeWindow} picks for the startup log. Window identity comes from
|
|
94
|
+
* the reported length, because which slot an account uses differs.
|
|
95
|
+
*
|
|
96
|
+
* @param usage - a usage reply.
|
|
97
|
+
* @returns the reported windows, primary before secondary.
|
|
98
|
+
*/
|
|
99
|
+
export function reportWindows(usage) {
|
|
100
|
+
const primary = usage.rate_limit?.primary_window;
|
|
101
|
+
const secondary = usage.rate_limit?.secondary_window;
|
|
102
|
+
return [primary, secondary]
|
|
103
|
+
.filter((window) => window != null)
|
|
104
|
+
.map(toReported);
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=usage.js.map
|
package/lib/usage.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@young1lin/dsh-gpt-sub",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "Direct ChatGPT/Codex subscription access for DeepSeek Harness, replacing CLIProxyAPI.",
|
|
8
|
+
"author": "young1lin",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/young1lin/dsh-gpt-sub.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/young1lin/dsh-gpt-sub#readme",
|
|
14
|
+
"bugs": "https://github.com/young1lin/dsh-gpt-sub/issues",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "lib/index.js",
|
|
17
|
+
"types": "lib/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./lib/index.d.ts",
|
|
21
|
+
"default": "./lib/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json",
|
|
24
|
+
"./client": "./client.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"lib",
|
|
28
|
+
"src",
|
|
29
|
+
"client.js",
|
|
30
|
+
"cordis.patch.yml",
|
|
31
|
+
"README.md",
|
|
32
|
+
"CHANGELOG.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=22.13"
|
|
37
|
+
},
|
|
38
|
+
"packageManager": "pnpm@11.22.0",
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc -p tsconfig.json",
|
|
41
|
+
"typecheck": "tsc -p tsconfig.test.json",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"prepublishOnly": "npm run build && npm test"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"deepseek-harness",
|
|
47
|
+
"dsh",
|
|
48
|
+
"cordis",
|
|
49
|
+
"plugin",
|
|
50
|
+
"chatgpt",
|
|
51
|
+
"codex",
|
|
52
|
+
"openai",
|
|
53
|
+
"subscription",
|
|
54
|
+
"quota"
|
|
55
|
+
],
|
|
56
|
+
"license": "MIT",
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
59
|
+
"undici": "^7.10.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1",
|
|
63
|
+
"@deepseek-ai/dsh-credentials": "^0.1.0-rc.2"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1",
|
|
67
|
+
"@deepseek-ai/dsh-credentials": "^0.1.0-rc.2",
|
|
68
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.0-rc.7",
|
|
69
|
+
"@types/node": "^22.10.0",
|
|
70
|
+
"typescript": "^5.9.0",
|
|
71
|
+
"vitest": "^3.2.0"
|
|
72
|
+
},
|
|
73
|
+
"dsh": {
|
|
74
|
+
"bundle": {
|
|
75
|
+
"patch": "./cordis.patch.yml"
|
|
76
|
+
},
|
|
77
|
+
"client": {
|
|
78
|
+
"inject": [],
|
|
79
|
+
"platform": "web"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|