@cortexkit/pi-openai-auth 0.7.2 → 0.8.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/README.md +15 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +203 -162
- package/package.json +6 -2
- package/dist/raw-ws-node.d.ts +0 -27
- package/dist/raw-ws-node.js +0 -328
package/README.md
CHANGED
|
@@ -32,6 +32,21 @@ Restart Pi after installing, then authenticate through Pi's normal login flow:
|
|
|
32
32
|
/login openai-codex
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
## Commands
|
|
36
|
+
|
|
37
|
+
The extension registers three commands in Pi:
|
|
38
|
+
|
|
39
|
+
- `openai-account` — list configured fallback accounts, `openai-account add [label]` to add a fallback account via OAuth (browser or `--headless`), or `openai-account remove <id>` to remove one.
|
|
40
|
+
- `openai-quota` — show the quota last recorded for each stored fallback account.
|
|
41
|
+
- `openai-routing` — set the routing order (`main-first`, `fallback-first`, `sticky-balanced`) or reset session pins.
|
|
42
|
+
|
|
43
|
+
These manage the account store. **Pi requests do not yet route through it** — the
|
|
44
|
+
extension streams through the credential Pi itself supplies, so a routing choice is
|
|
45
|
+
recorded but does not move traffic between accounts, and quota shows the last values
|
|
46
|
+
written to the store rather than fetching current ones. The OpenCode plugin is where
|
|
47
|
+
routing is live today. The store format is shared between the two, so what you configure
|
|
48
|
+
here is what Pi will use once its request path reads it.
|
|
49
|
+
|
|
35
50
|
## License
|
|
36
51
|
|
|
37
52
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
//#region src/index.d.ts
|
|
2
3
|
export declare function installRawCodexWebSocket(): () => void;
|
|
3
4
|
export default function cortexKitPiOpenAIAuth(pi: ExtensionAPI): void;
|
|
5
|
+
//#endregion
|
package/dist/index.js
CHANGED
|
@@ -1,162 +1,203 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
input: ['text'],
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
1
|
+
var O1=Object.defineProperty;var U1=(Z)=>Z;function q1(Z,$){this[Z]=U1.bind(null,$)}var K1=(Z,$)=>{for(var G in $)O1(Z,G,{get:$[G],enumerable:!0,configurable:!0,set:q1.bind($,G)})};var _1=(Z,$)=>()=>(Z&&($=Z(Z=0)),$);var q8={};K1(q8,{toResetIso:()=>H0,normalizeWsFrame:()=>WZ,normalizeWham:()=>QZ,normalizeQuotaHeaders:()=>jZ,isCompleteQuotaHeaderFrame:()=>HZ});function H0(Z){if(Z==null||Z==="")return;let $=typeof Z==="number"?Z:Number(Z);if(Number.isFinite($)){let X=$<100000000000?$*1000:$,Y=new Date(X);return Number.isNaN(Y.getTime())?void 0:Y.toISOString()}let G=new Date(Z);return Number.isNaN(G.getTime())?void 0:G.toISOString()}function I0(Z){let $=Number(Z);return Number.isFinite($)&&$>0?$:void 0}function V8(Z){return typeof Z==="number"&&Number.isFinite(Z)&&Z>=0?Z:void 0}function N8(Z,$){let G=Z.get($);if(G===null||G.trim()==="")return;let X=Number(G);if(!Number.isFinite(X)||X<0||X>100)return;let Y=$.slice(0,-13),J=I0(Z.get(`${Y}-window-minutes`)),B=H0(Z.get(`${Y}-reset-at`)??void 0);if(X===0&&Z.get(`${Y}-window-minutes`)==="0"&&!B)return;return{usedPercent:X,remainingPercent:100-X,resetsAt:B,checkedAt:Date.now(),...J!==void 0?{windowMinutes:J}:{}}}function U8(Z,$){let G=Z.get($);if(G===null)return"absent";if(G.trim()==="")return"invalid";let X=Number(G);return Number.isFinite(X)&&X>=0&&X<=100?"valid":"invalid"}function jZ(Z){if(O8.some((Y)=>U8(Z,Y)==="invalid"))return{};let $={},G=N8(Z,"x-codex-primary-used-percent");if(G)$.primary=G;let X=N8(Z,"x-codex-secondary-used-percent");if(X)$.secondary=X;return $}function HZ(Z){let $=O8.map((G)=>U8(Z,G));return $.includes("valid")&&!$.includes("invalid")}function L8(Z){if(!Z)return;if(!Number.isFinite(Z.used_percent)||Z.used_percent<0||Z.used_percent>100)return;let $=I0(Z.window_minutes),G=H0(Z.reset_at);if(Z.used_percent===0&&$===void 0&&!G)return;return{usedPercent:Z.used_percent,remainingPercent:100-Z.used_percent,resetsAt:G,checkedAt:Date.now(),...$!==void 0?{windowMinutes:$}:{}}}function WZ(Z){let $={},G=L8(Z.rate_limits?.primary);if(G)$.primary=G;let X=L8(Z.rate_limits?.secondary);if(X)$.secondary=X;return $}function D8(Z){if(!Z)return;if(!Number.isFinite(Z.used_percent)||Z.used_percent<0||Z.used_percent>100)return;let $=I0(Z.limit_window_seconds),G=H0(Z.reset_at);if(Z.used_percent===0&&$===void 0&&!G)return;return{usedPercent:Z.used_percent,remainingPercent:100-Z.used_percent,resetsAt:G,checkedAt:Date.now(),...$!==void 0?{windowMinutes:$/60}:{}}}function QZ(Z){let $={},G=D8(Z.rate_limit?.primary_window);if(G)$.primary=G;let X=D8(Z.rate_limit?.secondary_window);if(X)$.secondary=X;let Y=V8(Z.rate_limit_reset_credits?.available_count);if(Y!==void 0)$.resetCreditsAvailable=Y;let J=V8(Z.rate_limit_reset_credits?.applicable_available_count);if(J!==void 0)$.resetCreditsApplicable=J;return $}var O8;var x0=_1(()=>{O8=["x-codex-primary-used-percent","x-codex-secondary-used-percent"]});import{createAssistantMessageEventStream as y3}from"@earendil-works/pi-ai";import{streamSimple as f3}from"@earendil-works/pi-ai/compat";import{createHash as D4,randomUUID as IZ}from"node:crypto";import{setTimeout as xZ}from"node:timers/promises";import{randomUUID as M1}from"node:crypto";import{mkdir as F1,rename as P1,rm as b1,writeFile as C1}from"node:fs/promises";import{dirname as T1}from"node:path";async function K0(Z,$){await F1(T1(Z),{recursive:!0});let G=`${Z}.${M1()}.tmp`;await C1(G,`${JSON.stringify($,null,2)}
|
|
2
|
+
`,{encoding:"utf8",mode:384});try{await P1(G,Z)}catch(X){throw await b1(G,{force:!0}).catch(()=>{}),X}}var S1=60000,E1=900000,R1=300000;function t0(Z){return Z instanceof Error?Z.message:String(Z)}function e0(Z){let $=t0(Z),G=Z?.status;if(typeof G==="number")return G===429||G>=500;if(!(Z instanceof Error))return!1;let X=Z.code;return Z.name==="AbortError"||Z.name==="TimeoutError"||$.includes("fetch failed")||X==="ECONNRESET"||X==="ECONNREFUSED"||X==="ETIMEDOUT"||X==="UND_ERR_CONNECT_TIMEOUT"}function J0(Z){let G=(Z.previous?.retryCount??0)+1,X,Y=Z.error?.retryAfter;if(typeof Y==="number"&&Y>0)X=Y*1000;else if(e0(Z.error))X=Math.min(E1,S1*2**Math.min(G-1,6));else X=R1;return{message:t0(Z.error),checkedAt:Z.now,nextRetryAt:Z.now+X,retryCount:G}}function d(Z,$){if(!Z?.nextRetryAt||Z.nextRetryAt<=$)return!1;return!0}function Z8(Z){if(!Z)return;let $=Number(Z);if(Number.isFinite($)&&$>0)return Math.ceil($);let G=Date.parse(Z);if(Number.isFinite(G)){let X=Math.ceil((G-Date.now())/1000);return X>0?X:void 0}return}import{appendFileSync as I1,chmodSync as x1,existsSync as F0,renameSync as $8,statSync as A1}from"node:fs";var P0={error:0,warn:1,info:2,debug:3,trace:4},y1=5242880,u,_0,G8;function f1(){if(u===void 0)return;return(typeof u==="function"?u():u)||void 0}function k1(){if(G8)return G8;let Z=typeof _0==="function"?_0():_0;if(Z&&Z in P0)return Z;return"info"}var m1=/^(authorization|x-api-key|cookie|set-cookie|refresh|access|token)$/i;function w1(Z){if(m1.test(Z))return!0;let $=Z.toLowerCase().replace(/[-_]/g,"");if($==="chatgptaccountid")return!0;if($.includes("apikey"))return!0;if($.endsWith("secret")||$.endsWith("password"))return!0;if($.endsWith("token")&&!$.endsWith("tokens"))return!0;return!1}var h1=/\b(Bearer\s+[\w.-]+|sk-[\w-]+|eyJ[\w.-]+)\b/g;function v1(Z){return b0(Z,new WeakSet)}function b0(Z,$){if(typeof Z==="string")return Z.replace(h1,"***REDACTED***");if(Array.isArray(Z)){if($.has(Z))return"[Circular]";$.add(Z);let G=Z.map((X)=>b0(X,$));return $.delete(Z),G}if(Z&&typeof Z==="object"){if($.has(Z))return"[Circular]";$.add(Z);let G={};for(let[X,Y]of Object.entries(Z))G[X]=w1(X)?"***REDACTED***":b0(Y,$);return $.delete(Z),G}return Z}var s=[],M0,p1=3;function C0(Z){try{x1(Z,384)}catch{}}function g1(Z){try{if(!(F0(Z)&&A1(Z).size>y1))return;for(let G=p1-1;G>=1;G--)if(F0(`${Z}.${G}`)){let X=`${Z}.${G+1}`;$8(`${Z}.${G}`,X),C0(X)}let $=`${Z}.1`;$8(Z,$),C0($)}catch{}}function X8(){if(!s.length)return;let Z=f1(),$=s.join("");if(s=[],!Z)return;try{if(g1(Z),F0(Z))C0(Z);I1(Z,$,{encoding:"utf8",mode:384})}catch{}}function l1(){if(!M0)M0=setTimeout(()=>{M0=void 0,X8()},500)}function d1(Z){try{return` ${JSON.stringify(v1(Z))}`}catch{return" [unserializable]"}}function i(Z,$,G,X){if(u===void 0)return;if(P0[$]>P0[k1()])return;let Y=`[${new Date().toISOString()}] ${$.toUpperCase()} [${Z}] ${G}`+(X===void 0?"":d1(X))+`
|
|
3
|
+
`;if(s.push(Y),s.length>=50)X8();else l1()}function R(Z){return{error:($,G)=>i(Z,"error",$,G),warn:($,G)=>i(Z,"warn",$,G),info:($,G)=>i(Z,"info",$,G),debug:($,G)=>i(Z,"debug",$,G),trace:($,G)=>i(Z,"trace",$,G)}}import{createServer as i1}from"node:http";import{setTimeout as u1}from"node:timers/promises";var z0="app_EMoamEEZ73f0CkXaXp7hrann",m="https://auth.openai.com",c=1455,s1=3000,c1="cortexkit-opencode-openai-auth";function B8(Z){return`${c1}/${Z}`}var z8="main",n1=`"${z8}" is a reserved account id; choose a different label.`;function j8(Z){let $=new Uint8Array(Z),G=String.fromCharCode(...$);return btoa(G).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}async function r1(){let $=Array.from(crypto.getRandomValues(new Uint8Array(43))).map((X)=>"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"[X%66]).join(""),G=j8(await crypto.subtle.digest("SHA-256",new TextEncoder().encode($)));return{verifier:$,challenge:G}}function Y8(Z){if(Z.length>16384)return;let $=Z.split(".");if($.length!==3)return;let G=$[1];if(!G||G.length>8192)return;try{return JSON.parse(Buffer.from(G,"base64url").toString())}catch{return}}function J8(Z){let $=Z.chatgpt_account_id;if(typeof $==="string"&&$.length>0)return $;let G=Z["https://api.openai.com/auth"];if(G&&typeof G==="object"&&!Array.isArray(G)&&typeof G.chatgpt_account_id==="string"&&G.chatgpt_account_id.length>0)return G.chatgpt_account_id;let X=Z.organizations;if(Array.isArray(X)){let Y=X[0];if(Y&&typeof Y==="object"&&!Array.isArray(Y)&&typeof Y.id==="string"&&Y.id.length>0)return Y.id}return}function S0(Z){if(Z.id_token){let $=Y8(Z.id_token),G=$&&J8($);if(G)return G}if(Z.access_token){let $=Y8(Z.access_token);return $?J8($):void 0}return}function a1(Z){return typeof Z==="string"&&Z.toLowerCase()===z8}function T0(Z){if(a1(Z))throw Error(n1);return Z}function o1(Z,$,G){let X=new URLSearchParams({response_type:"code",client_id:z0,redirect_uri:Z,scope:"openid profile email offline_access",code_challenge:$.challenge,code_challenge_method:"S256",id_token_add_organizations:"true",codex_cli_simplified_flow:"true",state:G,originator:"opencode"});return`${m}/oauth/authorize?${X.toString()}`}async function t1(Z,$,G){let X=await fetch(`${m}/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:Z,redirect_uri:$,client_id:z0,code_verifier:G.verifier}).toString()});if(!X.ok)throw Error(`Token exchange failed: ${X.status}`);return await X.json()}function e1(Z){return Z.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}var ZZ=`<!doctype html>
|
|
4
|
+
<html>
|
|
5
|
+
<head>
|
|
6
|
+
<title>CortexKit OpenAI Auth - Authorization Successful</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family:
|
|
10
|
+
system-ui,
|
|
11
|
+
-apple-system,
|
|
12
|
+
sans-serif;
|
|
13
|
+
display: flex;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
align-items: center;
|
|
16
|
+
height: 100vh;
|
|
17
|
+
margin: 0;
|
|
18
|
+
background: #131010;
|
|
19
|
+
color: #f1ecec;
|
|
20
|
+
}
|
|
21
|
+
.container {
|
|
22
|
+
text-align: center;
|
|
23
|
+
padding: 2rem;
|
|
24
|
+
}
|
|
25
|
+
h1 {
|
|
26
|
+
color: #f1ecec;
|
|
27
|
+
margin-bottom: 1rem;
|
|
28
|
+
}
|
|
29
|
+
p {
|
|
30
|
+
color: #b7b1b1;
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
33
|
+
</head>
|
|
34
|
+
<body>
|
|
35
|
+
<div class="container">
|
|
36
|
+
<h1>Authorization Successful</h1>
|
|
37
|
+
<p>You can close this window and return to OpenCode.</p>
|
|
38
|
+
</div>
|
|
39
|
+
<script>
|
|
40
|
+
setTimeout(() => window.close(), 2000)
|
|
41
|
+
</script>
|
|
42
|
+
</body>
|
|
43
|
+
</html>`,B0=(Z)=>`<!doctype html>
|
|
44
|
+
<html>
|
|
45
|
+
<head>
|
|
46
|
+
<title>CortexKit OpenAI Auth - Authorization Failed</title>
|
|
47
|
+
<style>
|
|
48
|
+
body {
|
|
49
|
+
font-family:
|
|
50
|
+
system-ui,
|
|
51
|
+
-apple-system,
|
|
52
|
+
sans-serif;
|
|
53
|
+
display: flex;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
align-items: center;
|
|
56
|
+
height: 100vh;
|
|
57
|
+
margin: 0;
|
|
58
|
+
background: #131010;
|
|
59
|
+
color: #f1ecec;
|
|
60
|
+
}
|
|
61
|
+
.container {
|
|
62
|
+
text-align: center;
|
|
63
|
+
padding: 2rem;
|
|
64
|
+
}
|
|
65
|
+
h1 {
|
|
66
|
+
color: #fc533a;
|
|
67
|
+
margin-bottom: 1rem;
|
|
68
|
+
}
|
|
69
|
+
p {
|
|
70
|
+
color: #b7b1b1;
|
|
71
|
+
}
|
|
72
|
+
.error {
|
|
73
|
+
color: #ff917b;
|
|
74
|
+
font-family: monospace;
|
|
75
|
+
margin-top: 1rem;
|
|
76
|
+
padding: 1rem;
|
|
77
|
+
background: #3c140d;
|
|
78
|
+
border-radius: 0.5rem;
|
|
79
|
+
}
|
|
80
|
+
</style>
|
|
81
|
+
</head>
|
|
82
|
+
<body>
|
|
83
|
+
<div class="container">
|
|
84
|
+
<h1>Authorization Failed</h1>
|
|
85
|
+
<p>An error occurred during authorization.</p>
|
|
86
|
+
<div class="error">${e1(Z)}</div>
|
|
87
|
+
</div>
|
|
88
|
+
</body>
|
|
89
|
+
</html>`,r,n,I=new Map;function f(){return I.size}function w(Z){if(I.delete(Z),f()===0)k()}async function $Z(){let Z=`http://localhost:${c}/auth/callback`;if(r)return{port:c,redirectUri:Z};if(n)return n;n=(async()=>{let $=i1((G,X)=>{let Y=new URL(G.url||"/",`http://127.0.0.1:${c}`);if(Y.pathname==="/auth/callback"){let J=Y.searchParams.get("code"),B=Y.searchParams.get("state"),z=Y.searchParams.get("error"),j=Y.searchParams.get("error_description");if(z){let Q=j||z;if(B){let W=I.get(B);if(W)W.reject(Error(Q)),w(B);else if(f()===0)k()}else if(f()===0)k();X.writeHead(200,{"Content-Type":"text/html"}),X.end(B0(Q));return}if(!B){if(f()===0)k();X.writeHead(400,{"Content-Type":"text/html"}),X.end(B0("Missing state parameter"));return}if(!J){if(f()===0)k();X.writeHead(400,{"Content-Type":"text/html"}),X.end(B0("Missing authorization code"));return}let H=I.get(B);if(!H){if(f()===0)k();X.writeHead(400,{"Content-Type":"text/html"}),X.end(B0("Unknown state — authorization may have expired"));return}w(B),t1(J,Z,H.pkce).then((Q)=>H.resolve(Q)).catch((Q)=>H.reject(Q)),X.writeHead(200,{"Content-Type":"text/html"}),X.end(ZZ);return}if(Y.pathname==="/cancel"){let J=Y.searchParams.get("state"),B=J?[J]:[...I.keys()];X.writeHead(200),X.end("Login cancelled");for(let z of B){let j=I.get(z);if(!j)continue;j.reject(Error("Login cancelled")),w(z)}if(f()===0)k();return}X.writeHead(404),X.end("Not found")});return await new Promise((G,X)=>{$.once("error",X),$.listen(c,"127.0.0.1",()=>{$.off("error",X),G()})}),r=$,{port:c,redirectUri:Z}})();try{return await n}finally{n=void 0}}function k(){if(r){let Z=r;r=void 0,Z.close(()=>{}),Z.closeIdleConnections?.(),setTimeout(()=>{Z.closeAllConnections?.()},25).unref?.()}}function GZ(Z,$,G=300000,X){return new Promise((Y,J)=>{if(X?.aborted){w($),J(Error("Login cancelled"));return}let B=setTimeout(()=>{if(I.get($))w($),J(Error("OAuth callback timeout — authorization took too long"))},G),z=()=>{if(clearTimeout(B),I.get($))w($),J(Error("Login cancelled"))};if(X)X.addEventListener("abort",z);I.set($,{pkce:Z,state:$,resolve:(j)=>{if(clearTimeout(B),X)X.removeEventListener("abort",z);Y(j)},reject:(j)=>{if(clearTimeout(B),X)X.removeEventListener("abort",z);J(j)}})})}async function XZ(Z){let $=await fetch(`${m}/api/accounts/deviceauth/usercode`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":B8(Z)},body:JSON.stringify({client_id:z0})});if(!$.ok)throw Error("Failed to initiate device authorization");let G=await $.json();return{deviceData:G,url:`${m}/codex/device`,instructions:`Enter code: ${G.user_code}`}}async function YZ(Z,$,G){let X=Math.max(parseInt(Z.interval,10)||5,1)*1000,Y=typeof Z.expires_in==="number"?Z.expires_in:parseInt(Z.expires_in||"",10),J=!Number.isNaN(Y)&&Y>0?Y*1000:900000,B=Date.now();while(!0){if(G?.aborted)throw Error("Device authorization cancelled");if(Date.now()-B>J)throw Error("Device authorization expired");let z=await fetch(`${m}/api/accounts/deviceauth/token`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":B8($)},body:JSON.stringify({device_auth_id:Z.device_auth_id,user_code:Z.user_code})});if(z.ok){let Q=await z.json(),W=await fetch(`${m}/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:Q.authorization_code,redirect_uri:`${m}/deviceauth/callback`,client_id:z0,code_verifier:Q.code_verifier}).toString()});if(!W.ok)throw Error(`Token exchange failed: ${W.status}`);return await W.json()}if(z.status!==403&&z.status!==404)throw Error(`Device authorization failed: ${z.status}`);let j=J-(Date.now()-B);if(j<=0)throw Error("Device authorization expired");let H=Math.min(X+s1,j);try{await u1(H,void 0,{signal:G})}catch(Q){if(Q instanceof Error&&Q.name==="AbortError")throw Error("Device authorization cancelled");throw Q}}}function H8(Z,$){let G=-1;if($.accountId)G=Z.findIndex((X)=>X.accountId&&X.accountId===$.accountId);if(G<0)G=Z.findIndex((X)=>X.id===$.id);if(G<0&&$.label&&!$.accountId)G=Z.findIndex((X)=>X.label===$.label);if(G>=0)return Z[G]={...Z[G],...$,addedAt:Z[G]?.addedAt??$.addedAt},G;return Z.push($),Z.length-1}async function W8(Z){let{label:$,headless:G=!1,signal:X,version:Y}=Z;if(T0($),G){let{deviceData:Q,url:W,instructions:V}=await XZ(Y),N=(async()=>{let O=await YZ(Q,Y,X),U=Date.now(),E=S0(O);return{id:T0($||E||crypto.randomUUID()),label:$||void 0,type:"oauth",access:O.access_token,refresh:O.refresh_token,expires:U+(O.expires_in??3600)*1000,enabled:!0,addedAt:U,lastUsed:U,lastRefreshedAt:U,accountId:E}})();return{url:W,instructions:V,completion:N}}let{redirectUri:J}=await $Z(),B=await r1(),z=j8(crypto.getRandomValues(new Uint8Array(32)).buffer),j=o1(J,B,z),H=(async()=>{try{if(X?.aborted)throw Error("Login cancelled");let Q=await GZ(B,z,void 0,X),W=Date.now(),V=S0(Q);return{id:T0($||V||crypto.randomUUID()),label:$||void 0,type:"oauth",access:Q.access_token,refresh:Q.refresh_token,expires:W+(Q.expires_in??3600)*1000,enabled:!0,addedAt:W,lastUsed:W,lastRefreshedAt:W,accountId:V}}finally{w(z)}})();return{url:j,instructions:"Complete authorization in your browser. This window will close automatically.",completion:H}}import{basename as JZ,dirname as BZ,join as zZ}from"node:path";var j0="openai-auth.json",Q8="openai-auth-state.json";function E0(Z){return JZ(Z)===j0?zZ(BZ(Z),Q8):`${Z}.state.json`}function R0(Z){if(Z instanceof Error)return Z.message;if(typeof Z==="string")return Z;try{return JSON.stringify(Z)}catch{return String(Z)}}var VZ=R("quota");var NZ="https://chatgpt.com/backend-api/wham/usage";async function A0(Z){let $=Z.logger??VZ,G=Z.accountKey??"unknown",X=Date.now();$.debug("wham usage fetch started",{pid:process.pid,accountId:G});try{let Y=await Z.fetchImpl(NZ,{signal:AbortSignal.timeout(15000),headers:{authorization:`Bearer ${Z.accessToken}`,"chatgpt-account-id":Z.accountId??"","oai-client-platform":"web","oai-client-version":"0","x-openai-target-path":"/backend-api/wham/usage"}});if(!Y.ok){let z=Z8(Y.headers.get("retry-after"));throw Object.assign(Error(`wham usage check failed: ${Y.status}`),{status:Y.status,retryAfter:z})}let{normalizeWham:J}=await Promise.resolve().then(() => (x0(),q8)),B=J(await Y.json());return $.debug("wham usage fetch succeeded",{pid:process.pid,accountId:G,status:Y.status,elapsedMs:Date.now()-X}),B}catch(Y){throw $.warn("wham usage fetch failed",{pid:process.pid,accountId:G,status:typeof Y?.status==="number"?Y.status:"error",elapsedMs:Date.now()-X,error:R0(Y)}),Y}}import{createHash as MZ}from"node:crypto";import{randomUUID as y0}from"node:crypto";import{mkdir as K8,readFile as f0,rename as LZ,rm as p,stat as _8,writeFile as W0}from"node:fs/promises";import{dirname as DZ,join as M8}from"node:path";var OZ=globalThis.setTimeout.bind(globalThis),UZ=globalThis.clearTimeout.bind(globalThis);function qZ(Z){let $=Z?.code;return $==="ENOENT"||$==="EINVAL"||$==="ENOTDIR"}async function a(Z){let $=`${Z.path}.${Z.name}.lock`,G=M8($,"owner.json"),X=y0(),Y=Z.now??Date.now,J=null,B=!1,z=null,j=`${$}.evicting`,H=M8(j,"owner.json"),Q=y0(),W=5000,V=8;async function N(){try{return JSON.parse(await f0($,"utf8"))}catch(D){if(D.code!=="EISDIR")throw D;return JSON.parse(await f0(G,"utf8"))}}async function O(){await W0($,`${JSON.stringify({ownerId:X,expiresAt:Y()+Z.ttlMs})}
|
|
90
|
+
`,{encoding:"utf8",mode:384})}async function U(){try{return await W0($,`${JSON.stringify({ownerId:X,expiresAt:Y()+Z.ttlMs})}
|
|
91
|
+
`,{encoding:"utf8",mode:384,flag:"wx"}),!0}catch(D){let q=D.code;if(q==="EEXIST"||q==="EISDIR")return!1;if(q==="ENOENT"){await K8(DZ($),{recursive:!0});try{return await W0($,`${JSON.stringify({ownerId:X,expiresAt:Y()+Z.ttlMs})}
|
|
92
|
+
`,{encoding:"utf8",mode:384,flag:"wx"}),!0}catch(_){let Y0=_.code;if(Y0==="EEXIST"||Y0==="EISDIR")return!1;throw _}}throw D}}async function E(){await new Promise((D)=>setTimeout(D,Math.floor(Math.random()*4)))}async function X0(){try{let D=await N();return Number(D?.expiresAt)>Y()}catch{try{return(await _8($)).mtimeMs+Z.ttlMs>Y()}catch{return!1}}}async function T(){try{return JSON.parse(await f0(H,"utf8"))?.ownerId===Q}catch{return!1}}async function A(){if(await T())await p(j,{recursive:!0,force:!0}).catch(()=>{})}async function F(){await K8(j);try{await W0(H,`${JSON.stringify({ownerId:Q,createdAt:Y()})}
|
|
93
|
+
`,{encoding:"utf8",mode:384,flag:"wx"})}catch(D){if(qZ(D))return!1;throw await A(),D}if(Z.onStep)await Z.onStep("eviction-marker-acquired");return!0}async function n0(){let D;try{D=await _8(j)}catch(_){if(_.code==="ENOENT")return"missing";throw _}if(D.mtimeMs+W>Y())return"fresh";if(Z.onStep)await Z.onStep("stale-marker-stat");let q=`${j}.${y0()}`;try{await LZ(j,q)}catch(_){if(_.code==="ENOENT")return"missing";throw _}if(Z.onStep)await Z.onStep("stale-marker-claimed");return await p(q,{recursive:!0,force:!0}).catch(()=>{}),"recovered"}async function r0(D){try{if(!await F())return!1}catch(q){if(q.code==="EEXIST")return!1;throw q}try{await D()}finally{await A()}return!0}async function L1(){for(let D=0;D<V;D++){if(Z.onStep)await Z.onStep("relinquish-read");let q;try{q=await N()}catch{return}if(q?.ownerId!==X)return;try{await p($,{recursive:!0,force:!0});return}catch{await E()}}}function a0(){if(!Z.renew||B)return;let D=Z.renewIntervalMs??Math.max(1000,Math.floor(Z.ttlMs/3));if(J=OZ(()=>{let q=(async()=>{let _=!B;try{if(!await r0(async()=>{let o0=await N(),D1=Y();if(B||o0?.ownerId!==X){_=!1;return}if(Number(o0?.expiresAt)<=D1){_=!1;return}if(Z.onStep)await Z.onStep("renewal-owner-confirmed");if(B){_=!1;return}if(!await T())return;if(Z.onStep)await Z.onStep("renewal-write-fenced");if(B){_=!1;return}if(!await T())return;if(Z.onStep)await Z.onStep("renewal-write-ready");if(await O(),!await T()){_=!1,await L1();return}})&&Z.onStep)await Z.onStep("renewal-marker-unavailable")}catch{}finally{if(Z.onStep)try{await Z.onStep("renewal-finished")}catch{}if(_&&!B)a0()}})();z=q,q.finally(()=>{if(z===q)z=null})},D),"unref"in J)J.unref()}let y=await U();if(!y)for(let D=0;D<V;D++){if(y=await U(),y)break;if(await X0())return null;try{if(!await F()){await E();continue}}catch(q){if(q.code!=="EEXIST")throw q;if(await n0()==="fresh")return null;await E();continue}try{if(await X0())return null;if(!await T())return null;if(Z.onStep)await Z.onStep("stale-lock-confirmed");if(!await T())return null;if(await p($,{recursive:!0,force:!0}).catch(()=>{}),!await T())return null;if(y=await U(),!y)return null;if(!await T())return await p($,{recursive:!0,force:!0}).catch(()=>{}),y=!1,null;break}finally{await A()}}if(!y)return null;return a0(),{release:async()=>{if(B=!0,J)UZ(J),J=null;await z;for(let D=0;D<V;D++){try{if(await r0(async()=>{if((await N())?.ownerId!==X)return;if(Z.onStep)await Z.onStep("release-owner-confirmed");if(!await T())return;await p($,{recursive:!0,force:!0}).catch(()=>{})}))return;await n0()}catch{return}await E()}}}}var FZ=globalThis.setTimeout,PZ=5;function o(Z){let $=Z?.quota?.checkIntervalMinutes??PZ;return Math.max(1,$)*60000}function bZ(Z){return Z?.quota?.enabled!==!1}function CZ(Z){let $=Z?.quota?.minimumRemaining||{};return{primary:$.primary??$["5h"]??15,secondary:$.secondary??$["1w"]??10}}function F8(Z,$,G){if(!bZ($))return G+o($);let X=CZ($),Y=[];for(let J of["primary","secondary"]){let B=Z?.[J];if(!B)continue;if(B.remainingPercent>=X[J])continue;let z=B.resetsAt?Date.parse(B.resetsAt):Number.NaN;if(!Number.isFinite(z)||z<=G)return G+o($);Y.push(z)}if(!Y.length)return G+o($);return Math.min(...Y)+60000}function TZ(Z){if(!Z?.quota?.mainQuota||!Z.quota.mainQuotaCheckedAt)return null;return{quota:Z.quota.mainQuota,checkedAt:Z.quota.mainQuotaCheckedAt,tokenFingerprint:Z.quota.mainQuotaToken}}function SZ(Z){let $=Z?.quota?.refreshEveryNRequests;return typeof $==="number"&&Number.isFinite($)&&$>0?Math.floor($):0}function P(Z){return MZ("sha256").update(Z).digest("hex").slice(0,16)}class S{main=null;mainTokenFp=null;mainQuotaAccountId=null;fallbacks=new Map;fallbackTokenFps=new Map;inflightMain=null;inflightMainFp=null;inflightFallbacks=new Map;mainLastApiError=void 0;fallbackApiErrors=new Map;fallbackErrorTokenFps=new Map;rateLimitedUntilMap=new Map;apiGate=Promise.resolve();lastApiCallAt=0;storage;fetchImpl;now;fetchQuotaFn;onMainQuotaFetched;onApiError;configPath;constructor(Z){this.storage=Z.storage,this.fetchImpl=Z.fetchImpl??fetch,this.configPath=Z.configPath,this.now=Z.now??Date.now,this.fetchQuotaFn=Z.fetchQuotaFn,this.onMainQuotaFetched=Z.onMainQuotaFetched,this.onApiError=Z.onApiError,this.seedMainFromStorage(Z.storage),this.seedMainBackoffFromStorage(Z.storage)}getMain(Z){if(Z&&this.main&&this.mainTokenFp&&this.mainTokenFp!==P(Z))this.main=null,this.mainTokenFp=null;return this.main}getFallback(Z,$){let G=this.fallbacks.get(Z)??null;if(!$||!G)return G;if(this.fallbackTokenFps.get(Z)!==P($))return this.fallbacks.delete(Z),this.fallbackTokenFps.delete(Z),null;return G}getAllFallbacks(){return this.fallbacks}setMain(Z,$,G,X=!1){if(!X&&!P8($.quota))return;if(this.mainTokenFp=P(Z),G&&this.mainQuotaAccountId&&this.mainQuotaAccountId!==G)this.clearRateLimited("main");if(G)this.mainQuotaAccountId=G;if(this.main=$,k0($.quota))this.clearRateLimited("main")}peekMainForPolicy(Z){if(Z&&this.mainQuotaAccountId&&this.mainQuotaAccountId!==Z)return null;return this.main}peekFallbackForPolicy(Z,$){let G=this.fallbacks.get(Z)??null;if($&&G?.accountId&&G.accountId!==$)return null;return G}setFallback(Z,$,G,X=!1,Y){if(!X&&!P8($.quota))return;let J=this.fallbacks.get(Z)?.accountId;if(Y&&J&&Y!==J)this.clearRateLimited(Z);let B=Y??J;if(this.fallbacks.set(Z,{...$,accountId:B}),G)this.fallbackTokenFps.set(Z,P(G));else this.fallbackTokenFps.delete(Z);if(k0($.quota))this.clearRateLimited(Z)}markRateLimited(Z,$){let G=this.rateLimitedUntilMap.get(Z);if(G!==void 0&&G>$)return;this.rateLimitedUntilMap.set(Z,$)}isRateLimited(Z){let $=this.rateLimitedUntilMap.get(Z);return $!==void 0&&this.now()<$}rateLimitedUntil(Z){return this.rateLimitedUntilMap.get(Z)}clearRateLimited(Z){this.rateLimitedUntilMap.delete(Z)}async refreshMain(Z){let $=P(Z);if(this.mainTokenFp&&this.mainTokenFp!==$)this.main=null,this.mainTokenFp=null;if(this.inflightMain&&this.inflightMainFp===$)return this.inflightMain;if(this.isBackedOff()){if(this.main)return this.main.quota;throw Error("Quota API rate-limited — try again later")}return this.inflightMainFp=$,this.inflightMain=this._fetchMain(Z),this.inflightMain}async refreshFallback(Z,$){let G=S.fallbackInflightKey(Z,$),X=this.inflightFallbacks.get(G);if(X)return X;if(this.isFallbackBackedOff(Z,$)){let J=this.getFallback(Z,$);if(J)return J.quota;throw Error("Quota API rate-limited — try again later")}let Y=this._fetchFallback(Z,$);return this.inflightFallbacks.set(G,Y),Y}async refreshAllFallbacks(Z){let $=this.now();for(let G of Z){if(G.enabled===!1)continue;if(!G.access)continue;let X=this.getFallback(G.id,G.access);if(X&&$<X.refreshAfter)continue;try{await this.refreshFallback(G.id,G.access)}catch{}}}refreshMainInBackground(Z){let $=P(Z);if(this.inflightMain&&this.inflightMainFp===$)return;if(this.isBackedOff())return;this.refreshMain(Z).catch(()=>{})}isMainStale(){if(!this.main)return!0;return this.now()>=this.main.refreshAfter}isFallbackStale(Z,$){let G=this.getFallback(Z,$);if(!G)return!0;return this.now()>=G.refreshAfter}shouldRefreshOnRequestCount(Z){let $=SZ(this.storage);if($<=0)return!1;return Z>0&&Z%$===0}needsRefresh(Z){return this.isMainStale()||this.shouldRefreshOnRequestCount(Z)}updateStorage(Z){this.storage=Z,this.seedMainFromStorage(Z),this.seedMainBackoffFromStorage(Z)}seedMainFromStorage(Z,$){let G=TZ(Z);if(!G)return;let X=$?P($):null;if(X&&G.tokenFingerprint&&G.tokenFingerprint!==X)return;let Y={quota:G.quota,refreshAfter:F8(G.quota,Z,G.checkedAt),checkedAt:G.checkedAt};if(this.main&&this.main.checkedAt>=Y.checkedAt&&(!X||!this.mainTokenFp||this.mainTokenFp===X))return;if(this.main=Y,this.mainTokenFp=G.tokenFingerprint??null,Z?.mainAccountId){if(this.mainQuotaAccountId&&this.mainQuotaAccountId!==Z.mainAccountId)this.clearRateLimited("main");this.mainQuotaAccountId=Z.mainAccountId}}seedMainBackoffFromStorage(Z){let $=Z?.quota?.mainLastQuotaApiError;this.mainLastApiError=$&&d($,this.now())?$:void 0}seedFallbacksFromAccounts(Z){let $=o(this.storage);for(let G of Z){if(G.enabled===!1)continue;if(!G.quota)continue;let X=Math.max(G.quota.primary?.checkedAt??0,G.quota.secondary?.checkedAt??0);if(X<=0)continue;let Y=this.getFallback(G.id,G.access);if(Y&&Y.checkedAt>=X)continue;this.setFallback(G.id,{quota:G.quota,refreshAfter:X+$,checkedAt:X},G.access,!1,G.accountId)}}isBackedOff(){return d(this.mainLastApiError,this.now())}isFallbackBackedOff(Z,$){if($){if(this.fallbackErrorTokenFps.get(Z)!==P($))return!1}return d(this.fallbackApiErrors.get(Z),this.now())}getLastApiError(){return this.mainLastApiError}static API_CALL_GAP_MS=1000;static fallbackInflightKey(Z,$){return JSON.stringify([Z,P($)])}static quotaLockName(Z){return`opencode-fallback-quota-refresh-${Z.replace(/[^a-zA-Z0-9._-]+/g,"-")||"account"}`}_enqueueApiFetch(Z){let $=async()=>{let X=this.now()-this.lastApiCallAt;if(X<S.API_CALL_GAP_MS)await new Promise((Y)=>{let J=FZ(Y,S.API_CALL_GAP_MS-X);if(typeof J==="object"&&"unref"in J)J.unref()});return this.lastApiCallAt=this.now(),Z()},G=this.apiGate.then($,$);return this.apiGate=G.catch(()=>{}),G}async _fetchMain(Z){let $=P(Z);return this._enqueueApiFetch(async()=>{try{if(this.isBackedOff()){if(this.main)return this.main.quota;throw Error("Quota API rate-limited — try again later")}let G=await a({name:"opencode-main-quota-refresh",ttlMs:30000,path:this.configPath});if(!G){let X=this.main;if(X&&this.now()<X.refreshAfter)return X.quota;throw Error("Quota refresh is already in progress")}try{let X=this.now(),Y=await this._fetchQuota(Z),J=this.now();if(this.mainTokenFp=P(Z),this.main={quota:Y,refreshAfter:F8(Y,this.storage,J),checkedAt:J},k0(Y))this.clearRateLimited("main");return this.mainLastApiError=void 0,this.onMainQuotaFetched?.(Y,J,this.mainTokenFp,X),Y}catch(X){throw this._handleMainFetchError(X),X}finally{await G.release()}}finally{if(this.inflightMainFp===$)this.inflightMain=null,this.inflightMainFp=null}})}async _fetchFallback(Z,$){return this._enqueueApiFetch(async()=>{try{if(this.isFallbackBackedOff(Z,$)){let X=this.getFallback(Z,$);if(X)return X.quota;throw Error("Quota API rate-limited — try again later")}let G=await a({name:S.quotaLockName(Z),ttlMs:30000,path:this.configPath});if(!G){let X=this.getFallback(Z,$);if(X&&this.now()<X.refreshAfter)return X.quota;throw Error("Quota refresh is already in progress")}try{let X=await this._fetchQuota($),Y=this.now();return this.setFallback(Z,{quota:X,refreshAfter:Y+o(this.storage),checkedAt:Y},$,!0),this.fallbackApiErrors.delete(Z),this.fallbackErrorTokenFps.delete(Z),X}finally{await G.release()}}catch(G){throw this._handleFallbackFetchError(Z,$,G),G}finally{this.inflightFallbacks.delete(S.fallbackInflightKey(Z,$))}})}async _fetchQuota(Z){if(!this.fetchQuotaFn)throw Error("Quota fetch not available — no fetchQuotaFn injected (push-only mode)");return this.fetchQuotaFn({accessToken:Z,fetchImpl:this.fetchImpl,now:this.now})}static isAuthError(Z){return Z?.status===401}_handleMainFetchError(Z){if(S.isAuthError(Z))return;this.mainLastApiError=J0({error:Z,now:this.now(),previous:this.mainLastApiError}),this.onApiError?.(this.mainLastApiError)}_handleFallbackFetchError(Z,$,G){if(S.isAuthError(G))return;let X=P($),Y=this.fallbackErrorTokenFps.get(Z)===X?this.fallbackApiErrors.get(Z):void 0;this.fallbackApiErrors.set(Z,J0({error:G,now:this.now(),previous:Y})),this.fallbackErrorTokenFps.set(Z,X)}}function P8(Z){return Boolean(Z.primary||Z.secondary)}function k0(Z){let{primary:$,secondary:G}=Z;if(!$&&!G)return!1;if($&&$.usedPercent>=100)return!1;if(G&&G.usedPercent>=100)return!1;return!0}import{readFile as vZ}from"node:fs/promises";var C4=R("refresh"),E8=R("accounts"),AZ=1e4,yZ=15000,m0=50;function R8(Z){return Z.type==="oauth"}function fZ(Z){let $=Z?.trim();if(!$)return!1;try{let G=new URL($);return(G.protocol==="http:"||G.protocol==="https:")&&!G.username&&!G.password}catch{return!1}}function L(Z){return Z!=null&&typeof Z==="object"&&!Array.isArray(Z)}var kZ=new Set(["__proto__","constructor","prototype"]);function b(Z){return Z.length>0&&!kZ.has(Z)}function b8(Z){return{id:typeof Z.id==="string"&&Z.id.trim()?Z.id.trim():IZ(),label:typeof Z.label==="string"?Z.label:void 0,enabled:typeof Z.enabled==="boolean"?Z.enabled:void 0,addedAt:typeof Z.addedAt==="number"?Z.addedAt:void 0,lastUsed:typeof Z.lastUsed==="number"?Z.lastUsed:void 0,accountId:typeof Z.accountId==="string"?Z.accountId:void 0}}function C8(Z){if(!L(Z))return;if(typeof Z.message!=="string")return;let $=Number(Z.checkedAt);if(!Number.isFinite($))return;let G=Number(Z.nextRetryAt),X=Number(Z.retryCount);return{message:Z.message,checkedAt:$,nextRetryAt:Number.isFinite(G)?G:void 0,retryCount:Number.isFinite(X)?X:void 0,tokenHash:typeof Z.tokenHash==="string"?Z.tokenHash:void 0}}function mZ(Z){if(!L(Z))return;let $={};for(let G of["primary","secondary"]){let X=Z[G];if(!L(X))continue;let Y=Number(X.usedPercent),J=Number(X.remainingPercent),B=Number(X.checkedAt);if(!Number.isFinite(Y)||!Number.isFinite(J)||!Number.isFinite(B))continue;let z=typeof X.windowMinutes==="number"?X.windowMinutes:Number.NaN;$[G]={usedPercent:Y,remainingPercent:J,checkedAt:B,resetsAt:typeof X.resetsAt==="string"?X.resetsAt:void 0,...Number.isFinite(z)&&z>0?{windowMinutes:z}:{}}}for(let G of["resetCreditsAvailable","resetCreditsApplicable"]){let X=typeof Z[G]==="number"?Z[G]:Number.NaN;if(Number.isFinite(X)&&X>=0)$[G]=X}return Object.keys($).length?$:void 0}function wZ(Z){if(!L(Z))return null;if(Z.type==="api"){let $=typeof Z.baseURL==="string"?Z.baseURL.trim():"",G=typeof Z.apiKey==="string"?Z.apiKey.trim():"";if(!fZ($))return null;let X=Z.authHeader==="x-api-key"?"x-api-key":"authorization-bearer";return{...b8(Z),type:"api",apiKey:G||void 0,baseURL:$,authHeader:X}}if(Z.type!=="oauth")return null;if(typeof Z.refresh!=="string"||!Z.refresh.trim())return null;return{...b8(Z),type:"oauth",access:typeof Z.access==="string"?Z.access:void 0,refresh:Z.refresh,expires:typeof Z.expires==="number"?Z.expires:void 0,lastRefreshedAt:typeof Z.lastRefreshedAt==="number"?Z.lastRefreshedAt:void 0,lastRefreshError:C8(Z.lastRefreshError),lastQuotaRefreshError:C8(Z.lastQuotaRefreshError),quota:mZ(Z.quota)}}function hZ(Z){if(!L(Z))return;let $=Object.create(null);for(let[G,X]of Object.entries(Z)){if(!b(G)||!L(X))continue;let Y={};if(L(X.inFlight))Y.inFlight={...X.inFlight};if(L(X.lastOutcome)&&typeof X.lastOutcome.code==="string"&&X.lastOutcome.code.length>0&&typeof X.lastOutcome.at==="number"&&Number.isFinite(X.lastOutcome.at))Y.lastOutcome={code:X.lastOutcome.code,at:X.lastOutcome.at,...L(X.lastOutcome.previousOutcome)&&typeof X.lastOutcome.previousOutcome.code==="string"&&X.lastOutcome.previousOutcome.code.length>0&&typeof X.lastOutcome.previousOutcome.at==="number"&&Number.isFinite(X.lastOutcome.previousOutcome.at)?{previousOutcome:{code:X.lastOutcome.previousOutcome.code,at:X.lastOutcome.previousOutcome.at}}:{}};if(typeof X.cooldownUntil==="number"&&Number.isFinite(X.cooldownUntil))Y.cooldownUntil=X.cooldownUntil;if(Object.keys(Y).length>0)$[G]=Y}return Object.keys($).length>0?$:void 0}var T8=new Set;function I8(Z){if(Z.length===0)return;let $=JSON.stringify([...Z].sort());if(T8.has($))return;T8.add($),E8.warn("account load-dropped, preserved on disk",{droppedIds:Z})}function x8(Z){if(!L(Z)||!Array.isArray(Z.accounts))return null;let $=Z.accounts,G=$.map(wZ).filter((X)=>X!=null);if(G.length<$.length){let X=new Set;for(let B of $)if(L(B)&&typeof B.id==="string"&&B.id.trim())X.add(B.id.trim());let Y=new Set(G.map((B)=>B.id)),J=[];for(let B of X)if(!Y.has(B))J.push(B);I8(J)}return{version:1,main:{type:"opencode",provider:"openai"},routing:L(Z.routing)?Z.routing:void 0,fallbackOn:Array.isArray(Z.fallbackOn)?Z.fallbackOn.filter((X)=>Number.isInteger(X)):void 0,refresh:L(Z.refresh)?Z.refresh:void 0,quota:L(Z.quota)?Z.quota:void 0,reset:hZ(Z.reset),dump:L(Z.dump)?Z.dump:void 0,costZeroing:L(Z.costZeroing)?Z.costZeroing:void 0,killswitch:L(Z.killswitch)?Z.killswitch:void 0,logging:L(Z.logging)?Z.logging:void 0,cachekeep:L(Z.cachekeep)?Z.cachekeep:void 0,mainAccountId:typeof Z.mainAccountId==="string"?Z.mainAccountId:void 0,accounts:G}}async function t(Z){try{return{exists:!0,value:JSON.parse(await vZ(Z,"utf8"))}}catch($){if($.code==="ENOENT")return{exists:!1,value:null};throw $}}function x(Z){return Object.fromEntries(Object.entries(Z).filter(([,$])=>$!==void 0))}async function A8(Z){let $;try{$=(await t(Z)).value}catch{return null}if(!L($)||!Array.isArray($.accounts))return null;let G=new Set;for(let X of $.accounts){if(!L(X))continue;if(typeof X.id!=="string")continue;let Y=X.id.trim();if(!Y)continue;G.add(Y)}return G}function y8(Z,$){if(!L(Z))return Z;let G=L($)?$:{},X=L(G.main)?G.main:void 0,Y=L(G.accounts)?G.accounts:{},J=L(Z.quota)?Z.quota:{},B=L(Z.refresh)?Z.refresh:{},z=X??J,j=X??B,Q=Array.isArray(Z.accounts)?Z.accounts.map((W)=>{if(!L(W))return W;let V=typeof W.id==="string"&&L(Y[W.id])?Y[W.id]:{};return{...W,...V}}):void 0;return k8({...Z,refresh:x({...B,mainLastRefreshError:j.lastRefreshError,mainRefreshLeaseId:j.refreshLeaseId,mainRefreshLeaseUntil:j.refreshLeaseUntil,mainRefreshLeaseTokenHash:j.refreshLeaseTokenHash}),quota:x({...J,mainQuota:z.quota,mainQuotaCheckedAt:z.quotaCheckedAt,mainQuotaToken:z.quotaToken,mainLastQuotaApiError:z.lastQuotaApiError}),accounts:Q})}async function f8(Z){let $=await t(Z.configPath);if(!$.exists)return null;let G=await t(Z.statePath);return x8(y8($.value,G.value))}function k8(Z){return Object.fromEntries(Object.entries(Z).filter(([,$])=>$!==void 0))}function pZ(Z){return x({id:Z.id,label:Z.label,type:Z.type,enabled:Z.enabled,addedAt:Z.addedAt,accountId:Z.accountId,baseURL:Z.type==="api"?Z.baseURL:void 0,authHeader:Z.type==="api"?Z.authHeader:void 0})}function gZ(Z){if(Z.type==="api")return x({apiKey:Z.apiKey,lastUsed:Z.lastUsed});return x({access:Z.access,refresh:Z.refresh,expires:Z.expires,lastUsed:Z.lastUsed,lastRefreshedAt:Z.lastRefreshedAt,lastRefreshError:Z.lastRefreshError,lastQuotaRefreshError:Z.lastQuotaRefreshError,quota:Z.quota})}function lZ(Z){let $=Z.refresh?x({enabled:Z.refresh.enabled,intervalMinutes:Z.refresh.intervalMinutes,refreshBeforeExpiryMinutes:Z.refresh.refreshBeforeExpiryMinutes}):void 0,G=Z.quota?x({enabled:Z.quota.enabled,checkIntervalMinutes:Z.quota.checkIntervalMinutes,refreshEveryNRequests:Z.quota.refreshEveryNRequests,minimumRemaining:Z.quota.minimumRemaining,failClosedOnUnknownQuota:Z.quota.failClosedOnUnknownQuota,showToasts:Z.quota.showToasts}):void 0;return k8({version:1,main:Z.main,routing:Z.routing,fallbackOn:Z.fallbackOn,refresh:$,quota:G,reset:Z.reset,dump:Z.dump,costZeroing:Z.costZeroing,killswitch:Z.killswitch,logging:Z.logging,cachekeep:Z.cachekeep,mainAccountId:Z.mainAccountId,accounts:Z.accounts.map(pZ)})}async function S8(Z){let $=Date.now(),G=$+yZ,X=0;while(Date.now()<=G){X++;let B=await a({name:"save",ttlMs:AZ,path:Z});if(B)return B;let z=G-Date.now();if(z<=0)break;await xZ(Math.min(m0+nZ(m0),z))}let Y=Date.now()-$,J=Math.round(Y/Math.max(1,X));throw Error(`Timed out after ${Y}ms waiting for the account store lock on ${Z} (${X} attempts, ~${J}ms apart; retry interval is ${m0}ms). A gap near the retry interval means lock contention; a much larger one means this process's event loop was saturated and the wait expired without getting scheduled.`)}function dZ(Z){let $=Object.fromEntries(Z.accounts.map((G)=>[G.id,gZ(G)]));return{version:1,main:x({quota:Z.quota?.mainQuota,quotaCheckedAt:Z.quota?.mainQuotaCheckedAt,quotaToken:Z.quota?.mainQuotaToken,lastQuotaApiError:Z.quota?.mainLastQuotaApiError,lastRefreshError:Z.refresh?.mainLastRefreshError,refreshLeaseId:Z.refresh?.mainRefreshLeaseId,refreshLeaseUntil:Z.refresh?.mainRefreshLeaseUntil,refreshLeaseTokenHash:Z.refresh?.mainRefreshLeaseTokenHash}),accounts:$}}function m8(Z){if(!L(Z)||!Array.isArray(Z.accounts))return null;let $=new Set;for(let G of Z.accounts){if(!L(G))continue;if(typeof G.id!=="string")continue;let X=G.id.trim();if(!X)continue;$.add(X)}return $}function iZ(Z,$,G,X){if(!L(Z)||!Array.isArray(Z.accounts))return{preservedRawEntries:[],preservedIds:[]};let Y=[],J=[];for(let B of $){if(G.has(B))continue;if(X.has(B))continue;let z=Z.accounts.find((j)=>L(j)&&typeof j.id==="string"&&j.id.trim()===B);if(!z)continue;Y.push(z),J.push(B)}return{preservedRawEntries:Y,preservedIds:J}}function uZ(Z){if(!Array.isArray(Z))return[];let $=[];for(let G of Z){if(!L(G))continue;if(typeof G.id!=="string")continue;$.push(G.id)}return $}function sZ(Z,$,G){let X=m8(Z);if(X===null)return[];let{preservedRawEntries:Y,preservedIds:J}=iZ(Z,X,$,G);return I8(J),Y}async function g(Z,$,G={}){let{configPath:X,statePath:Y}=$,J=await S8(X);try{let B=await S8(Y);try{let z=await t(X),j=await t(Y),H=(z.exists?x8(y8(z.value,j.value)):null)??cZ(),Q=new Set(H.accounts.map((F)=>F.id)),W=m8(z.value)??new Set,V=Z(H,{rawRosterIds:[...W],loadedRosterIds:[...Q]})??H,N=new Set(G.allowDrop??[]),O=sZ(z.value,Q,N),U=lZ(V),E=new Set((Array.isArray(U.accounts)?U.accounts:[]).map((F)=>L(F)&&typeof F.id==="string"?F.id.trim():"").filter(Boolean)),X0=O.filter((F)=>{if(!L(F))return!1;if(typeof F.id!=="string")return!1;return!E.has(F.id.trim())}),A={...L(z.value)?z.value:{},...U,accounts:[...Array.isArray(U.accounts)?U.accounts:[],...X0]};return await K0(X,A),await K0(Y,dZ(V)),E8.debug("account config written",{accountCount:A.accounts.length,accountIds:uZ(A.accounts)}),V}finally{await B.release()}}finally{await J.release()}}function cZ(){return{version:1,main:{type:"opencode",provider:"openai"},accounts:[]}}function nZ(Z){return Math.floor(Math.random()*(Z+1))}var S4=globalThis.setTimeout.bind(globalThis),E4=globalThis.clearTimeout.bind(globalThis);var w0="/backend-api/wham/rate-limit-reset-credits",rZ=`https://chatgpt.com${w0}`,g8=`${w0}/consume`,aZ=`https://chatgpt.com${g8}`,oZ=300000,tZ=60000,eZ=15000,Z3=60000;function Q0(Z){return{configPath:Z.configPath,statePath:Z.statePath}}class C extends Error{kind;cooldownUntil;constructor(Z,$,G){super($);this.name="ResetRedemptionError",this.kind=Z,this.cooldownUntil=G}}class h extends Error{kind;status;constructor(Z,$,G){super($);this.name="ResetCreditError",this.kind=Z,this.status=G}}function l8(Z,$,G){return{authorization:`Bearer ${Z}`,...G?{"chatgpt-account-id":G}:{},"oai-client-platform":"web","oai-client-version":"0","x-openai-target-path":$}}function V0(Z){return typeof Z==="object"&&Z!==null&&!Array.isArray(Z)}function d8(Z){if(!Z)return;return{...Z.inFlight?{inFlight:{...Z.inFlight}}:{},...Z.lastOutcome?{lastOutcome:{...Z.lastOutcome}}:{},...Z.cooldownUntil!==void 0?{cooldownUntil:Z.cooldownUntil}:{}}}function h0(Z){if(!V0(Z)||typeof Z.redeemRequestId!=="string"||Z.redeemRequestId.length===0||typeof Z.creditId!=="string"||Z.creditId.length===0||typeof Z.startedAt!=="number"||!Number.isFinite(Z.startedAt))return;return{redeemRequestId:Z.redeemRequestId,creditId:Z.creditId,startedAt:Z.startedAt}}function i8(Z){return Boolean(Z&&Object.hasOwn(Z,"inFlight"))}function u8(Z,$){return $-Z.startedAt<oZ}function N0(Z,$){if(!b($))return;return Z.reset?.[$]}function s8(Z){if(!Z.reset)Z.reset=Object.create(null);return Z.reset}function c8(Z,$,G){if(!b($))return;let X=s8(Z),Y=X[$]??{},J=Y.lastOutcome;delete Y.inFlight,Y.lastOutcome={code:"ambiguous_local",at:G,...J?{previousOutcome:{code:J.code,at:J.at}}:{}},X[$]=Y}function n8(Z,$){let G=d8(Z),X=h0(Z?.inFlight);if(i8(Z)&&!X)return{kind:"corrupt",beforeState:G};if(Z?.cooldownUntil!==void 0&&Z.cooldownUntil>$)return{kind:"cooldown",beforeState:G,cooldownUntil:Z.cooldownUntil};if(X&&u8(X,$))return{kind:"claim",beforeState:G,claim:{inFlight:X,selectedCredit:{id:X.creditId}}};if(X)return{kind:"expired_unreconciled",beforeState:G,claim:{inFlight:X,selectedCredit:{id:X.creditId}}};return{kind:"fresh",beforeState:G}}async function $3(Z,$){if(!b($))return{kind:"fresh",beforeState:void 0};let G=await Z.loadAccountsFn(Q0(Z));return n8(G?N0(G,$):void 0,Z.now())}async function G3(Z,$){let G;if(await Z.mutateAccountsFn((X)=>{let Y=Z.now(),J=N0(X,$);if(G=n8(J,Y),G.kind==="corrupt")c8(X,$,Y);return X},Q0(Z)),!G)throw Error("corrupt reset state mutation did not run");return G}async function X3(Z,$,G){if(!b($))return{kind:"fresh",beforeState:void 0};let X;if(await Z.mutateAccountsFn((Y)=>{let J=Z.now(),B=N0(Y,$),z=d8(B),j=h0(B?.inFlight);if(i8(B)&&!j)return c8(Y,$,J),X={kind:"corrupt",beforeState:z},Y;if(B?.cooldownUntil!==void 0&&B.cooldownUntil>J)return X={kind:"cooldown",beforeState:z,cooldownUntil:B.cooldownUntil},Y;if(j&&u8(j,J))return X={kind:"claim",beforeState:z,claim:{inFlight:j,selectedCredit:{id:j.creditId}}},Y;if(j)return X={kind:"expired_unreconciled",beforeState:z,claim:{inFlight:j,selectedCredit:{id:j.creditId}}},Y;let H=L0(G);if(!H)return X={kind:"fresh",beforeState:z},Y;let Q={redeemRequestId:Z.randomUUID(),creditId:H.id,startedAt:J},W=s8(Y);return W[$]={...B,inFlight:Q},X={kind:"claim",beforeState:z,claim:{inFlight:Q,selectedCredit:H}},Y},Q0(Z)),!X)throw Error("reset claim mutation did not run");return X}async function Y3(Z,$,G,X){if(!b($))return;if(!g0(X.kind))return;await Z.mutateAccountsFn((Y)=>{let J=N0(Y,$),B=h0(J?.inFlight);if(!J||!B||B.redeemRequestId!==G.redeemRequestId||B.creditId!==G.creditId)return Y;let z=Z.now();if(delete J.inFlight,J.lastOutcome={code:X.kind,at:z},X.kind==="reset"||X.kind==="already_redeemed")J.cooldownUntil=z+Z3;else delete J.cooldownUntil;return Y},Q0(Z))}function e(Z,$){if(!Z||Z.usedPercent<100)return!1;if(!Z.resetsAt)return!0;let G=Date.parse(Z.resetsAt);return!Number.isFinite(G)||G>$}function v0(Z,$,G){if(!($||e(Z.primary,G)||e(Z.secondary,G)))return{ok:!1,reason:"not exhausted"};return{ok:!0}}function w8(Z,$){let G=Z[$];if(G===void 0||G===null)return;return typeof G==="string"?G:null}function J3(Z){if(!V0(Z))return;let $=w8(Z,"granted_at"),G=w8(Z,"reset_type");if(typeof Z.id!=="string"||typeof Z.status!=="string"||typeof Z.expires_at!=="string"||Number.isNaN(Date.parse(Z.expires_at))||typeof Z.is_supported_by_plan!=="boolean"||$===null||G===null)return;return{id:Z.id,status:Z.status,...$===void 0?{}:{grantedAt:$},expiresAt:Z.expires_at,...G===void 0?{}:{resetType:G},isSupportedByPlan:Z.is_supported_by_plan}}async function p0(Z,$,G){let X;try{X=await Z(rZ,{method:"GET",headers:l8($,w0,G),signal:AbortSignal.timeout(eZ)})}catch{throw new h("http_error","reset credit list request failed")}if(!X.ok)throw new h("http_error",`reset credit list request failed: ${X.status}`,X.status);let Y;try{Y=await X.json()}catch{throw new h("invalid_response","reset credit list response was not valid JSON",X.status)}if(!V0(Y))throw new h("invalid_response","reset credit list response was not an object",X.status);return{credits:(Array.isArray(Y.credits)?Y.credits:[]).flatMap((B)=>{let z=J3(B);return z===void 0?[]:[z]}),availableCount:typeof Y.available_count==="number"&&Number.isFinite(Y.available_count)?Y.available_count:void 0}}function L0(Z){return[...Z].filter(r8).sort(($,G)=>Date.parse($.expiresAt)-Date.parse(G.expiresAt))[0]}function r8(Z){return Z.status==="available"&&Z.isSupportedByPlan&&Z.resetType==="codex_rate_limits"}function a8(Z){return Z.filter(r8).length}function g0(Z){return Z==="reset"||Z==="already_redeemed"||Z==="nothing_to_reset"||Z==="no_credit"}async function B3(Z,$,G,X,Y){let J;try{J=await Z(aZ,{method:"POST",headers:{...l8($,g8,G),"content-type":"application/json"},body:JSON.stringify({redeem_request_id:Y,credit_id:X}),signal:AbortSignal.timeout(tZ)})}catch(H){return{kind:"ambiguous",raw:H}}let B;try{B=await J.text()}catch(H){return J.ok?{kind:"ambiguous",raw:H}:{kind:"http_error",raw:H,status:J.status}}let z=B,j=!1;try{z=JSON.parse(B),j=!0}catch{}if(!J.ok)return{kind:"http_error",raw:z,status:J.status};if(!j||!V0(z)||!g0(z.code))return{kind:"ambiguous",raw:z};return{kind:z.code,raw:z}}function h8(Z){if(Z.kind==="cooldown")throw new C("cooldown_active","reset credit redemption is cooling down",Z.cooldownUntil);throw Error(`unexpected reset state decision: ${Z.kind}`)}function v8(){throw new C("expired_unreconciled","the previous reset attempt outcome is unknown")}function p8(Z,$){return{target:Z,selectedCredit:void 0,beforeState:$,outcome:{kind:"ambiguous_local",raw:{reason:"corrupt_in_flight"}},retrySafety:"No request was sent because the saved redemption identity was incomplete."}}function z3(Z){if(Z.kind==="reset"||Z.kind==="already_redeemed")return"The server returned a terminal result; cooldown blocks an immediate new redemption.";if(Z.kind==="nothing_to_reset"||Z.kind==="no_credit")return"The server returned a terminal no-op. No cooldown was set; a new attempt starts fresh and remains gated by fresh preconditions.";return"The outcome is uncertain. A retry within five minutes reuses the same request and credit identifiers; this does not prove the server did nothing."}async function o8(Z,$){if(!b($.accountKey))throw new C("invalid_account_key","the reset account key is reserved");let G=await Z.resolveTarget($.accountKey);if(G.chatgptAccountId!==$.expectedChatgptAccountId)throw new C("identity_mismatch","the resolved ChatGPT account identity changed before redemption");let X=G.accountKey==="main"?void 0:G.chatgptAccountId,Y=await $3(Z,$.accountKey);if(Y.kind==="corrupt"){if(Y=await G3(Z,$.accountKey),Y.kind==="corrupt")return p8(G,Y.beforeState)}if(Y.kind==="cooldown")h8(Y);let J;if(Y.kind==="claim")J=Y.claim;else if(Y.kind==="expired_unreconciled"){if(!$.retry)v8();J=Y.claim}else{if($.retry)throw new C("retry_without_inflight","there is no active reset credit redemption to retry");let[j,H]=await Promise.all([Z.fetchUsage(G),p0(Z.fetchImpl,G.accessToken,X)]),Q=v0(j,Z.hasActiveRateLimitMark($.accountKey),Z.now());if(!Q.ok)throw new C("not_exhausted",Q.reason);if(!L0(H.credits))throw new C("no_eligible_credit","no eligible reset credit was returned");let W=await X3(Z,$.accountKey,H.credits);if(W.kind==="corrupt")return p8(G,W.beforeState);if(W.kind==="cooldown")h8(W);if(W.kind==="expired_unreconciled")v8();if(W.kind!=="claim")throw new C("no_eligible_credit","no eligible reset credit was available while claiming");J=W.claim}let B=await B3(Z.fetchImpl,G.accessToken,X,J.inFlight.creditId,J.inFlight.redeemRequestId),z=!1;try{await Y3(Z,$.accountKey,J.inFlight,B)}catch{z=g0(B.kind)}return{target:G,selectedCredit:J.selectedCredit,beforeState:Y.beforeState,outcome:B,retrySafety:z?"The server returned a terminal result, but the state write failed. A retry within five minutes reuses the same request and credit identifiers.":z3(B),...z?{finalizeStateWriteFailed:!0}:{}}}function t8(Z){return typeof Z==="object"&&Z!==null&&!Array.isArray(Z)}var O0="openai-quota",U0="openai-account",q0="openai-routing";var Z0="openai-reset";var K=R("commands");function v(Z){return{configPath:Z.accountStoragePath,statePath:Z.accountStatePath}}function e8(Z){if(Z==="fallback-first")return"Try usable fallback accounts before the main account.";if(Z==="sticky-balanced")return"Keep each session on its assigned account while balancing new sessions.";return"Try the main account first. Use fallback accounts only when required."}var j3=900000;function Z1(Z,$){if(typeof Z!=="number"||!Number.isFinite(Z))return" (age unknown)";let G=$-Z;if(G<j3)return"";let X=Math.floor(G/3600000);if(X>=24)return` (${Math.floor(X/24)}d old)`;if(X>=1)return` (${X}h old)`;return` (${Math.floor(G/60000)}m old)`}async function H3(Z){let $=await Z.refreshAllQuota?.(),G=Z.quotaManager.getMain(),X=Date.now(),Y=["## OpenAI Quota",""];if(G?.quota){let z=G.quota;Y.push("### Main account");for(let j of["primary","secondary"]){let H=z[j];if(H){let Q=Math.round(H.usedPercent),W="█".repeat(Math.max(0,Math.min(Math.round(Q/10),10)))+"░".repeat(Math.max(0,10-Math.min(Math.round(Q/10),10)));Y.push(`- ${j}: ${W} ${Q}% used (${Math.round(H.remainingPercent)}% remaining)${Z1(H.checkedAt??G.checkedAt,X)}`)}}if(z.resetCreditsAvailable!==void 0)Y.push(`- resets: ${z.resetCreditsAvailable}`)}else Y.push("No main quota snapshot available. Send a request first.");let B=[...Z.quotaManager.getAllFallbacks().entries()].filter(([,z])=>z);if(B.length>0){Y.push(""),Y.push("### Fallback accounts");for(let[z,j]of B){if(!j?.quota)continue;Y.push(`**${z}**`);for(let H of["primary","secondary"]){let Q=j.quota[H];if(Q){let W=Math.round(Q.usedPercent);Y.push(` - ${H}: ${W}% used (${Math.round(Q.remainingPercent)}% remaining)${Z1(Q.checkedAt??j.checkedAt,X)}`)}}if(j.quota.resetCreditsAvailable!==void 0)Y.push(` - resets: ${j.quota.resetCreditsAvailable}`)}}if($?.length){let z=$.filter((j)=>!j.ok);if(z.length>0){Y.push("");for(let j of z)Y.push(j.permanent?`- ${j.account}: sign-in no longer accepted — remove and add this account again`:`- ${j.account}: fetch failed — Refresh to retry`)}}return{command:"openai-quota",text:Y.join(`
|
|
94
|
+
`),knobs:{}}}function l(Z){return{id:Z.id,type:Z.type??"oauth",enabled:Z.enabled,label:Z.label}}async function W3(Z,$){let G=Z.trim().split(/\s+/).filter(Boolean),X=await $.loadAccounts(v($))??{version:1,accounts:[]},Y=X.accounts??[];if(G.length===0||G.length===1&&G[0]==="list"){let J=["## OpenAI Accounts",""];if(Y.length===0)J.push("No accounts configured. Use `/login openai` to add your main account, or `/openai-account add` to add a fallback account.");else{let B=X.routing?.mode??"main-first";J.push(`Routing: \`${B}\` (set with \`/openai-routing\`). Modes: main-first, fallback-first, or sticky-balanced. \`/openai-routing reset\` clears this session's pin.`),J.push("");for(let z of Y){let j=z.type??"oauth";J.push(`- \`${z.id}\` (${j})`)}}return J.push(""),J.push("Commands: `/openai-account add [label]` | `/openai-account remove <id>`"),{command:"openai-account",text:J.join(`
|
|
95
|
+
`),knobs:{accounts:Y.map(l)}}}if(G[0]==="remove"&&G[1]){let J=G[1],B=await A8($.accountStoragePath),z=B?B.has(J):!1,j=!1,H=await g((W)=>{let V=W.accounts.findIndex((N)=>N.id===J);if(V===-1)return W;return W.accounts.splice(V,1),j=!0,W},v($),{allowDrop:[J]});if(!(j||z))return{command:"openai-account",text:`## Account Not Found
|
|
96
|
+
|
|
97
|
+
No account with id \`${J}\` exists.`,knobs:{accounts:H.accounts.map(l)}};return K.info("account removed",{id:J}),$.refreshSidebar?.().catch(()=>{}),{command:"openai-account",text:`## Account Removed
|
|
98
|
+
|
|
99
|
+
Removed account \`${J}\`.`,knobs:{accounts:H.accounts.map(l)}}}if(G[0]==="order"&&G.length>=3){let J=!1,B=await g((z)=>{let j=z.accounts.findIndex((W)=>W.id===G[1]),H=z.accounts.findIndex((W)=>W.id===G[2]);if(j===-1||H===-1)return z;J=!0;let Q=z.accounts[j];return z.accounts[j]=z.accounts[H],z.accounts[H]=Q,z},v($));if(!J)return{command:"openai-account",text:`## Invalid Order
|
|
100
|
+
|
|
101
|
+
Both account IDs must exist.`,knobs:{accounts:B.accounts.map(l)}};return K.info("accounts reordered",{a:G[1],b:G[2]}),$.refreshSidebar?.().catch(()=>{}),{command:"openai-account",text:`## Accounts Reordered
|
|
102
|
+
|
|
103
|
+
Swapped positions of \`${G[1]}\` and \`${G[2]}\`.`,knobs:{accounts:B.accounts.map(l)}}}if(G[0]==="add"){let J=G.includes("--headless"),B=G.filter((N)=>N!=="add"&&N!=="--headless"),z=B.length>0?B.join(" "):void 0,{url:j,instructions:H,completion:Q}=await($.beginAccountLogin??W8)({label:z,headless:J,version:$.packageVersion}),W=$.notify,V=$.sessionId;if(Q.then(async(N)=>{let O=!1;if(await g((U)=>{if(N.accountId&&U.mainAccountId&&N.accountId===U.mainAccountId)return O=!0,U;return H8(U.accounts,N),U},v($)),O){K.warn("account add rejected (main identity)",{id:N.id,sessionId:V}),W?.({command:"openai-account",text:`## Add Failed
|
|
104
|
+
|
|
105
|
+
${"That account is already your main account — not added as a fallback."}`,knobs:{}});return}K.info("account added",{id:N.id,label:N.label}),$.refreshSidebar?.().catch(()=>{}),W?.({command:"openai-account",text:`## Account Added
|
|
106
|
+
|
|
107
|
+
Added account \`${N.id}\`${N.label?` ("${N.label}")`:""}.
|
|
108
|
+
|
|
109
|
+
Run \`/openai-account\` to confirm.`,knobs:{}})}).catch((N)=>{let O=N instanceof Error?N.message:String(N??"unknown error");K.warn("account add failed",{error:O,sessionId:V}),W?.({command:"openai-account",text:`## Add Failed
|
|
110
|
+
|
|
111
|
+
Account add failed: ${O}`,knobs:{}})}),J){let N=H.match(/Enter code: (.+)/)?.[1]??H;return{command:"openai-account",text:`## Device Code
|
|
112
|
+
|
|
113
|
+
1. Open this verification URL:
|
|
114
|
+
|
|
115
|
+
${j}
|
|
116
|
+
|
|
117
|
+
2. Enter the code: **${N}**
|
|
118
|
+
|
|
119
|
+
${H}
|
|
120
|
+
|
|
121
|
+
The account will be added automatically — run \`/openai-account\` to confirm.`,knobs:{verificationUrl:j,userCode:N,instructions:H}}}return{command:"openai-account",text:`## Add OpenAI Account
|
|
122
|
+
|
|
123
|
+
Open this URL and complete sign-in:
|
|
124
|
+
|
|
125
|
+
${j}
|
|
126
|
+
|
|
127
|
+
${H}
|
|
128
|
+
|
|
129
|
+
The account will be added automatically — run \`/openai-account\` to confirm.`,knobs:{url:j,instructions:H}}}return{command:"openai-account",text:"## Account Commands\n\n- `/openai-account` — show accounts\n- `/openai-account add [label]` — add a new account\n- `/openai-account remove <id>` — remove\n- `/openai-account order <a> <b>` — swap fallback positions\n\nRouting modes are `main-first`, `fallback-first`, and `sticky-balanced`. `/openai-routing reset` clears the current session pin.",knobs:{accounts:Y.map(l)}}}async function Q3(Z,$){let G=Z.trim().toLowerCase().split(/\s+/).filter(Boolean),Y=(await $.loadAccounts(v($))??{version:1,accounts:[]}).routing?.mode??"main-first";if(G.length===1&&G[0]==="reset"){if(!$.sessionId)return{command:"openai-routing",text:`## OpenAI Routing Reset
|
|
130
|
+
|
|
131
|
+
No current session is available, so no pin was changed.`,knobs:{mode:Y}};if(!$.clearStickyRouting)return{command:"openai-routing",text:`## OpenAI Routing Reset
|
|
132
|
+
|
|
133
|
+
This runtime cannot clear the current session pin.`,knobs:{mode:Y}};return await $.clearStickyRouting($.sessionId),K.info("routing session pin cleared"),{command:"openai-routing",text:`## OpenAI Routing Reset
|
|
134
|
+
|
|
135
|
+
This session's pin was cleared. The next request may choose the same account if it remains the best selection.`,knobs:{mode:Y}}}if(G.length===1&&(G[0]==="main-first"||G[0]==="fallback-first"||G[0]==="sticky-balanced")){let z=G[0];return await g((j)=>{return j.routing={...j.routing??{},mode:z},j},v($)),K.info("routing mode changed",{mode:z}),{command:"openai-routing",text:`## OpenAI Routing Updated
|
|
136
|
+
|
|
137
|
+
Mode: \`${z}\`
|
|
138
|
+
- ${e8(z)}
|
|
139
|
+
|
|
140
|
+
Usage: \`/openai-routing\`, \`/openai-routing main-first\`, \`/openai-routing fallback-first\`, or \`/openai-routing sticky-balanced\`.`,knobs:{mode:z}}}let J=Y==="sticky-balanced"&&$.sessionId?await $.getStickyRouting?.($.sessionId):void 0,B=Y==="sticky-balanced"?J?`
|
|
141
|
+
- Session pin: \`${J}\`. Use \`/openai-routing reset\` to clear it.`:`
|
|
142
|
+
- Session pin: none yet. A request will choose one when a usable account is available.`:"";return{command:"openai-routing",text:`## OpenAI Routing
|
|
143
|
+
|
|
144
|
+
- Mode: \`${Y}\`
|
|
145
|
+
- ${e8(Y)}${B}
|
|
146
|
+
|
|
147
|
+
Usage: \`/openai-routing\`, \`/openai-routing main-first\`, \`/openai-routing fallback-first\`, or \`/openai-routing sticky-balanced\`.`,knobs:{mode:Y}}}function X1(Z){let $=[Z.primary?.usedPercent,Z.secondary?.usedPercent].filter((G)=>G!==void 0);return $.length>0?Math.max(...$):void 0}function V3(Z,$){let G=[Z.primary,Z.secondary].filter((Y)=>Y!==void 0),X=G.filter((Y)=>e(Y,$));return(X.length>0?X:G).sort((Y,J)=>J.usedPercent-Y.usedPercent)[0]?.resetsAt}function N3(Z,$){if(!Z)return!1;let G=[Z.primary,Z.secondary].filter((X)=>X!==void 0);if(G.length===0)return!1;return G.every((X)=>!e(X,$))}function l0(Z){if(!Z)return;try{return decodeURIComponent(Z)}catch{return}}async function $1(Z,$){try{let G=await $.resolveResetTarget(Z),X=G.accountKey==="main"?void 0:G.chatgptAccountId,[Y,J]=await Promise.all([A0({accessToken:G.accessToken,fetchImpl:$.fetchImpl,now:$.now,accountId:G.chatgptAccountId}),p0($.fetchImpl,G.accessToken,X)]),B=L0(J.credits),z=a8(J.credits),j=J.availableCount??Y.resetCreditsAvailable??(z>0?z:void 0),H=Y.resetCreditsApplicable,Q=v0(Y,$.quotaManager.isRateLimited(Z),$.now()),W;if(!G.chatgptAccountId)W="stable ChatGPT account identity unavailable";else if(!Q.ok)W=Q.reason;else if(!B)W="no eligible credit";return{accountKey:G.accountKey,label:G.label,chatgptAccountId:G.chatgptAccountId,usedPercent:X1(Y),resetTime:V3(Y,$.now()),availableCount:j,applicableAvailableCount:H,eligible:W===void 0,reason:W,selectedCreditId:B?.id,selectedCreditExpiresAt:B?.expiresAt}}catch(G){return K.warn("reset preview row failed",{accountKey:Z,error:G?.message??String(G)}),{accountKey:Z,label:Z==="main"?"Main account":Z,eligible:!1,reason:G?.message??String(G)}}}function L3(Z){let $=["## Reset credits","","Select an account to fetch a fresh confirmation preview:",""];for(let G of Z){let X=G.usedPercent===void 0?"quota unavailable":`${G.usedPercent}% used`,Y=G.availableCount===void 0?"credits unavailable":`${G.applicableAvailableCount===void 0?"?":G.applicableAvailableCount}/${G.availableCount} applicable/available`,J=G.eligible?`eligible · credit ${G.selectedCreditId} expires ${G.selectedCreditExpiresAt}`:G.reason;$.push(`- **${G.label}** (\`${G.accountKey}\`) — ${X}; ${Y}; ${J}`)}return $.push(""),$.push("Command: `/openai-reset select <encodedAccountKey>`"),$.join(`
|
|
148
|
+
`)}function G1(Z){let $=["## Confirm reset credit","",`Account: **${Z.label}** (\`${Z.accountKey}\`)`,`Current quota: **${Z.usedPercent??"unknown"}% used**`,`Credit: **Spend 1 of ${Z.availableCount??"unknown"}**`,`Credit expires: **${Z.selectedCreditExpiresAt??"unavailable"}**`,`Quota resets: **${Z.resetTime??"unavailable"}**`,""];if((Z.availableCount??0)>0&&Z.applicableAvailableCount===0)$.push("The server does not currently count this credit as applicable; redemption may return a no-op, and a no-op does not spend the credit."),$.push("");if(Z.eligible&&Z.chatgptAccountId)$.push(`Confirm: \`/openai-reset confirm ${encodeURIComponent(Z.accountKey)} ${encodeURIComponent(Z.chatgptAccountId)}\``);else $.push(`Cannot reset: **${Z.reason??"not eligible"}**`);return $.join(`
|
|
149
|
+
`)}function M(Z,$,G,X={}){return{command:Z0,text:G,knobs:{stage:"result",accountKey:Z,code:$,...X}}}function D3(Z,$,G){if($ instanceof C){let Y={identity_mismatch:"The account identity changed before redemption. Reopen the reset account list.",invalid_account_key:"The selected account key is reserved. Reopen the reset account list.",cooldown_active:"This account just reset — re-checking quota. Wait for the cooldown before another redemption.",expired_unreconciled:"The previous attempt outcome is unknown — retry replays the same identifiers, or wait until quota reflects the earlier attempt.",retry_without_inflight:"There is no active reset redemption to retry. Reopen the account list.",not_exhausted:"No credit was spent: the fresh account state is not exhausted.",no_eligible_credit:"No credit was spent: no eligible credit was returned."};return M(Z,$.kind,`## Reset credit
|
|
150
|
+
|
|
151
|
+
${Y[$.kind]}
|
|
152
|
+
|
|
153
|
+
Code: \`${$.kind}\``,{cooldownUntil:$.cooldownUntil,...$.kind==="expired_unreconciled"?{chatgptAccountId:G,retryGuidance:"Retry replays the same request and credit identifiers from the previous attempt."}:{}})}let X=$?.code;if(X==="unknown_account"||X==="disabled_account"||X==="non_oauth_account"||X==="token_unavailable")return M(Z,X,`## Reset credit
|
|
154
|
+
|
|
155
|
+
${{unknown_account:"Account unavailable: the selected account no longer exists. Reopen the reset account list.",disabled_account:"Account unavailable: the selected account is disabled. Reopen the reset account list.",non_oauth_account:"Account unavailable: the selected account is not authenticated with OAuth. Reopen the reset account list.",token_unavailable:"Authentication problem: the selected account token is unavailable. Reauthenticate the account before retrying."}[X]}
|
|
156
|
+
|
|
157
|
+
Code: \`${X}\``);if($ instanceof h)return M(Z,$.kind,`## Reset credit
|
|
158
|
+
|
|
159
|
+
No redemption was attempted: reset credit availability could not be loaded.
|
|
160
|
+
|
|
161
|
+
Code: \`${$.kind}\``);return K.warn("reset command failed before a known result",{accountKey:Z,error:$?.message??String($)}),M(Z,"error",`## Reset credit
|
|
162
|
+
|
|
163
|
+
The reset request failed before a known result: internal command failure — see plugin log.`)}async function Y1(Z,$,G){let{accountKey:X}=Z.target,Y=Z.outcome.kind;if(Z.finalizeStateWriteFailed)return M(X,Y,`## Reset credit result
|
|
164
|
+
|
|
165
|
+
Account: **${Z.target.label}** (\`${X}\`)
|
|
166
|
+
|
|
167
|
+
The server outcome recorded as \`${Y}\`, but the state write failed. A retry within five minutes reuses the same request and credit identifiers; this does not prove the server did nothing.`,{stateWriteFailed:!0,retryGuidance:Z.retrySafety,chatgptAccountId:G});if(Y==="reset"||Y==="already_redeemed"){let B={account:X,ok:!1,error:"targeted quota refresh did not complete"};try{B=await $.refreshResetTargetQuota(X)}catch(N){B.error=N?.message??String(N)}let z=!B.ok&&B.error!==void 0;if(z)K.warn("reset quota re-check failed",{accountKey:X,error:B.error});let j=X==="main"?$.quotaManager.getMain():$.quotaManager.getFallback(X),H=B.ok&&N3(j?.quota,$.now()),Q=j?.quota.resetCreditsApplicable,W=H?"Post-verification: **window fresh**.":`Post-verification: **window not yet refreshed** (server code \`${Y}\`).`,V=z?`
|
|
168
|
+
|
|
169
|
+
quota re-check failed — see log.`:"";return M(X,Y,`## Reset credit result
|
|
170
|
+
|
|
171
|
+
Account: **${Z.target.label}** (\`${X}\`)
|
|
172
|
+
|
|
173
|
+
Code: \`${Y}\`
|
|
174
|
+
|
|
175
|
+
${W}${V}${Q===void 0?"":`
|
|
176
|
+
|
|
177
|
+
Remaining applicable credits: **${Q}**`}`,{verifiedFresh:H,afterUsedPercent:X1(j?.quota??{}),remainingCredits:Q})}if(Y==="ambiguous_local")return M(X,Y,`## Reset credit result
|
|
178
|
+
|
|
179
|
+
Account: **${Z.target.label}** (\`${X}\`)
|
|
180
|
+
|
|
181
|
+
Code: \`${Y}\`
|
|
182
|
+
|
|
183
|
+
${Z.retrySafety}`,{retryGuidance:Z.retrySafety});if(Y==="ambiguous"||Y==="http_error"){let B=G?`/openai-reset retry ${encodeURIComponent(X)} ${encodeURIComponent(G)}`:"/openai-reset";return M(X,Y,`## Reset credit result
|
|
184
|
+
|
|
185
|
+
Account: **${Z.target.label}** (\`${X}\`)
|
|
186
|
+
|
|
187
|
+
The redemption outcome is unknown (\`${Y}\`).
|
|
188
|
+
|
|
189
|
+
Retry with \`${B}\`. A retry within five minutes reuses the same request and credit identifiers; this does not prove the server did nothing.`,{retryGuidance:Z.retrySafety,chatgptAccountId:G})}let J={nothing_to_reset:"The server found no exhausted quota window to reset. No reset was confirmed. A new attempt starts fresh and must pass the current preconditions.",no_credit:"The server found no usable reset credit. No reset was confirmed. A new attempt starts fresh and must pass the current preconditions."};return M(X,Y,`## Reset credit result
|
|
190
|
+
|
|
191
|
+
Account: **${Z.target.label}** (\`${X}\`)
|
|
192
|
+
|
|
193
|
+
Code: \`${Y}\`
|
|
194
|
+
|
|
195
|
+
${J[Y]??"The server returned a no-op result. No reset was confirmed."}`)}async function O3(Z,$){let G=[$.resolveResetTarget?void 0:"resolveResetTarget",$.fetchImpl?void 0:"fetchImpl",$.now?void 0:"now",$.randomUUID?void 0:"randomUUID",$.refreshResetTargetQuota?void 0:"refreshResetTargetQuota"].filter((B)=>B!==void 0);if(G.length>0)return K.warn("reset command dependencies unwired",{missingDeps:G}),{command:Z0,text:`## Reset credit
|
|
196
|
+
|
|
197
|
+
Unavailable: reset command runtime dependencies are not wired.`,knobs:{}};let X=$,Y=Z.trim().split(/\s+/).filter(Boolean),J=Y[0];if(!J||J==="refresh"){let z=["main",...((await $.loadAccounts(v($)))?.accounts??[]).filter((H)=>H.enabled!==!1&&H.type==="oauth").map((H)=>H.id)];K.debug("reset accounts stage requested",{accountKeys:z});let j=await Promise.all(z.map((H)=>$1(H,X)));return K.debug("reset accounts stage built",{rows:j.map((H)=>({accountKey:H.accountKey,eligible:H.eligible,reason:H.reason,usedPercent:H.usedPercent,availableCount:H.availableCount,applicableAvailableCount:H.applicableAvailableCount}))}),{command:Z0,text:L3(j),knobs:{stage:"accounts",accounts:j}}}if(J==="select"){let B=l0(Y[1]);if(!B||!b(B)||Y.length!==2)return M("","invalid_command","Usage: `/openai-reset select <encodedAccountKey>`");let z=await $1(B,X);if(!z.eligible)return M(B,"not_eligible",G1(z));return{command:Z0,text:G1(z),knobs:{stage:"confirm",preview:z}}}if(J==="confirm"||J==="retry"){let B=l0(Y[1]),z=l0(Y[2]);if(!B||!b(B)||!z||Y.length!==3)return M(B??"","invalid_command",`Usage: \`/openai-reset ${J} <encodedAccountKey> <encodedChatgptAccountId>\``);K.info("reset redemption decision",{accountKey:B,action:J}),K.debug("reset redemption identity binding",{accountKey:B,expectedChatgptAccountId:z});try{let j=await o8({configPath:$.accountStoragePath,statePath:$.accountStatePath,mutateAccountsFn:g,loadAccountsFn:$.loadAccounts,now:X.now,randomUUID:X.randomUUID,fetchImpl:X.fetchImpl,resolveTarget:X.resolveResetTarget,fetchUsage:(H)=>A0({accessToken:H.accessToken,fetchImpl:X.fetchImpl,now:X.now,accountId:H.chatgptAccountId}),hasActiveRateLimitMark:(H)=>$.quotaManager.isRateLimited(H)},{accountKey:B,expectedChatgptAccountId:z,retry:J==="retry"});return K.info("reset redemption outcome",{accountKey:B,code:j.outcome.kind}),Y1(j,X,z)}catch(j){let H=D3(B,j,z);return K.info("reset redemption outcome",{accountKey:B,code:H.knobs.code}),H}}return M("","invalid_command","Usage: `/openai-reset` | `/openai-reset select <encodedAccountKey>` | `/openai-reset confirm <encodedAccountKey> <encodedChatgptAccountId>` | `/openai-reset retry <encodedAccountKey> <encodedChatgptAccountId>` | `/openai-reset refresh`")}var U3=new Set(["access","refresh","apikey","authheader","password","secret"]);function q3(Z){let $=Z.toLowerCase().replace(/[-_]/g,"");if(U3.has($))return!0;return $.endsWith("token")}function D0(Z,$,G){if(Array.isArray(Z))return Z.map((Y,J)=>D0(Y,`${$}[${J}]`,G));if(!t8(Z))return Z;let X={};for(let[Y,J]of Object.entries(Z)){if(q3(Y)){G.push(`${$}.${Y}`);continue}X[Y]=D0(J,`${$}.${Y}`,G)}return X}async function d0(Z,$,G,X={}){let Y=await K3(Z,$,G,X),J=[],B=D0(Y.knobs,"knobs",J);if(J.length>0)return K.warn("credential-shaped knob stripped before RPC",{command:Z,fields:J}),{...Y,knobs:B};return Y}async function K3(Z,$,G,X){switch(Z){case"openai-quota":return H3(G);case"openai-account":return W3($,G);case"openai-routing":return Q3($,G);case"openai-reset":return O3($,G);default:{let Y=X[Z];if(!Y)throw Error(`unhandled command: ${Z}`);return Y($,G)}}}x0();var o4=R("quota");var J1={name:"@cortexkit/pi-openai-auth",version:"0.8.0",license:"MIT",type:"module",repository:{type:"git",url:"git+https://github.com/cortexkit/openai-auth.git",directory:"packages/pi"},main:"./dist/index.js",types:"./dist/index.d.ts",exports:{".":{import:"./dist/index.js",types:"./dist/index.d.ts"}},files:["dist","README.md","LICENSE"],keywords:["pi-package"],pi:{extensions:["./dist/index.js"]},scripts:{build:"rm -rf dist && bun build src/index.ts --outdir dist --target node --format esm --external '@earendil-works/pi-ai' --external '@earendil-works/pi-ai/*' --external '@earendil-works/pi-coding-agent' --external '@earendil-works/pi-coding-agent/*' --external '@earendil-works/pi-tui' --external '@earendil-works/pi-tui/*' --minify && bun run build:types","build:types":"cd ../core && bun run build && cd ../pi && tsdown --config tsdown.config.ts",test:"bun test src/tests",typecheck:"tsc",types:"tsc",prepublishOnly:"bun run build"},devDependencies:{"@cortexkit/openai-auth-core":"workspace:*"},peerDependencies:{"@earendil-works/pi-ai":">=0.80.10","@earendil-works/pi-coding-agent":">=0.80.10","@earendil-works/pi-tui":">=0.80.10"}};import{homedir as M3}from"node:os";import{join as B1}from"node:path";function F3(){return process.env.PI_AGENT_DIR?.trim()||B1(M3(),".pi","agent")}function z1(){return process.env.PI_OPENAI_AUTH_FILE?.trim()||B1(F3(),j0)}function P3(Z=z1()){return process.env.PI_OPENAI_AUTH_STATE_FILE?.trim()||E0(Z)}function j1(Z=z1()){return{configPath:Z,statePath:P3(Z)}}var H1=new Map;function W1(Z){return H1.get(Z)}function Q1(Z){return H1.delete(Z)}var b3={auth:{set:async()=>{}}};async function C3(Z,$){let G=($.accountPaths??j1)(),X=$.loadAccounts??f8,Y=await X(G),J=new S({storage:null,configPath:G.configPath,fetchImpl:$.fetchImpl,now:$.now});return J.seedFallbacksFromAccounts((Y?.accounts??[]).filter(R8)),{accountStoragePath:G.configPath,accountStatePath:G.statePath,packageVersion:$.packageVersion??J1.version,quotaManager:J,loadAccounts:X,client:b3,sessionId:Z.sessionManager.getSessionId(),notify:(B)=>Z.ui.notify(B.text),clearStickyRouting:async(B)=>Q1(B),getStickyRouting:async(B)=>W1(B),fetchImpl:$.fetchImpl,now:$.now,randomUUID:$.randomUUID,beginAccountLogin:$.beginAccountLogin}}async function T3(Z,$,G,X={}){return d0(Z,$,await C3(G,X))}async function i0(Z,$,G,X){let Y=await T3(Z,$,G,X);G.ui.notify(Y.text)}function V1(Z,$={}){Z.registerCommand(U0,{description:"List, add, remove, or reorder OpenAI fallback accounts",handler:(G,X)=>i0(U0,G,X,$)}),Z.registerCommand(O0,{description:"Show persisted OpenAI quota for fallback accounts",handler:(G,X)=>i0(O0,G,X,$)}),Z.registerCommand(q0,{description:"Show or change OpenAI account routing mode",handler:(G,X)=>i0(q0,G,X,$)})}import{connect as S3}from"node:net";import{connect as E3}from"node:tls";var R3="258EAFA5-E914-47DA-95CA-C5AB0DC85B11",N1=["chatgpt-account-id","authorization","user-agent","originator","openai-beta","version","x-codex-beta-features","x-codex-turn-metadata","x-client-request-id","session-id","thread-id","x-codex-window-id"];class u0{url;readyState=0;socket;listeners={open:new Set,message:new Set,error:new Set,close:new Set};rxBuffer=new Uint8Array(0);handshakeDone=!1;expectedAccept="";fragOpcode=0;fragChunks=[];constructor(Z,$){this.url=Z,this.connect(Z,$)}addEventListener(Z,$,G){this.listeners[Z]?.add($)}removeEventListener(Z,$){this.listeners[Z]?.delete($)}emit(Z,$){for(let G of[...this.listeners[Z]??[]])try{G($)}catch{}}async connect(Z,$){let G=new URL(Z.replace(/^ws:/,"http:").replace(/^wss:/,"https:")),X=G.hostname,Y=G.port?Number(G.port):G.protocol==="https:"?443:80,J=`${G.pathname}${G.search}`,B=crypto.getRandomValues(new Uint8Array(16)),z=Buffer.from(B).toString("base64"),j=await crypto.subtle.digest("SHA-1",new TextEncoder().encode(z+R3));this.expectedAccept=Buffer.from(new Uint8Array(j)).toString("base64");let H={};for(let[V,N]of Object.entries($))H[V.toLowerCase()]=N;let Q=[`GET ${J} HTTP/1.1`,`Host: ${X}`,"Connection: Upgrade","Upgrade: websocket","Sec-WebSocket-Version: 13",`Sec-WebSocket-Key: ${z}`];for(let V of N1)if(H[V]!==void 0)Q.push(`${I3(V)}: ${H[V]}`);for(let[V,N]of Object.entries($))if(!N1.includes(V.toLowerCase())&&V.toLowerCase()!=="content-length")Q.push(`${V}: ${N}`);let W=`${Q.join(`\r
|
|
198
|
+
`)}\r
|
|
199
|
+
\r
|
|
200
|
+
`;try{this.log("connect_start",{host:X,port:Y,tls:G.protocol==="https:"});let V=()=>{this.socket?.write(W)},N=G.protocol==="https:"?E3({host:X,port:Y,servername:X},V):S3({host:X,port:Y},V);this.socket=N,N.on("data",(O)=>{this.onData(typeof O==="string"?Buffer.from(O,"binary"):O)}),N.on("error",(O)=>{this.readyState=3;let U=O instanceof Error?O.message:String(O);this.log("socket_error",{message:U}),this.emit("error",{message:U})}),N.on("close",(O)=>{this.readyState=3,this.log("socket_close",{hadError:O}),this.emit("close",{code:1006,reason:"socket closed"})})}catch(V){this.readyState=3,this.log("connect_exception",{message:V instanceof Error?V.message:String(V)}),this.emit("error",{message:V instanceof Error?V.message:String(V)})}}onData(Z){if(this.rxBuffer=x3(this.rxBuffer,Z),!this.handshakeDone){let $=Buffer.from(this.rxBuffer).toString("latin1"),G=$.indexOf(`\r
|
|
201
|
+
\r
|
|
202
|
+
`);if(G===-1)return;let X=$.slice(0,G),Y=X.split(`\r
|
|
203
|
+
`)[0]??"",J=X.match(/sec-websocket-accept:\s*(\S+)/i);if(!Y.includes(" 101")){this.readyState=3,this.socket?.destroy?.(),this.log("upgrade_failed",{statusLine:Y}),this.emit("error",{message:`WS upgrade failed: ${Y}`});return}if(!J||J[1]!==this.expectedAccept){this.readyState=3,this.socket?.destroy?.(),this.log("accept_mismatch",{statusLine:Y}),this.emit("error",{message:"WS upgrade Sec-WebSocket-Accept mismatch"});return}this.handshakeDone=!0,this.readyState=1,this.rxBuffer=this.rxBuffer.slice(G+4),this.log("open",{statusLine:Y}),this.emit("open",{})}this.drainFrames()}drainFrames(){for(;;){let Z=this.rxBuffer;if(Z.length<2)return;let $=Z[0],G=Z[1],X=($&128)!==0,Y=$&15,J=(G&128)!==0,B=G&127,z=2;if(B===126){if(Z.length<4)return;B=Z[2]<<8|Z[3],z=4}else if(B===127){if(Z.length<10)return;B=0;for(let H=0;H<8;H++)B=B*256+Z[2+H];z=10}if(J)z+=4;if(Z.length<z+B)return;let j=Z.slice(z,z+B);this.rxBuffer=Z.slice(z+B),this.handleFrame(X,Y,j)}}handleFrame(Z,$,G){if($===9){this.writeFrame(10,G);return}if($===10)return;if($===8){let Y=1005,J="";if(G.length>=2)Y=G[0]<<8|G[1],J=Buffer.from(G.slice(2)).toString("utf-8");this.readyState=3;try{this.socket?.end()}catch{}this.emit("close",{code:Y,reason:J});return}if($===1||$===2)this.fragOpcode=$,this.fragChunks=[G];else if($===0)this.fragChunks.push(G);if(!Z)return;let X=A3(this.fragChunks);if(this.fragChunks=[],this.fragOpcode===1){let Y=Buffer.from(X).toString("utf-8");this.emit("message",{data:Y})}}send(Z){if(this.readyState!==1)return;this.writeFrame(1,new TextEncoder().encode(Z))}writeFrame(Z,$){let G=$.length,X=crypto.getRandomValues(new Uint8Array(4)),Y;if(G<126)Y=[128|Z,128|G];else if(G<65536)Y=[128|Z,254,G>>8&255,G&255];else{Y=[128|Z,255];for(let z=7;z>=0;z--)Y.push(G/2**(8*z)&255)}let J=new Uint8Array(G);for(let z=0;z<G;z++)J[z]=$[z]^X[z%4];let B=new Uint8Array(Y.length+4+G);B.set(Y,0),B.set(X,Y.length),B.set(J,Y.length+4);try{this.socket?.write(B)}catch{}}close(){if(this.readyState===3)return;this.readyState=2;try{this.writeFrame(8,new Uint8Array([3,232])),this.socket?.end()}catch{}this.readyState=3}log(Z,$){return Promise.resolve()}}function I3(Z){return Z}function x3(Z,$){let G=new Uint8Array(Z.length+$.length);return G.set(Z,0),G.set($,Z.length),G}function A3(Z){let $=0;for(let Y of Z)$+=Y.length;let G=new Uint8Array($),X=0;for(let Y of Z)G.set(Y,X),X+=Y.length;return G}var G0="https://chatgpt.com/backend-api";class c0 extends u0{constructor(Z,$){let G=$&&typeof $==="object"&&!Array.isArray($)?$.headers??{}:{};super(String(Z),G)}}var $0=0,s0;function k3(){let Z=globalThis;if($0===0)s0=Z.WebSocket,Z.WebSocket=c0;return $0++,()=>{if($0=Math.max(0,$0-1),$0===0){if(Z.WebSocket===c0)Z.WebSocket=s0;s0=void 0}}}var m3=[{id:"gpt-5.5",name:"GPT-5.5",api:"openai-codex-responses",provider:"openai-codex",baseUrl:G0,reasoning:!0,thinkingLevelMap:{xhigh:"xhigh",minimal:"low"},input:["text","image"],cost:{input:5,output:30,cacheRead:0.5,cacheWrite:0},contextWindow:272000,maxTokens:128000},{id:"gpt-5.4",name:"GPT-5.4",api:"openai-codex-responses",provider:"openai-codex",baseUrl:G0,reasoning:!0,thinkingLevelMap:{xhigh:"xhigh",minimal:"low"},input:["text","image"],cost:{input:2.5,output:15,cacheRead:0.25,cacheWrite:0},contextWindow:272000,maxTokens:128000},{id:"gpt-5.4-mini",name:"GPT-5.4 mini",api:"openai-codex-responses",provider:"openai-codex",baseUrl:G0,reasoning:!0,thinkingLevelMap:{xhigh:"xhigh",minimal:"low"},input:["text","image"],cost:{input:0.75,output:4.5,cacheRead:0.075,cacheWrite:0},contextWindow:272000,maxTokens:128000},{id:"gpt-5.3-codex-spark",name:"GPT-5.3 Codex Spark",api:"openai-codex-responses",provider:"openai-codex",baseUrl:G0,reasoning:!0,thinkingLevelMap:{xhigh:"xhigh",minimal:"low"},input:["text"],cost:{input:1.75,output:14,cacheRead:0.175,cacheWrite:0},contextWindow:128000,maxTokens:128000}];function w3(Z,$,G){let X=y3(),Y=k3(),J;try{J=f3(Z,$,G)}catch(B){throw Y(),B}return(async()=>{try{for await(let B of J)X.push(B);X.end()}catch(B){X.push({type:"error",reason:"error",error:{role:"assistant",content:[],api:Z.api,provider:Z.provider,model:Z.id,usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:"error",errorMessage:B instanceof Error?B.message:String(B),timestamp:Date.now()}}),X.end()}finally{Y()}})(),X}function h3(Z){V1(Z),Z.registerProvider("openai-codex",{name:"OpenAI Codex (CortexKit OAuth)",baseUrl:G0,api:"openai-codex-responses",models:m3,streamSimple:w3})}export{k3 as installRawCodexWebSocket,h3 as default};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/pi-openai-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -30,12 +30,16 @@
|
|
|
30
30
|
]
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
|
-
"build": "rm -rf dist &&
|
|
33
|
+
"build": "rm -rf dist && bun build src/index.ts --outdir dist --target node --format esm --external '@earendil-works/pi-ai' --external '@earendil-works/pi-ai/*' --external '@earendil-works/pi-coding-agent' --external '@earendil-works/pi-coding-agent/*' --external '@earendil-works/pi-tui' --external '@earendil-works/pi-tui/*' --minify && bun run build:types",
|
|
34
|
+
"build:types": "cd ../core && bun run build && cd ../pi && tsdown --config tsdown.config.ts",
|
|
34
35
|
"test": "bun test src/tests",
|
|
35
36
|
"typecheck": "tsc",
|
|
36
37
|
"types": "tsc",
|
|
37
38
|
"prepublishOnly": "bun run build"
|
|
38
39
|
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@cortexkit/openai-auth-core": "workspace:*"
|
|
42
|
+
},
|
|
39
43
|
"peerDependencies": {
|
|
40
44
|
"@earendil-works/pi-ai": ">=0.80.10",
|
|
41
45
|
"@earendil-works/pi-coding-agent": ">=0.80.10",
|
package/dist/raw-ws-node.d.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
type Listener = (event: unknown) => void;
|
|
2
|
-
export declare class RawWebSocket {
|
|
3
|
-
url: string;
|
|
4
|
-
readyState: number;
|
|
5
|
-
private socket;
|
|
6
|
-
private listeners;
|
|
7
|
-
private rxBuffer;
|
|
8
|
-
private handshakeDone;
|
|
9
|
-
private expectedAccept;
|
|
10
|
-
private fragOpcode;
|
|
11
|
-
private fragChunks;
|
|
12
|
-
constructor(url: string, headers: Record<string, string>);
|
|
13
|
-
addEventListener(type: string, fn: Listener, _opts?: {
|
|
14
|
-
once?: boolean;
|
|
15
|
-
}): void;
|
|
16
|
-
removeEventListener(type: string, fn: Listener): void;
|
|
17
|
-
private emit;
|
|
18
|
-
private connect;
|
|
19
|
-
private onData;
|
|
20
|
-
private drainFrames;
|
|
21
|
-
private handleFrame;
|
|
22
|
-
send(data: string): void;
|
|
23
|
-
private writeFrame;
|
|
24
|
-
close(): void;
|
|
25
|
-
private log;
|
|
26
|
-
}
|
|
27
|
-
export {};
|
package/dist/raw-ws-node.js
DELETED
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
// Hand-rolled WebSocket client over raw TCP/TLS sockets.
|
|
2
|
-
//
|
|
3
|
-
// Purpose: native WebSocket implementations fix the HTTP upgrade header ORDER and
|
|
4
|
-
// own the RFC 6455 frame codec, so they cannot speak the wire the way Codex's
|
|
5
|
-
// Rust tokio-tungstenite client does. This client writes the upgrade bytes and
|
|
6
|
-
// frames by hand, giving full control of header order + framing, which surfaces
|
|
7
|
-
// Codex-style incremental streaming that native WebSocket implementations can
|
|
8
|
-
// suppress.
|
|
9
|
-
//
|
|
10
|
-
// Used by the Pi package for Codex only. permessage-deflate is intentionally NOT
|
|
11
|
-
// negotiated; plain text frames keep the codec simple.
|
|
12
|
-
//
|
|
13
|
-
// Implements only the subset of the WebSocket interface the plugin consumes:
|
|
14
|
-
// addEventListener/removeEventListener for "open"|"message"|"error"|"close",
|
|
15
|
-
// send(string), close(), and a `url` field. Text frames only on receive.
|
|
16
|
-
import { connect as netConnect } from 'node:net';
|
|
17
|
-
import { connect as tlsConnect } from 'node:tls';
|
|
18
|
-
const WS_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
|
19
|
-
// Codex (tokio-tungstenite) application-header order. WS control headers
|
|
20
|
-
// (host/connection/upgrade/sec-websocket-version/sec-websocket-key) are written
|
|
21
|
-
// first by us in Codex's exact order; Sec-WebSocket-Extensions would go last but
|
|
22
|
-
// we omit it (no permessage-deflate).
|
|
23
|
-
const CODEX_APP_HEADER_ORDER = [
|
|
24
|
-
'chatgpt-account-id',
|
|
25
|
-
'authorization',
|
|
26
|
-
'user-agent',
|
|
27
|
-
'originator',
|
|
28
|
-
'openai-beta',
|
|
29
|
-
'version',
|
|
30
|
-
'x-codex-beta-features',
|
|
31
|
-
'x-codex-turn-metadata',
|
|
32
|
-
'x-client-request-id',
|
|
33
|
-
'session-id',
|
|
34
|
-
'thread-id',
|
|
35
|
-
'x-codex-window-id',
|
|
36
|
-
];
|
|
37
|
-
export class RawWebSocket {
|
|
38
|
-
url;
|
|
39
|
-
readyState = 0; // CONNECTING
|
|
40
|
-
socket;
|
|
41
|
-
listeners = {
|
|
42
|
-
open: new Set(),
|
|
43
|
-
message: new Set(),
|
|
44
|
-
error: new Set(),
|
|
45
|
-
close: new Set(),
|
|
46
|
-
};
|
|
47
|
-
rxBuffer = new Uint8Array(0);
|
|
48
|
-
handshakeDone = false;
|
|
49
|
-
expectedAccept = '';
|
|
50
|
-
// message reassembly across fragmented frames
|
|
51
|
-
fragOpcode = 0;
|
|
52
|
-
fragChunks = [];
|
|
53
|
-
constructor(url, headers) {
|
|
54
|
-
this.url = url;
|
|
55
|
-
void this.connect(url, headers);
|
|
56
|
-
}
|
|
57
|
-
addEventListener(type, fn, _opts) {
|
|
58
|
-
this.listeners[type]?.add(fn);
|
|
59
|
-
}
|
|
60
|
-
removeEventListener(type, fn) {
|
|
61
|
-
this.listeners[type]?.delete(fn);
|
|
62
|
-
}
|
|
63
|
-
emit(type, event) {
|
|
64
|
-
for (const fn of [...(this.listeners[type] ?? [])]) {
|
|
65
|
-
try {
|
|
66
|
-
fn(event);
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
// listener errors must not crash the socket loop
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
async connect(url, headers) {
|
|
74
|
-
const u = new URL(url.replace(/^ws:/, 'http:').replace(/^wss:/, 'https:'));
|
|
75
|
-
const host = u.hostname;
|
|
76
|
-
const port = u.port ? Number(u.port) : u.protocol === 'https:' ? 443 : 80;
|
|
77
|
-
const path = `${u.pathname}${u.search}`;
|
|
78
|
-
const keyBytes = crypto.getRandomValues(new Uint8Array(16));
|
|
79
|
-
const key = Buffer.from(keyBytes).toString('base64');
|
|
80
|
-
const acceptDigest = await crypto.subtle.digest('SHA-1', new TextEncoder().encode(key + WS_GUID));
|
|
81
|
-
this.expectedAccept = Buffer.from(new Uint8Array(acceptDigest)).toString('base64');
|
|
82
|
-
const lower = {};
|
|
83
|
-
for (const [k, v] of Object.entries(headers))
|
|
84
|
-
lower[k.toLowerCase()] = v;
|
|
85
|
-
// Build the upgrade in Codex's exact byte order.
|
|
86
|
-
const lines = [
|
|
87
|
-
`GET ${path} HTTP/1.1`,
|
|
88
|
-
`Host: ${host}`,
|
|
89
|
-
`Connection: Upgrade`,
|
|
90
|
-
`Upgrade: websocket`,
|
|
91
|
-
`Sec-WebSocket-Version: 13`,
|
|
92
|
-
`Sec-WebSocket-Key: ${key}`,
|
|
93
|
-
];
|
|
94
|
-
for (const name of CODEX_APP_HEADER_ORDER) {
|
|
95
|
-
if (lower[name] !== undefined)
|
|
96
|
-
lines.push(`${canonical(name)}: ${lower[name]}`);
|
|
97
|
-
}
|
|
98
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
99
|
-
if (!CODEX_APP_HEADER_ORDER.includes(k.toLowerCase()) &&
|
|
100
|
-
k.toLowerCase() !== 'content-length') {
|
|
101
|
-
lines.push(`${k}: ${v}`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
const request = `${lines.join('\r\n')}\r\n\r\n`;
|
|
105
|
-
try {
|
|
106
|
-
void this.log('connect_start', {
|
|
107
|
-
host,
|
|
108
|
-
port,
|
|
109
|
-
tls: u.protocol === 'https:',
|
|
110
|
-
});
|
|
111
|
-
const onOpen = () => {
|
|
112
|
-
this.socket?.write(request);
|
|
113
|
-
};
|
|
114
|
-
const socket = u.protocol === 'https:'
|
|
115
|
-
? tlsConnect({ host, port, servername: host }, onOpen)
|
|
116
|
-
: netConnect({ host, port }, onOpen);
|
|
117
|
-
this.socket = socket;
|
|
118
|
-
socket.on('data', (data) => {
|
|
119
|
-
this.onData(typeof data === 'string' ? Buffer.from(data, 'binary') : data);
|
|
120
|
-
});
|
|
121
|
-
socket.on('error', (e) => {
|
|
122
|
-
this.readyState = 3;
|
|
123
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
124
|
-
void this.log('socket_error', { message });
|
|
125
|
-
this.emit('error', { message });
|
|
126
|
-
});
|
|
127
|
-
socket.on('close', (hadError) => {
|
|
128
|
-
this.readyState = 3;
|
|
129
|
-
void this.log('socket_close', { hadError });
|
|
130
|
-
this.emit('close', { code: 1006, reason: 'socket closed' });
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
catch (e) {
|
|
134
|
-
this.readyState = 3;
|
|
135
|
-
void this.log('connect_exception', {
|
|
136
|
-
message: e instanceof Error ? e.message : String(e),
|
|
137
|
-
});
|
|
138
|
-
this.emit('error', {
|
|
139
|
-
message: e instanceof Error ? e.message : String(e),
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
onData(d) {
|
|
144
|
-
this.rxBuffer = concat(this.rxBuffer, d);
|
|
145
|
-
if (!this.handshakeDone) {
|
|
146
|
-
const text = Buffer.from(this.rxBuffer).toString('latin1');
|
|
147
|
-
const idx = text.indexOf('\r\n\r\n');
|
|
148
|
-
if (idx === -1)
|
|
149
|
-
return;
|
|
150
|
-
const headerText = text.slice(0, idx);
|
|
151
|
-
const statusLine = headerText.split('\r\n')[0] ?? '';
|
|
152
|
-
const acceptMatch = headerText.match(/sec-websocket-accept:\s*(\S+)/i);
|
|
153
|
-
if (!statusLine.includes(' 101')) {
|
|
154
|
-
this.readyState = 3;
|
|
155
|
-
this.socket?.destroy?.();
|
|
156
|
-
void this.log('upgrade_failed', { statusLine });
|
|
157
|
-
this.emit('error', { message: `WS upgrade failed: ${statusLine}` });
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
if (!acceptMatch || acceptMatch[1] !== this.expectedAccept) {
|
|
161
|
-
this.readyState = 3;
|
|
162
|
-
this.socket?.destroy?.();
|
|
163
|
-
void this.log('accept_mismatch', { statusLine });
|
|
164
|
-
this.emit('error', {
|
|
165
|
-
message: 'WS upgrade Sec-WebSocket-Accept mismatch',
|
|
166
|
-
});
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
this.handshakeDone = true;
|
|
170
|
-
this.readyState = 1;
|
|
171
|
-
this.rxBuffer = this.rxBuffer.slice(idx + 4);
|
|
172
|
-
void this.log('open', { statusLine });
|
|
173
|
-
this.emit('open', {});
|
|
174
|
-
}
|
|
175
|
-
this.drainFrames();
|
|
176
|
-
}
|
|
177
|
-
drainFrames() {
|
|
178
|
-
// RFC 6455 frame parsing for server->client (never masked).
|
|
179
|
-
for (;;) {
|
|
180
|
-
const buf = this.rxBuffer;
|
|
181
|
-
if (buf.length < 2)
|
|
182
|
-
return;
|
|
183
|
-
const b0 = buf[0];
|
|
184
|
-
const b1 = buf[1];
|
|
185
|
-
const fin = (b0 & 0x80) !== 0;
|
|
186
|
-
const opcode = b0 & 0x0f;
|
|
187
|
-
const masked = (b1 & 0x80) !== 0;
|
|
188
|
-
let len = b1 & 0x7f;
|
|
189
|
-
let offset = 2;
|
|
190
|
-
if (len === 126) {
|
|
191
|
-
if (buf.length < 4)
|
|
192
|
-
return;
|
|
193
|
-
len = (buf[2] << 8) | buf[3];
|
|
194
|
-
offset = 4;
|
|
195
|
-
}
|
|
196
|
-
else if (len === 127) {
|
|
197
|
-
if (buf.length < 10)
|
|
198
|
-
return;
|
|
199
|
-
len = 0;
|
|
200
|
-
for (let i = 0; i < 8; i++)
|
|
201
|
-
len = len * 256 + buf[2 + i];
|
|
202
|
-
offset = 10;
|
|
203
|
-
}
|
|
204
|
-
if (masked)
|
|
205
|
-
offset += 4; // shouldn't happen server->client
|
|
206
|
-
if (buf.length < offset + len)
|
|
207
|
-
return;
|
|
208
|
-
const payload = buf.slice(offset, offset + len);
|
|
209
|
-
this.rxBuffer = buf.slice(offset + len);
|
|
210
|
-
this.handleFrame(fin, opcode, payload);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
handleFrame(fin, opcode, payload) {
|
|
214
|
-
if (opcode === 0x9) {
|
|
215
|
-
// ping -> pong (echo payload), masked
|
|
216
|
-
this.writeFrame(0xa, payload);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
if (opcode === 0xa)
|
|
220
|
-
return; // pong
|
|
221
|
-
if (opcode === 0x8) {
|
|
222
|
-
// close
|
|
223
|
-
let code = 1005;
|
|
224
|
-
let reason = '';
|
|
225
|
-
if (payload.length >= 2) {
|
|
226
|
-
code = (payload[0] << 8) | payload[1];
|
|
227
|
-
reason = Buffer.from(payload.slice(2)).toString('utf-8');
|
|
228
|
-
}
|
|
229
|
-
this.readyState = 3;
|
|
230
|
-
try {
|
|
231
|
-
this.socket?.end();
|
|
232
|
-
}
|
|
233
|
-
catch {
|
|
234
|
-
/* ignore */
|
|
235
|
-
}
|
|
236
|
-
this.emit('close', { code, reason });
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
// data frame: 0x1 text, 0x2 binary, 0x0 continuation
|
|
240
|
-
if (opcode === 0x1 || opcode === 0x2) {
|
|
241
|
-
this.fragOpcode = opcode;
|
|
242
|
-
this.fragChunks = [payload];
|
|
243
|
-
}
|
|
244
|
-
else if (opcode === 0x0) {
|
|
245
|
-
this.fragChunks.push(payload);
|
|
246
|
-
}
|
|
247
|
-
if (!fin)
|
|
248
|
-
return;
|
|
249
|
-
const full = concatAll(this.fragChunks);
|
|
250
|
-
this.fragChunks = [];
|
|
251
|
-
if (this.fragOpcode === 0x1) {
|
|
252
|
-
const text = Buffer.from(full).toString('utf-8');
|
|
253
|
-
this.emit('message', { data: text });
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
send(data) {
|
|
257
|
-
if (this.readyState !== 1)
|
|
258
|
-
return;
|
|
259
|
-
this.writeFrame(0x1, new TextEncoder().encode(data));
|
|
260
|
-
}
|
|
261
|
-
writeFrame(opcode, payload) {
|
|
262
|
-
const len = payload.length;
|
|
263
|
-
const mask = crypto.getRandomValues(new Uint8Array(4));
|
|
264
|
-
let header;
|
|
265
|
-
if (len < 126)
|
|
266
|
-
header = [0x80 | opcode, 0x80 | len];
|
|
267
|
-
else if (len < 65536)
|
|
268
|
-
header = [0x80 | opcode, 0x80 | 126, (len >> 8) & 0xff, len & 0xff];
|
|
269
|
-
else {
|
|
270
|
-
header = [0x80 | opcode, 0x80 | 127];
|
|
271
|
-
for (let i = 7; i >= 0; i--)
|
|
272
|
-
header.push((len / 2 ** (8 * i)) & 0xff);
|
|
273
|
-
}
|
|
274
|
-
const masked = new Uint8Array(len);
|
|
275
|
-
for (let i = 0; i < len; i++)
|
|
276
|
-
masked[i] = payload[i] ^ mask[i % 4];
|
|
277
|
-
const frame = new Uint8Array(header.length + 4 + len);
|
|
278
|
-
frame.set(header, 0);
|
|
279
|
-
frame.set(mask, header.length);
|
|
280
|
-
frame.set(masked, header.length + 4);
|
|
281
|
-
try {
|
|
282
|
-
this.socket?.write(frame);
|
|
283
|
-
}
|
|
284
|
-
catch {
|
|
285
|
-
/* socket gone */
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
close() {
|
|
289
|
-
if (this.readyState === 3)
|
|
290
|
-
return;
|
|
291
|
-
this.readyState = 2;
|
|
292
|
-
try {
|
|
293
|
-
this.writeFrame(0x8, new Uint8Array([0x03, 0xe8])); // 1000
|
|
294
|
-
this.socket?.end();
|
|
295
|
-
}
|
|
296
|
-
catch {
|
|
297
|
-
/* ignore */
|
|
298
|
-
}
|
|
299
|
-
this.readyState = 3;
|
|
300
|
-
}
|
|
301
|
-
log(event, data) {
|
|
302
|
-
void event;
|
|
303
|
-
void data;
|
|
304
|
-
return Promise.resolve();
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
function canonical(lower) {
|
|
308
|
-
// Match Codex's header casing for the few that aren't all-lowercase on the wire.
|
|
309
|
-
return lower;
|
|
310
|
-
}
|
|
311
|
-
function concat(a, b) {
|
|
312
|
-
const out = new Uint8Array(a.length + b.length);
|
|
313
|
-
out.set(a, 0);
|
|
314
|
-
out.set(b, a.length);
|
|
315
|
-
return out;
|
|
316
|
-
}
|
|
317
|
-
function concatAll(parts) {
|
|
318
|
-
let total = 0;
|
|
319
|
-
for (const p of parts)
|
|
320
|
-
total += p.length;
|
|
321
|
-
const out = new Uint8Array(total);
|
|
322
|
-
let off = 0;
|
|
323
|
-
for (const p of parts) {
|
|
324
|
-
out.set(p, off);
|
|
325
|
-
off += p.length;
|
|
326
|
-
}
|
|
327
|
-
return out;
|
|
328
|
-
}
|