@xiaoso/dsh-tool-plus 0.1.0-beta.3 → 0.1.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/abort-D1_Z5Ut5.mjs +51 -0
- package/lib/abort-dIkZBV7Q.mjs +51 -0
- package/lib/amazon-bedrock-B0nxT06F.mjs +917 -0
- package/lib/amazon-bedrock-WQhvjHYu.mjs +917 -0
- package/lib/anthropic-CJCauvYz.mjs +3115 -0
- package/lib/anthropic-CZghe_9U.mjs +3115 -0
- package/lib/anthropic-sCMH7JID.mjs +262 -0
- package/lib/anthropic-tTntD8BD.mjs +262 -0
- package/lib/api-key-login-BzqNdM2y.mjs +157 -0
- package/lib/api-key-login-CWVwvHT1.mjs +157 -0
- package/lib/aws-credentials-CXryMobh.mjs +867 -0
- package/lib/aws-credentials-ChG79ey-.mjs +867 -0
- package/lib/azure-openai-responses-Cgmc-rbL.mjs +274 -0
- package/lib/azure-openai-responses-DuukbD0F.mjs +274 -0
- package/lib/bun-shim.mjs +81 -6
- package/lib/bundled-references-Cavaffah.mjs +55 -0
- package/lib/bundled-references-sLN0YdXU.mjs +55 -0
- package/lib/callback-server-CQ5Jo8F-.mjs +398 -0
- package/lib/callback-server-DDSrZ6l_.mjs +398 -0
- package/lib/client.js +1 -1
- package/lib/cursor-B3zg7EHj.mjs +273 -0
- package/lib/cursor-B6TUEmgP.mjs +135 -0
- package/lib/cursor-D8gvtTL3.mjs +135 -0
- package/lib/cursor-DkSX2h3H.mjs +5441 -0
- package/lib/cursor-aJrXeYCb.mjs +273 -0
- package/lib/cursor-mJQwUKXK.mjs +5441 -0
- package/lib/device-code-DBo3B5nh.mjs +51 -0
- package/lib/device-code-DIb0FU-H.mjs +51 -0
- package/lib/devin-BHZZyMhO.mjs +95 -0
- package/lib/devin-BK7DHeca.mjs +114 -0
- package/lib/devin-CfMp3cXS.mjs +95 -0
- package/lib/devin-D9zR5B_R.mjs +542 -0
- package/lib/devin-Q1yem3lp.mjs +114 -0
- package/lib/devin-vNZTNUON.mjs +542 -0
- package/lib/error-Dd8DIqs-.mjs +1449 -0
- package/lib/error-yCSG0mGC.mjs +1449 -0
- package/lib/event-stream-BbFZJy-z.mjs +151 -0
- package/lib/event-stream-loUa0s1T.mjs +151 -0
- package/lib/fs-promises-shim-Bx0fpxuk.mjs +57 -0
- package/lib/github-copilot-B1yiyVRh.mjs +104 -0
- package/lib/github-copilot-BWT3eAOF.mjs +208 -0
- package/lib/github-copilot-CYkjeTi3.mjs +208 -0
- package/lib/github-copilot-jscUZ8Hf.mjs +104 -0
- package/lib/gitlab-duo-CM7Ref1H.mjs +155 -0
- package/lib/gitlab-duo-gubO4FNw.mjs +155 -0
- package/lib/gitlab-duo-workflow-BGAF6JDw.mjs +91 -0
- package/lib/gitlab-duo-workflow-D78tdn8E.mjs +91 -0
- package/lib/google-BS4T25Pn.mjs +30 -0
- package/lib/google-TFe-7-of.mjs +30 -0
- package/lib/google-antigravity-DGmwC2KV.mjs +198 -0
- package/lib/google-antigravity-DNYhgTpA.mjs +198 -0
- package/lib/google-auth-B6gjsZvg.mjs +232 -0
- package/lib/google-auth-De7_s3eo.mjs +232 -0
- package/lib/google-gemini-cli-CWu8HvXW.mjs +211 -0
- package/lib/google-gemini-cli-Cb97bzQc.mjs +850 -0
- package/lib/google-gemini-cli-Cqds_Q5T.mjs +850 -0
- package/lib/google-gemini-cli-Dsztq0N5.mjs +211 -0
- package/lib/google-oauth-shared-CuEHaDDj.mjs +147 -0
- package/lib/google-oauth-shared-yqlyqPPC.mjs +147 -0
- package/lib/google-shared-CgKmmdO3.mjs +760 -0
- package/lib/google-shared-Dr-VG-ap.mjs +760 -0
- package/lib/google-vertex-BDN2tLaD.mjs +99 -0
- package/lib/google-vertex-DopVOQOG.mjs +99 -0
- package/lib/identity-Da1GPU9a.mjs +219 -0
- package/lib/identity-xRP7_eSR.mjs +219 -0
- package/lib/idle-iterator-Baoyn3Hp.mjs +366 -0
- package/lib/idle-iterator-DUjKQEGT.mjs +366 -0
- package/lib/index.mjs +1731 -1178
- package/lib/kimi--wUOWER5.mjs +236 -0
- package/lib/kimi-Dh0pv3WZ.mjs +236 -0
- package/lib/markit-BL3cH47U.mjs +1417 -0
- package/lib/markit-BNiNY6SE.mjs +1417 -0
- package/lib/minimax-code-DCbuA6Yk.mjs +53 -0
- package/lib/minimax-code-Uq8Qu9LW.mjs +53 -0
- package/lib/models-D_XnT6ay.mjs +104660 -0
- package/lib/models-SGASygJ3.mjs +104660 -0
- package/lib/ollama-DPzBmq89.mjs +589 -0
- package/lib/ollama-DoBz5SFm.mjs +589 -0
- package/lib/openai-codex-Bi-4S4PX.mjs +255 -0
- package/lib/openai-codex-Ch8CkT3p.mjs +255 -0
- package/lib/openai-codex-responses-CvxL_FMF.mjs +3144 -0
- package/lib/openai-codex-responses-pejZgdZo.mjs +3144 -0
- package/lib/openai-completions-CF8IyNtP.mjs +1360 -0
- package/lib/openai-completions-DIf361kF.mjs +1360 -0
- package/lib/openai-reasoning-fallback-Nj6yB2rY.mjs +286 -0
- package/lib/openai-reasoning-fallback-OdJREP1o.mjs +286 -0
- package/lib/openai-responses-BqXJInHX.mjs +867 -0
- package/lib/openai-responses-Db_a-U4-.mjs +867 -0
- package/lib/opencode-DhMolSoB.mjs +38 -0
- package/lib/opencode-Rq_Q2vAO.mjs +38 -0
- package/lib/perplexity-C4iLVMUj.mjs +194 -0
- package/lib/perplexity-Uu7Jzg5b.mjs +194 -0
- package/lib/proxy-DDqrmjjC.mjs +190 -0
- package/lib/proxy-pM_NIv-H.mjs +190 -0
- package/lib/request-debug-HJMoJncL.mjs +183 -0
- package/lib/sanitize-text-CfcXzw5X.mjs +33 -0
- package/lib/scrapers-BkFfcPHj.mjs +9198 -0
- package/lib/scrapers-CVptboMs.mjs +9198 -0
- package/lib/scrapers-nhQAvc8d.mjs +9198 -0
- package/lib/shell-snapshot-fn-env.sh +67 -43
- package/lib/src-BgpusnsB.mjs +5076 -0
- package/lib/src-lDmkSRbm.mjs +5006 -0
- package/lib/stream-BlldLkVq.mjs +10236 -0
- package/lib/stream-CRtexYLe.mjs +10236 -0
- package/lib/stream-markup-healing-DMsxBx1L.mjs +491 -0
- package/lib/stream-markup-healing-KMkiAA00.mjs +491 -0
- package/lib/turndown-DN7ByzEI.mjs +667 -0
- package/lib/types/config/settings.d.ts +9 -1
- package/lib/types/config/truncate.d.ts +9 -5
- package/lib/types/tools/bash/background.d.ts +1 -1
- package/lib/types/tools/bash/bash-executor.d.ts +4 -0
- package/lib/types/tools/bash/bash-interceptor.d.ts +10 -8
- package/lib/types/tools/bash/cd-workdir.d.ts +35 -0
- package/lib/types/tools/bash/non-interactive-env.d.ts +2 -0
- package/lib/types/tools/bash/render.d.ts +4 -3
- package/lib/types/tools/bash/sanitize-text.d.ts +5 -0
- package/lib/types/tools/bash/shell-snapshot.d.ts +4 -1
- package/lib/types/tools/bash/shell-tokenize.d.ts +60 -0
- package/lib/types/tools/bash/streaming-output.d.ts +12 -212
- package/lib/types/tools/bash/which.d.ts +20 -0
- package/lib/types/tools/bash/windows-shell.d.ts +31 -0
- package/lib/types/tools/edit/adapter/index.d.ts +25 -3
- package/lib/types/tools/edit/adapter/tools/auto-generated-guard.d.ts +7 -17
- package/lib/types/tools/glob/adapter/index.d.ts +27 -1
- package/lib/types/tools/grep/adapter/index.d.ts +29 -1
- package/lib/types/tools/omp/edit/dispatch.d.ts +43 -0
- package/lib/types/tools/omp/edit/hashline/noop-loop-guard.d.ts +3 -1
- package/lib/types/tools/omp/tools/auto-generated-guard.d.ts +20 -0
- package/lib/types/tools/omp/tools/write.d.ts +4 -4
- package/lib/types/tools/shared/auto-generated-guard.dsh.d.ts +16 -4
- package/lib/types/tools/shared/bun-shim.d.ts +4 -0
- package/lib/types/tools/shared/fs-promises-shim.d.ts +3 -1
- package/lib/types/tools/shared/omp-prompt.d.ts +13 -7
- package/lib/types/tools/shared/session-state.d.ts +9 -3
- package/lib/utils-Bw139LTz.mjs +8119 -0
- package/lib/utils-CZw4tVZQ.mjs +12315 -0
- package/lib/utils-CiGN7OHR.mjs +12315 -0
- package/lib/utils-Cp76gCyk.mjs +8119 -0
- package/lib/utils-D0GRDgAr.mjs +12315 -0
- package/lib/utils-D2YknRk4.mjs +51 -0
- package/lib/utils-DzXZDfYc.mjs +51 -0
- package/lib/vision-guard-01qSwFpf.mjs +92 -0
- package/lib/vision-guard-DzZGaOga.mjs +92 -0
- package/lib/wafer-CgtPHerf.mjs +17 -0
- package/lib/wafer-cBtrn6q9.mjs +17 -0
- package/lib/xai-oauth-Buk8XVvM.mjs +446 -0
- package/lib/xai-oauth-CMal8l1O.mjs +446 -0
- package/lib/xiaomi-CZgwvX2G.mjs +157 -0
- package/lib/xiaomi-D2qT5cAC.mjs +157 -0
- package/lib/zai-7DWMkEqM.mjs +203 -0
- package/lib/zai-C59YnomG.mjs +203 -0
- package/lib/zip-Cg9ANN_U.mjs +1316 -0
- package/lib/zip-D36ZKsP8.mjs +1316 -0
- package/package.json +1 -1
- package/presets/package.json +1 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { installBunShim as __installBunShim } from "./bun-shim.mjs";
|
|
2
|
+
__installBunShim();
|
|
3
|
+
import { c as OAuthError, h as LoginCancelledError } from "./error-yCSG0mGC.mjs";
|
|
4
|
+
import "./utils-Bw139LTz.mjs";
|
|
5
|
+
import { n as isRecord } from "./type-guards-B9VVjRDK.mjs";
|
|
6
|
+
import { t as OAuthCallbackFlow } from "./callback-server-DDSrZ6l_.mjs";
|
|
7
|
+
import { t as generatePKCE } from "./pkce-BlQikGPu.mjs";
|
|
8
|
+
import { i as OPENAI_HEADER_VALUES } from "./codex-P1dGpX7G.mjs";
|
|
9
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/registry/oauth/openai-codex.ts
|
|
10
|
+
/**
|
|
11
|
+
* OpenAI Codex (ChatGPT OAuth) flow — browser and device-code flows.
|
|
12
|
+
*/
|
|
13
|
+
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
14
|
+
const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
|
|
15
|
+
const TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
16
|
+
const CALLBACK_PORT = 1455;
|
|
17
|
+
const CALLBACK_PATH = "/auth/callback";
|
|
18
|
+
const SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
|
|
19
|
+
const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
20
|
+
const JWT_PROFILE_CLAIM = "https://api.openai.com/profile";
|
|
21
|
+
const TOKEN_REQUEST_TIMEOUT_MS = 15e3;
|
|
22
|
+
const DEVICE_USERCODE_URL = "https://auth.openai.com/api/accounts/deviceauth/usercode";
|
|
23
|
+
const DEVICE_TOKEN_URL = "https://auth.openai.com/api/accounts/deviceauth/token";
|
|
24
|
+
const DEVICE_REDIRECT_URI = "https://auth.openai.com/deviceauth/callback";
|
|
25
|
+
const DEVICE_AUTH_URL = "https://auth.openai.com/codex/device";
|
|
26
|
+
const DEVICE_POLL_INTERVAL_MS = 5e3;
|
|
27
|
+
const DEVICE_POLL_SAFETY_MARGIN_MS = 3e3;
|
|
28
|
+
/** Upper bound on device-code polling to avoid infinite loops on server errors. */
|
|
29
|
+
const DEVICE_MAX_POLLS = 120;
|
|
30
|
+
function decodeJwt(token) {
|
|
31
|
+
try {
|
|
32
|
+
const parts = token.split(".");
|
|
33
|
+
if (parts.length !== 3) return null;
|
|
34
|
+
const payload = parts[1] ?? "";
|
|
35
|
+
const decoded = Buffer.from(payload, "base64").toString("utf-8");
|
|
36
|
+
return JSON.parse(decoded);
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Identity slice decoded from the token claims. The ChatGPT workspace
|
|
43
|
+
* (`chatgpt_account_id`) is the subscription pool the token draws limits
|
|
44
|
+
* from — one account email can hold several (e.g. a personal Pro plan plus a
|
|
45
|
+
* Team seat). `chatgpt_plan_type` may only be present on the `id_token`.
|
|
46
|
+
*/
|
|
47
|
+
function getTokenProfile(accessToken, idToken) {
|
|
48
|
+
const payload = decodeJwt(accessToken);
|
|
49
|
+
const idPayload = idToken ? decodeJwt(idToken) : null;
|
|
50
|
+
const auth = payload?.[JWT_CLAIM_PATH];
|
|
51
|
+
const idAuth = idPayload?.[JWT_CLAIM_PATH];
|
|
52
|
+
const accountId = auth?.chatgpt_account_id;
|
|
53
|
+
const email = payload?.[JWT_PROFILE_CLAIM]?.email?.trim().toLowerCase();
|
|
54
|
+
const planType = (auth?.chatgpt_plan_type ?? idAuth?.chatgpt_plan_type)?.trim().toLowerCase();
|
|
55
|
+
return {
|
|
56
|
+
accountId: typeof accountId === "string" && accountId.length > 0 ? accountId : void 0,
|
|
57
|
+
email: typeof email === "string" && email.length > 0 ? email : void 0,
|
|
58
|
+
planType: typeof planType === "string" && planType.length > 0 ? planType : void 0
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function describeTokenEndpointValue(value) {
|
|
62
|
+
if (typeof value === "string") {
|
|
63
|
+
const trimmed = value.trim();
|
|
64
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
65
|
+
}
|
|
66
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
67
|
+
if (!isRecord(value)) return void 0;
|
|
68
|
+
const code = describeTokenEndpointValue(value.code ?? value.error);
|
|
69
|
+
const message = describeTokenEndpointValue(value.message ?? value.error_description ?? value.description);
|
|
70
|
+
if (code && message && code !== message) return `${code}: ${message}`;
|
|
71
|
+
return code ?? message ?? JSON.stringify(value);
|
|
72
|
+
}
|
|
73
|
+
/** Formats OpenAI Codex OAuth token endpoint errors for login and refresh failures. */
|
|
74
|
+
function formatOpenAICodexTokenEndpointError(status, bodyText) {
|
|
75
|
+
const trimmed = bodyText.trim();
|
|
76
|
+
if (trimmed.length === 0) return `${status}`;
|
|
77
|
+
try {
|
|
78
|
+
const body = JSON.parse(trimmed);
|
|
79
|
+
if (!isRecord(body)) return `${status} ${trimmed}`;
|
|
80
|
+
const error = describeTokenEndpointValue(body.error);
|
|
81
|
+
const description = describeTokenEndpointValue(body.error_description);
|
|
82
|
+
if (error && description && error !== description) return `${status} ${error}: ${description}`;
|
|
83
|
+
return `${status} ${error ?? description ?? describeTokenEndpointValue(body.message) ?? trimmed}`;
|
|
84
|
+
} catch {
|
|
85
|
+
return `${status} ${trimmed}`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Builds the Codex browser OAuth URL used by browser login; exported for auth regression tests. */
|
|
89
|
+
function createOpenAICodexAuthorizationUrl(args) {
|
|
90
|
+
const originator = args.originator?.trim() || OPENAI_HEADER_VALUES.ORIGINATOR_CODEX;
|
|
91
|
+
const searchParams = new URLSearchParams({
|
|
92
|
+
response_type: "code",
|
|
93
|
+
client_id: CLIENT_ID,
|
|
94
|
+
redirect_uri: args.redirectUri,
|
|
95
|
+
scope: SCOPE,
|
|
96
|
+
code_challenge: args.challenge,
|
|
97
|
+
code_challenge_method: "S256",
|
|
98
|
+
state: args.state,
|
|
99
|
+
id_token_add_organizations: "true",
|
|
100
|
+
codex_cli_simplified_flow: "true",
|
|
101
|
+
originator
|
|
102
|
+
});
|
|
103
|
+
return `${AUTHORIZE_URL}?${searchParams.toString()}`;
|
|
104
|
+
}
|
|
105
|
+
var OpenAICodexOAuthFlow = class extends OAuthCallbackFlow {
|
|
106
|
+
#pkce;
|
|
107
|
+
#originator;
|
|
108
|
+
#fetch;
|
|
109
|
+
constructor(ctrl, pkce, originator, fetchImpl) {
|
|
110
|
+
super(ctrl, {
|
|
111
|
+
preferredPort: CALLBACK_PORT,
|
|
112
|
+
callbackPath: CALLBACK_PATH,
|
|
113
|
+
redirectUri: `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`
|
|
114
|
+
});
|
|
115
|
+
this.#pkce = pkce;
|
|
116
|
+
this.#originator = originator;
|
|
117
|
+
this.#fetch = fetchImpl;
|
|
118
|
+
}
|
|
119
|
+
async generateAuthUrl(state, redirectUri) {
|
|
120
|
+
return {
|
|
121
|
+
url: createOpenAICodexAuthorizationUrl({
|
|
122
|
+
state,
|
|
123
|
+
redirectUri,
|
|
124
|
+
challenge: this.#pkce.challenge,
|
|
125
|
+
originator: this.#originator
|
|
126
|
+
}),
|
|
127
|
+
instructions: "A browser window should open. Complete login to finish."
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
async exchangeToken(code, _state, redirectUri) {
|
|
131
|
+
return exchangeCodeForToken(code, this.#pkce.verifier, redirectUri, this.#fetch);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
async function exchangeCodeForToken(code, verifier, redirectUri, fetchImpl = fetch) {
|
|
135
|
+
const tokenResponse = await fetchImpl(TOKEN_URL, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
138
|
+
body: new URLSearchParams({
|
|
139
|
+
grant_type: "authorization_code",
|
|
140
|
+
client_id: CLIENT_ID,
|
|
141
|
+
code,
|
|
142
|
+
code_verifier: verifier,
|
|
143
|
+
redirect_uri: redirectUri
|
|
144
|
+
}),
|
|
145
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS)
|
|
146
|
+
});
|
|
147
|
+
if (!tokenResponse.ok) {
|
|
148
|
+
const bodyText = await tokenResponse.text();
|
|
149
|
+
throw new OAuthError(`Token exchange failed: ${formatOpenAICodexTokenEndpointError(tokenResponse.status, bodyText)}`, {
|
|
150
|
+
kind: "token-exchange",
|
|
151
|
+
status: tokenResponse.status
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
const tokenData = await tokenResponse.json();
|
|
155
|
+
if (!tokenData.access_token || !tokenData.refresh_token || typeof tokenData.expires_in !== "number") throw new OAuthError("Token response missing required fields", { kind: "validation" });
|
|
156
|
+
const { accountId, email, planType } = getTokenProfile(tokenData.access_token, tokenData.id_token);
|
|
157
|
+
if (!accountId) throw new OAuthError("Failed to extract accountId from token", { kind: "validation" });
|
|
158
|
+
return {
|
|
159
|
+
access: tokenData.access_token,
|
|
160
|
+
refresh: tokenData.refresh_token,
|
|
161
|
+
expires: Date.now() + tokenData.expires_in * 1e3,
|
|
162
|
+
accountId,
|
|
163
|
+
email,
|
|
164
|
+
orgId: accountId,
|
|
165
|
+
orgName: planType
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
async function loginOpenAICodex(options) {
|
|
169
|
+
return new OpenAICodexOAuthFlow(options, await generatePKCE(), options.originator?.trim() || OPENAI_HEADER_VALUES.ORIGINATOR_CODEX, options.fetch ?? fetch).login();
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Login with OpenAI Codex using the device-code (headless) flow.
|
|
173
|
+
*
|
|
174
|
+
* Avoids a local callback server entirely — useful when port 1455 is unavailable
|
|
175
|
+
* or when the browser callback flow fails with 403 (e.g. network/proxy issues).
|
|
176
|
+
*/
|
|
177
|
+
async function loginOpenAICodexDevice(ctrl) {
|
|
178
|
+
ctrl.onProgress?.("Initiating device authorization…");
|
|
179
|
+
const initResponse = await fetch(DEVICE_USERCODE_URL, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
headers: { "Content-Type": "application/json" },
|
|
182
|
+
body: JSON.stringify({ client_id: CLIENT_ID }),
|
|
183
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS)
|
|
184
|
+
});
|
|
185
|
+
if (!initResponse.ok) throw new OAuthError(`Device authorization initiation failed: ${initResponse.status}`, {
|
|
186
|
+
kind: "device-auth",
|
|
187
|
+
status: initResponse.status
|
|
188
|
+
});
|
|
189
|
+
const initData = await initResponse.json();
|
|
190
|
+
if (!initData.device_auth_id || !initData.user_code) throw new OAuthError("Device authorization response missing required fields", { kind: "validation" });
|
|
191
|
+
const userCode = initData.user_code;
|
|
192
|
+
const pollIntervalMs = (typeof initData.interval === "number" ? initData.interval : parseInt(String(initData.interval ?? "5"), 10) || 5) * 1e3 + DEVICE_POLL_SAFETY_MARGIN_MS;
|
|
193
|
+
ctrl.onAuth?.({
|
|
194
|
+
url: DEVICE_AUTH_URL,
|
|
195
|
+
instructions: `Enter code: ${userCode}`
|
|
196
|
+
});
|
|
197
|
+
ctrl.onProgress?.(`Waiting for browser authorization (code: ${userCode})…`);
|
|
198
|
+
for (let poll = 0; poll < DEVICE_MAX_POLLS; poll++) {
|
|
199
|
+
await Bun.sleep(poll === 0 ? Math.min(pollIntervalMs, DEVICE_POLL_INTERVAL_MS) : pollIntervalMs);
|
|
200
|
+
if (ctrl.signal?.aborted) throw new LoginCancelledError("Device authorization cancelled");
|
|
201
|
+
const pollResponse = await fetch(DEVICE_TOKEN_URL, {
|
|
202
|
+
method: "POST",
|
|
203
|
+
headers: { "Content-Type": "application/json" },
|
|
204
|
+
body: JSON.stringify({
|
|
205
|
+
device_auth_id: initData.device_auth_id,
|
|
206
|
+
user_code: userCode
|
|
207
|
+
}),
|
|
208
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS)
|
|
209
|
+
});
|
|
210
|
+
if (pollResponse.status === 403 || pollResponse.status === 404) continue;
|
|
211
|
+
if (!pollResponse.ok) throw new OAuthError(`Device token polling failed: ${pollResponse.status}`, {
|
|
212
|
+
kind: "polling",
|
|
213
|
+
status: pollResponse.status
|
|
214
|
+
});
|
|
215
|
+
const pollData = await pollResponse.json();
|
|
216
|
+
if (!pollData.authorization_code || !pollData.code_verifier) throw new OAuthError("Device token response missing authorization_code or code_verifier", { kind: "validation" });
|
|
217
|
+
ctrl.onProgress?.("Exchanging authorization code for tokens…");
|
|
218
|
+
return exchangeCodeForToken(pollData.authorization_code, pollData.code_verifier, DEVICE_REDIRECT_URI);
|
|
219
|
+
}
|
|
220
|
+
throw new OAuthError("Device authorization timed out — user did not complete login in time", { kind: "timeout" });
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Refresh OpenAI Codex OAuth token
|
|
224
|
+
*/
|
|
225
|
+
async function refreshOpenAICodexToken(refreshToken) {
|
|
226
|
+
const response = await fetch(TOKEN_URL, {
|
|
227
|
+
method: "POST",
|
|
228
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
229
|
+
body: new URLSearchParams({
|
|
230
|
+
grant_type: "refresh_token",
|
|
231
|
+
refresh_token: refreshToken,
|
|
232
|
+
client_id: CLIENT_ID
|
|
233
|
+
}),
|
|
234
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS)
|
|
235
|
+
});
|
|
236
|
+
if (!response.ok) {
|
|
237
|
+
const bodyText = await response.text();
|
|
238
|
+
throw new OAuthError(`OpenAI Codex token refresh failed: ${formatOpenAICodexTokenEndpointError(response.status, bodyText)}`, {
|
|
239
|
+
kind: "token-refresh",
|
|
240
|
+
status: response.status
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
const tokenData = await response.json();
|
|
244
|
+
if (!tokenData.access_token || !tokenData.refresh_token || typeof tokenData.expires_in !== "number") throw new OAuthError("Token response missing required fields", { kind: "validation" });
|
|
245
|
+
const { accountId, email } = getTokenProfile(tokenData.access_token);
|
|
246
|
+
return {
|
|
247
|
+
access: tokenData.access_token,
|
|
248
|
+
refresh: tokenData.refresh_token || refreshToken,
|
|
249
|
+
expires: Date.now() + tokenData.expires_in * 1e3,
|
|
250
|
+
accountId: accountId ?? void 0,
|
|
251
|
+
email
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
//#endregion
|
|
255
|
+
export { loginOpenAICodex, loginOpenAICodexDevice, refreshOpenAICodexToken };
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { installBunShim as __installBunShim } from "./bun-shim.mjs";
|
|
2
|
+
__installBunShim();
|
|
3
|
+
import { c as OAuthError, h as LoginCancelledError } from "./error-Dd8DIqs-.mjs";
|
|
4
|
+
import "./utils-Cp76gCyk.mjs";
|
|
5
|
+
import { n as isRecord } from "./type-guards-B9VVjRDK.mjs";
|
|
6
|
+
import { t as OAuthCallbackFlow } from "./callback-server-CQ5Jo8F-.mjs";
|
|
7
|
+
import { t as generatePKCE } from "./pkce-BlQikGPu.mjs";
|
|
8
|
+
import { i as OPENAI_HEADER_VALUES } from "./codex-P1dGpX7G.mjs";
|
|
9
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/registry/oauth/openai-codex.ts
|
|
10
|
+
/**
|
|
11
|
+
* OpenAI Codex (ChatGPT OAuth) flow — browser and device-code flows.
|
|
12
|
+
*/
|
|
13
|
+
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
14
|
+
const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
|
|
15
|
+
const TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
16
|
+
const CALLBACK_PORT = 1455;
|
|
17
|
+
const CALLBACK_PATH = "/auth/callback";
|
|
18
|
+
const SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
|
|
19
|
+
const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
20
|
+
const JWT_PROFILE_CLAIM = "https://api.openai.com/profile";
|
|
21
|
+
const TOKEN_REQUEST_TIMEOUT_MS = 15e3;
|
|
22
|
+
const DEVICE_USERCODE_URL = "https://auth.openai.com/api/accounts/deviceauth/usercode";
|
|
23
|
+
const DEVICE_TOKEN_URL = "https://auth.openai.com/api/accounts/deviceauth/token";
|
|
24
|
+
const DEVICE_REDIRECT_URI = "https://auth.openai.com/deviceauth/callback";
|
|
25
|
+
const DEVICE_AUTH_URL = "https://auth.openai.com/codex/device";
|
|
26
|
+
const DEVICE_POLL_INTERVAL_MS = 5e3;
|
|
27
|
+
const DEVICE_POLL_SAFETY_MARGIN_MS = 3e3;
|
|
28
|
+
/** Upper bound on device-code polling to avoid infinite loops on server errors. */
|
|
29
|
+
const DEVICE_MAX_POLLS = 120;
|
|
30
|
+
function decodeJwt(token) {
|
|
31
|
+
try {
|
|
32
|
+
const parts = token.split(".");
|
|
33
|
+
if (parts.length !== 3) return null;
|
|
34
|
+
const payload = parts[1] ?? "";
|
|
35
|
+
const decoded = Buffer.from(payload, "base64").toString("utf-8");
|
|
36
|
+
return JSON.parse(decoded);
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Identity slice decoded from the token claims. The ChatGPT workspace
|
|
43
|
+
* (`chatgpt_account_id`) is the subscription pool the token draws limits
|
|
44
|
+
* from — one account email can hold several (e.g. a personal Pro plan plus a
|
|
45
|
+
* Team seat). `chatgpt_plan_type` may only be present on the `id_token`.
|
|
46
|
+
*/
|
|
47
|
+
function getTokenProfile(accessToken, idToken) {
|
|
48
|
+
const payload = decodeJwt(accessToken);
|
|
49
|
+
const idPayload = idToken ? decodeJwt(idToken) : null;
|
|
50
|
+
const auth = payload?.[JWT_CLAIM_PATH];
|
|
51
|
+
const idAuth = idPayload?.[JWT_CLAIM_PATH];
|
|
52
|
+
const accountId = auth?.chatgpt_account_id;
|
|
53
|
+
const email = payload?.[JWT_PROFILE_CLAIM]?.email?.trim().toLowerCase();
|
|
54
|
+
const planType = (auth?.chatgpt_plan_type ?? idAuth?.chatgpt_plan_type)?.trim().toLowerCase();
|
|
55
|
+
return {
|
|
56
|
+
accountId: typeof accountId === "string" && accountId.length > 0 ? accountId : void 0,
|
|
57
|
+
email: typeof email === "string" && email.length > 0 ? email : void 0,
|
|
58
|
+
planType: typeof planType === "string" && planType.length > 0 ? planType : void 0
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function describeTokenEndpointValue(value) {
|
|
62
|
+
if (typeof value === "string") {
|
|
63
|
+
const trimmed = value.trim();
|
|
64
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
65
|
+
}
|
|
66
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
67
|
+
if (!isRecord(value)) return void 0;
|
|
68
|
+
const code = describeTokenEndpointValue(value.code ?? value.error);
|
|
69
|
+
const message = describeTokenEndpointValue(value.message ?? value.error_description ?? value.description);
|
|
70
|
+
if (code && message && code !== message) return `${code}: ${message}`;
|
|
71
|
+
return code ?? message ?? JSON.stringify(value);
|
|
72
|
+
}
|
|
73
|
+
/** Formats OpenAI Codex OAuth token endpoint errors for login and refresh failures. */
|
|
74
|
+
function formatOpenAICodexTokenEndpointError(status, bodyText) {
|
|
75
|
+
const trimmed = bodyText.trim();
|
|
76
|
+
if (trimmed.length === 0) return `${status}`;
|
|
77
|
+
try {
|
|
78
|
+
const body = JSON.parse(trimmed);
|
|
79
|
+
if (!isRecord(body)) return `${status} ${trimmed}`;
|
|
80
|
+
const error = describeTokenEndpointValue(body.error);
|
|
81
|
+
const description = describeTokenEndpointValue(body.error_description);
|
|
82
|
+
if (error && description && error !== description) return `${status} ${error}: ${description}`;
|
|
83
|
+
return `${status} ${error ?? description ?? describeTokenEndpointValue(body.message) ?? trimmed}`;
|
|
84
|
+
} catch {
|
|
85
|
+
return `${status} ${trimmed}`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Builds the Codex browser OAuth URL used by browser login; exported for auth regression tests. */
|
|
89
|
+
function createOpenAICodexAuthorizationUrl(args) {
|
|
90
|
+
const originator = args.originator?.trim() || OPENAI_HEADER_VALUES.ORIGINATOR_CODEX;
|
|
91
|
+
const searchParams = new URLSearchParams({
|
|
92
|
+
response_type: "code",
|
|
93
|
+
client_id: CLIENT_ID,
|
|
94
|
+
redirect_uri: args.redirectUri,
|
|
95
|
+
scope: SCOPE,
|
|
96
|
+
code_challenge: args.challenge,
|
|
97
|
+
code_challenge_method: "S256",
|
|
98
|
+
state: args.state,
|
|
99
|
+
id_token_add_organizations: "true",
|
|
100
|
+
codex_cli_simplified_flow: "true",
|
|
101
|
+
originator
|
|
102
|
+
});
|
|
103
|
+
return `${AUTHORIZE_URL}?${searchParams.toString()}`;
|
|
104
|
+
}
|
|
105
|
+
var OpenAICodexOAuthFlow = class extends OAuthCallbackFlow {
|
|
106
|
+
#pkce;
|
|
107
|
+
#originator;
|
|
108
|
+
#fetch;
|
|
109
|
+
constructor(ctrl, pkce, originator, fetchImpl) {
|
|
110
|
+
super(ctrl, {
|
|
111
|
+
preferredPort: CALLBACK_PORT,
|
|
112
|
+
callbackPath: CALLBACK_PATH,
|
|
113
|
+
redirectUri: `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`
|
|
114
|
+
});
|
|
115
|
+
this.#pkce = pkce;
|
|
116
|
+
this.#originator = originator;
|
|
117
|
+
this.#fetch = fetchImpl;
|
|
118
|
+
}
|
|
119
|
+
async generateAuthUrl(state, redirectUri) {
|
|
120
|
+
return {
|
|
121
|
+
url: createOpenAICodexAuthorizationUrl({
|
|
122
|
+
state,
|
|
123
|
+
redirectUri,
|
|
124
|
+
challenge: this.#pkce.challenge,
|
|
125
|
+
originator: this.#originator
|
|
126
|
+
}),
|
|
127
|
+
instructions: "A browser window should open. Complete login to finish."
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
async exchangeToken(code, _state, redirectUri) {
|
|
131
|
+
return exchangeCodeForToken(code, this.#pkce.verifier, redirectUri, this.#fetch);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
async function exchangeCodeForToken(code, verifier, redirectUri, fetchImpl = fetch) {
|
|
135
|
+
const tokenResponse = await fetchImpl(TOKEN_URL, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
138
|
+
body: new URLSearchParams({
|
|
139
|
+
grant_type: "authorization_code",
|
|
140
|
+
client_id: CLIENT_ID,
|
|
141
|
+
code,
|
|
142
|
+
code_verifier: verifier,
|
|
143
|
+
redirect_uri: redirectUri
|
|
144
|
+
}),
|
|
145
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS)
|
|
146
|
+
});
|
|
147
|
+
if (!tokenResponse.ok) {
|
|
148
|
+
const bodyText = await tokenResponse.text();
|
|
149
|
+
throw new OAuthError(`Token exchange failed: ${formatOpenAICodexTokenEndpointError(tokenResponse.status, bodyText)}`, {
|
|
150
|
+
kind: "token-exchange",
|
|
151
|
+
status: tokenResponse.status
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
const tokenData = await tokenResponse.json();
|
|
155
|
+
if (!tokenData.access_token || !tokenData.refresh_token || typeof tokenData.expires_in !== "number") throw new OAuthError("Token response missing required fields", { kind: "validation" });
|
|
156
|
+
const { accountId, email, planType } = getTokenProfile(tokenData.access_token, tokenData.id_token);
|
|
157
|
+
if (!accountId) throw new OAuthError("Failed to extract accountId from token", { kind: "validation" });
|
|
158
|
+
return {
|
|
159
|
+
access: tokenData.access_token,
|
|
160
|
+
refresh: tokenData.refresh_token,
|
|
161
|
+
expires: Date.now() + tokenData.expires_in * 1e3,
|
|
162
|
+
accountId,
|
|
163
|
+
email,
|
|
164
|
+
orgId: accountId,
|
|
165
|
+
orgName: planType
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
async function loginOpenAICodex(options) {
|
|
169
|
+
return new OpenAICodexOAuthFlow(options, await generatePKCE(), options.originator?.trim() || OPENAI_HEADER_VALUES.ORIGINATOR_CODEX, options.fetch ?? fetch).login();
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Login with OpenAI Codex using the device-code (headless) flow.
|
|
173
|
+
*
|
|
174
|
+
* Avoids a local callback server entirely — useful when port 1455 is unavailable
|
|
175
|
+
* or when the browser callback flow fails with 403 (e.g. network/proxy issues).
|
|
176
|
+
*/
|
|
177
|
+
async function loginOpenAICodexDevice(ctrl) {
|
|
178
|
+
ctrl.onProgress?.("Initiating device authorization…");
|
|
179
|
+
const initResponse = await fetch(DEVICE_USERCODE_URL, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
headers: { "Content-Type": "application/json" },
|
|
182
|
+
body: JSON.stringify({ client_id: CLIENT_ID }),
|
|
183
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS)
|
|
184
|
+
});
|
|
185
|
+
if (!initResponse.ok) throw new OAuthError(`Device authorization initiation failed: ${initResponse.status}`, {
|
|
186
|
+
kind: "device-auth",
|
|
187
|
+
status: initResponse.status
|
|
188
|
+
});
|
|
189
|
+
const initData = await initResponse.json();
|
|
190
|
+
if (!initData.device_auth_id || !initData.user_code) throw new OAuthError("Device authorization response missing required fields", { kind: "validation" });
|
|
191
|
+
const userCode = initData.user_code;
|
|
192
|
+
const pollIntervalMs = (typeof initData.interval === "number" ? initData.interval : parseInt(String(initData.interval ?? "5"), 10) || 5) * 1e3 + DEVICE_POLL_SAFETY_MARGIN_MS;
|
|
193
|
+
ctrl.onAuth?.({
|
|
194
|
+
url: DEVICE_AUTH_URL,
|
|
195
|
+
instructions: `Enter code: ${userCode}`
|
|
196
|
+
});
|
|
197
|
+
ctrl.onProgress?.(`Waiting for browser authorization (code: ${userCode})…`);
|
|
198
|
+
for (let poll = 0; poll < DEVICE_MAX_POLLS; poll++) {
|
|
199
|
+
await Bun.sleep(poll === 0 ? Math.min(pollIntervalMs, DEVICE_POLL_INTERVAL_MS) : pollIntervalMs);
|
|
200
|
+
if (ctrl.signal?.aborted) throw new LoginCancelledError("Device authorization cancelled");
|
|
201
|
+
const pollResponse = await fetch(DEVICE_TOKEN_URL, {
|
|
202
|
+
method: "POST",
|
|
203
|
+
headers: { "Content-Type": "application/json" },
|
|
204
|
+
body: JSON.stringify({
|
|
205
|
+
device_auth_id: initData.device_auth_id,
|
|
206
|
+
user_code: userCode
|
|
207
|
+
}),
|
|
208
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS)
|
|
209
|
+
});
|
|
210
|
+
if (pollResponse.status === 403 || pollResponse.status === 404) continue;
|
|
211
|
+
if (!pollResponse.ok) throw new OAuthError(`Device token polling failed: ${pollResponse.status}`, {
|
|
212
|
+
kind: "polling",
|
|
213
|
+
status: pollResponse.status
|
|
214
|
+
});
|
|
215
|
+
const pollData = await pollResponse.json();
|
|
216
|
+
if (!pollData.authorization_code || !pollData.code_verifier) throw new OAuthError("Device token response missing authorization_code or code_verifier", { kind: "validation" });
|
|
217
|
+
ctrl.onProgress?.("Exchanging authorization code for tokens…");
|
|
218
|
+
return exchangeCodeForToken(pollData.authorization_code, pollData.code_verifier, DEVICE_REDIRECT_URI);
|
|
219
|
+
}
|
|
220
|
+
throw new OAuthError("Device authorization timed out — user did not complete login in time", { kind: "timeout" });
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Refresh OpenAI Codex OAuth token
|
|
224
|
+
*/
|
|
225
|
+
async function refreshOpenAICodexToken(refreshToken) {
|
|
226
|
+
const response = await fetch(TOKEN_URL, {
|
|
227
|
+
method: "POST",
|
|
228
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
229
|
+
body: new URLSearchParams({
|
|
230
|
+
grant_type: "refresh_token",
|
|
231
|
+
refresh_token: refreshToken,
|
|
232
|
+
client_id: CLIENT_ID
|
|
233
|
+
}),
|
|
234
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS)
|
|
235
|
+
});
|
|
236
|
+
if (!response.ok) {
|
|
237
|
+
const bodyText = await response.text();
|
|
238
|
+
throw new OAuthError(`OpenAI Codex token refresh failed: ${formatOpenAICodexTokenEndpointError(response.status, bodyText)}`, {
|
|
239
|
+
kind: "token-refresh",
|
|
240
|
+
status: response.status
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
const tokenData = await response.json();
|
|
244
|
+
if (!tokenData.access_token || !tokenData.refresh_token || typeof tokenData.expires_in !== "number") throw new OAuthError("Token response missing required fields", { kind: "validation" });
|
|
245
|
+
const { accountId, email } = getTokenProfile(tokenData.access_token);
|
|
246
|
+
return {
|
|
247
|
+
access: tokenData.access_token,
|
|
248
|
+
refresh: tokenData.refresh_token || refreshToken,
|
|
249
|
+
expires: Date.now() + tokenData.expires_in * 1e3,
|
|
250
|
+
accountId: accountId ?? void 0,
|
|
251
|
+
email
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
//#endregion
|
|
255
|
+
export { loginOpenAICodex, loginOpenAICodexDevice, refreshOpenAICodexToken };
|