@sunshaobei/dsh-balance-panel 3.0.5 → 3.0.7
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/lib/client.js +6 -3
- package/lib/index.js +56 -1
- package/package.json +1 -1
- package/src/client.ts +1 -1
- package/src/index.ts +50 -1
package/lib/client.js
CHANGED
|
@@ -95,9 +95,12 @@ window.__ModuleLoader__.load({
|
|
|
95
95
|
}
|
|
96
96
|
if (family.length === 0) {
|
|
97
97
|
var prov = String(sel.provider || "").toLowerCase();
|
|
98
|
-
// DOM-fallback selection
|
|
99
|
-
//
|
|
100
|
-
|
|
98
|
+
// A DOM-fallback selection has no provider: the model name alone
|
|
99
|
+
// matched no known family, so this model has no known quota
|
|
100
|
+
// source. Show nothing rather than a wrong provider — an empty
|
|
101
|
+
// prov would otherwise substring-match EVERY row.
|
|
102
|
+
if (!prov)
|
|
103
|
+
return null;
|
|
101
104
|
for (var j = 0; j < specs.rows.length; j++) {
|
|
102
105
|
var id = String(specs.rows[j].id || "").toLowerCase();
|
|
103
106
|
if (prov.indexOf(id) > -1 || id.indexOf(prov) > -1)
|
package/lib/index.js
CHANGED
|
@@ -264,6 +264,35 @@ async function chatgptAuthExists() {
|
|
|
264
264
|
}
|
|
265
265
|
/** In-memory token cache; refreshed on expiry or on auth failure. */
|
|
266
266
|
let chatgptTokenCache = null;
|
|
267
|
+
// Live handle onto the credentials service — set in apply(). Lets the
|
|
268
|
+
// ChatGPT/Codex token resolver read the shared grant record that
|
|
269
|
+
// dsh-codex-auth writes (llm-pi-ai/openai-codex).
|
|
270
|
+
let codexCredentialsRef = null;
|
|
271
|
+
// Auto-detected local proxy for ChatGPT/Codex upstream calls. The usage and
|
|
272
|
+
// token endpoints are region-blocked on direct connections, and Node's fetch
|
|
273
|
+
// never reads the OS proxy — so when the panel row has no explicit proxy we
|
|
274
|
+
// probe the well-known local proxy ports once and remember the answer.
|
|
275
|
+
let localProxyCache = null; // null = not probed yet
|
|
276
|
+
async function detectLocalProxy() {
|
|
277
|
+
if (localProxyCache !== null)
|
|
278
|
+
return localProxyCache || undefined;
|
|
279
|
+
for (const port of [7897, 7890, 1087, 8118, 6152]) {
|
|
280
|
+
const ok = await new Promise((resolve) => {
|
|
281
|
+
const req = http.request({ host: '127.0.0.1', port, method: 'CONNECT', path: 'chatgpt.com:443', timeout: 2000 });
|
|
282
|
+
req.on('connect', (res, socket) => { socket.destroy(); resolve(res.statusCode === 200); });
|
|
283
|
+
req.on('error', () => resolve(false));
|
|
284
|
+
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
285
|
+
req.end();
|
|
286
|
+
});
|
|
287
|
+
if (ok) {
|
|
288
|
+
localProxyCache = 'http://127.0.0.1:' + port;
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (localProxyCache === null)
|
|
293
|
+
localProxyCache = '';
|
|
294
|
+
return localProxyCache || undefined;
|
|
295
|
+
}
|
|
267
296
|
async function readChatGPTAuthFile() {
|
|
268
297
|
const authJson = chatgptAuthJsonPath();
|
|
269
298
|
let raw;
|
|
@@ -632,6 +661,31 @@ function chatgptLoginStatus() {
|
|
|
632
661
|
async function resolveChatGPTToken(timeoutMs, proxyUrl) {
|
|
633
662
|
const now = Date.now();
|
|
634
663
|
const cached = chatgptTokenCache?.bundle;
|
|
664
|
+
// Source 0: the shared grant record written by dsh-codex-auth
|
|
665
|
+
// (llm-pi-ai/openai-codex). Preferred when present and fresh: the user
|
|
666
|
+
// authorized through the Codex subscription settings page, so the panel
|
|
667
|
+
// should reflect THAT credential instead of ~/.codex/auth.json.
|
|
668
|
+
try {
|
|
669
|
+
if (codexCredentialsRef?.readRecord) {
|
|
670
|
+
const rec = await codexCredentialsRef.readRecord('llm-pi-ai/openai-codex');
|
|
671
|
+
const p = rec && rec.kind === 'grant' ? rec.payload : undefined;
|
|
672
|
+
if (p && p.access) {
|
|
673
|
+
if (!p.expires || p.expires - now > 30000) {
|
|
674
|
+
chatgptTokenCache = { bundle: { access_token: p.access, refresh_token: p.refresh, expires_at_ms: p.expires, account_id: p.accountId } };
|
|
675
|
+
return { accessToken: p.access, accountId: p.accountId };
|
|
676
|
+
}
|
|
677
|
+
if (p.refresh) {
|
|
678
|
+
const refreshed = await refreshChatGPTToken(p.refresh, timeoutMs, proxyUrl);
|
|
679
|
+
if (!refreshed.account_id)
|
|
680
|
+
refreshed.account_id = p.accountId;
|
|
681
|
+
await codexCredentialsRef.modifyRecord('llm-pi-ai/openai-codex', async () => ({ kind: 'grant', payload: { ...p, access: refreshed.access_token, refresh: refreshed.refresh_token || p.refresh, expires: refreshed.expires_at_ms } })).catch(() => { });
|
|
682
|
+
chatgptTokenCache = { bundle: refreshed };
|
|
683
|
+
return { accessToken: refreshed.access_token, accountId: refreshed.account_id };
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
catch { /* record absent or unreadable — fall through to the other sources */ }
|
|
635
689
|
if (cached && cached.access_token && (!cached.expires_at_ms || cached.expires_at_ms - now > 30000)) {
|
|
636
690
|
return { accessToken: cached.access_token, accountId: cached.account_id };
|
|
637
691
|
}
|
|
@@ -1640,7 +1694,7 @@ async function fetchRow(ctx, provider, proxies, clientProxyUrl) {
|
|
|
1640
1694
|
// query AND token refresh — same region constraints apply.
|
|
1641
1695
|
const cgProxy = clientProxyUrl !== undefined
|
|
1642
1696
|
? clientProxyUrl
|
|
1643
|
-
: (provider.proxy !== undefined ? proxies[provider.proxy] :
|
|
1697
|
+
: (provider.proxy !== undefined ? proxies[provider.proxy] : await detectLocalProxy());
|
|
1644
1698
|
let { accessToken, accountId } = await resolveChatGPTToken(UPSTREAM_TIMEOUT_MS, cgProxy);
|
|
1645
1699
|
let body;
|
|
1646
1700
|
try {
|
|
@@ -1880,6 +1934,7 @@ function registerQuotaRpc(ctx, handler) {
|
|
|
1880
1934
|
* @param config - raw plugin config (schema-processed by the loader).
|
|
1881
1935
|
*/
|
|
1882
1936
|
export function apply(ctx, config = {}) {
|
|
1937
|
+
codexCredentialsRef = ctx.credentials;
|
|
1883
1938
|
const raw = config && typeof config === 'object' ? config : {};
|
|
1884
1939
|
const refreshMs = numOf(raw.refreshMs, 60000);
|
|
1885
1940
|
if (!(refreshMs >= 5000))
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sunshaobei/dsh-balance-panel",
|
|
3
3
|
"description": "Provider quota/balance widget for the dsh web surface: an inline sidebar card above the Settings row (collapsed glanceable row expanding into a Harness-native card with a settings panel), fed by a loopback Connection RPC channel whose host half proxies each provider with credentials that never reach the browser.",
|
|
4
|
-
"version": "3.0.
|
|
4
|
+
"version": "3.0.7",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
package/src/client.ts
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* follows the product theme (light/dark) instead of carrying its own palette.
|
|
26
26
|
*/
|
|
27
27
|
(window as any).__ModuleLoader__.load({
|
|
28
|
-
id: "dsh-balance-panel",
|
|
28
|
+
id: "@sunshaobei/dsh-balance-panel",
|
|
29
29
|
factory: (require) => {
|
|
30
30
|
var module = { exports: {} as any };
|
|
31
31
|
var exports = module.exports;
|
package/src/index.ts
CHANGED
|
@@ -287,6 +287,31 @@ interface ChatGPTTokenBundle {
|
|
|
287
287
|
|
|
288
288
|
/** In-memory token cache; refreshed on expiry or on auth failure. */
|
|
289
289
|
let chatgptTokenCache: { bundle: ChatGPTTokenBundle } | null = null;
|
|
290
|
+
// Live handle onto the credentials service — set in apply(). Lets the
|
|
291
|
+
// ChatGPT/Codex token resolver read the shared grant record that
|
|
292
|
+
// dsh-codex-auth writes (llm-pi-ai/openai-codex).
|
|
293
|
+
let codexCredentialsRef: any = null;
|
|
294
|
+
|
|
295
|
+
// Auto-detected local proxy for ChatGPT/Codex upstream calls. The usage and
|
|
296
|
+
// token endpoints are region-blocked on direct connections, and Node's fetch
|
|
297
|
+
// never reads the OS proxy — so when the panel row has no explicit proxy we
|
|
298
|
+
// probe the well-known local proxy ports once and remember the answer.
|
|
299
|
+
let localProxyCache: string | undefined | null = null; // null = not probed yet
|
|
300
|
+
async function detectLocalProxy(): Promise<string | undefined> {
|
|
301
|
+
if (localProxyCache !== null) return localProxyCache || undefined;
|
|
302
|
+
for (const port of [7897, 7890, 1087, 8118, 6152]) {
|
|
303
|
+
const ok = await new Promise<boolean>((resolve) => {
|
|
304
|
+
const req = http.request({ host: '127.0.0.1', port, method: 'CONNECT', path: 'chatgpt.com:443', timeout: 2000 });
|
|
305
|
+
req.on('connect', (res, socket) => { socket.destroy(); resolve(res.statusCode === 200); });
|
|
306
|
+
req.on('error', () => resolve(false));
|
|
307
|
+
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
308
|
+
req.end();
|
|
309
|
+
});
|
|
310
|
+
if (ok) { localProxyCache = 'http://127.0.0.1:' + port; break; }
|
|
311
|
+
}
|
|
312
|
+
if (localProxyCache === null) localProxyCache = '';
|
|
313
|
+
return localProxyCache || undefined;
|
|
314
|
+
}
|
|
290
315
|
|
|
291
316
|
async function readChatGPTAuthFile(): Promise<ChatGPTTokenBundle> {
|
|
292
317
|
const authJson = chatgptAuthJsonPath();
|
|
@@ -653,6 +678,29 @@ function chatgptLoginStatus(): { active: boolean; verificationUrl?: string; user
|
|
|
653
678
|
async function resolveChatGPTToken(timeoutMs: number, proxyUrl?: string): Promise<{ accessToken: string; accountId?: string }> {
|
|
654
679
|
const now = Date.now();
|
|
655
680
|
const cached = chatgptTokenCache?.bundle;
|
|
681
|
+
// Source 0: the shared grant record written by dsh-codex-auth
|
|
682
|
+
// (llm-pi-ai/openai-codex). Preferred when present and fresh: the user
|
|
683
|
+
// authorized through the Codex subscription settings page, so the panel
|
|
684
|
+
// should reflect THAT credential instead of ~/.codex/auth.json.
|
|
685
|
+
try {
|
|
686
|
+
if (codexCredentialsRef?.readRecord) {
|
|
687
|
+
const rec: any = await codexCredentialsRef.readRecord('llm-pi-ai/openai-codex');
|
|
688
|
+
const p = rec && rec.kind === 'grant' ? rec.payload : undefined;
|
|
689
|
+
if (p && p.access) {
|
|
690
|
+
if (!p.expires || p.expires - now > 30_000) {
|
|
691
|
+
chatgptTokenCache = { bundle: { access_token: p.access, refresh_token: p.refresh, expires_at_ms: p.expires, account_id: p.accountId } };
|
|
692
|
+
return { accessToken: p.access, accountId: p.accountId };
|
|
693
|
+
}
|
|
694
|
+
if (p.refresh) {
|
|
695
|
+
const refreshed = await refreshChatGPTToken(p.refresh, timeoutMs, proxyUrl);
|
|
696
|
+
if (!refreshed.account_id) refreshed.account_id = p.accountId;
|
|
697
|
+
await codexCredentialsRef.modifyRecord('llm-pi-ai/openai-codex', async () => ({ kind: 'grant', payload: { ...p, access: refreshed.access_token, refresh: refreshed.refresh_token || p.refresh, expires: refreshed.expires_at_ms } })).catch(() => {});
|
|
698
|
+
chatgptTokenCache = { bundle: refreshed };
|
|
699
|
+
return { accessToken: refreshed.access_token, accountId: refreshed.account_id };
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
} catch { /* record absent or unreadable — fall through to the other sources */ }
|
|
656
704
|
if (cached && cached.access_token && (!cached.expires_at_ms || cached.expires_at_ms - now > 30_000)) {
|
|
657
705
|
return { accessToken: cached.access_token, accountId: cached.account_id };
|
|
658
706
|
}
|
|
@@ -1601,7 +1649,7 @@ async function fetchRow(ctx, provider, proxies, clientProxyUrl) {
|
|
|
1601
1649
|
// query AND token refresh — same region constraints apply.
|
|
1602
1650
|
const cgProxy = clientProxyUrl !== undefined
|
|
1603
1651
|
? clientProxyUrl
|
|
1604
|
-
: (provider.proxy !== undefined ? proxies[provider.proxy] :
|
|
1652
|
+
: (provider.proxy !== undefined ? proxies[provider.proxy] : await detectLocalProxy());
|
|
1605
1653
|
let { accessToken, accountId } = await resolveChatGPTToken(UPSTREAM_TIMEOUT_MS, cgProxy);
|
|
1606
1654
|
let body: any;
|
|
1607
1655
|
try {
|
|
@@ -1830,6 +1878,7 @@ async function fetchRow(ctx, provider, proxies, clientProxyUrl) {
|
|
|
1830
1878
|
* @param config - raw plugin config (schema-processed by the loader).
|
|
1831
1879
|
*/
|
|
1832
1880
|
export function apply(ctx, config: Record<string, any> = {}) {
|
|
1881
|
+
codexCredentialsRef = ctx.credentials;
|
|
1833
1882
|
const raw: Record<string, any> = config && typeof config === 'object' ? config : {};
|
|
1834
1883
|
const refreshMs = numOf(raw.refreshMs, 60000);
|
|
1835
1884
|
if (!(refreshMs >= 5000)) throw new Error('quota-panel: config.refreshMs must be >= 5000');
|