@xiaoso/dsh-tool-plus 0.1.16 → 0.1.17-beta.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/lib/{abort-DZ54CaKU.mjs → abort-BTblp0Rt.mjs} +2 -2
- package/lib/{alibaba-coding-plan-DODpkcqi.mjs → alibaba-coding-plan-t3R4YKCB.mjs} +3 -3
- package/lib/{alibaba-token-plan-C6lpKCda.mjs → alibaba-token-plan-CO4djG1y.mjs} +4 -4
- package/lib/{alibaba-token-plan-CHGyWOfX.mjs → alibaba-token-plan-CUjYJX0-.mjs} +1 -1
- package/lib/{anthropic-Dv_GGX6g.mjs → anthropic-nf7w6so1.mjs} +48 -21
- package/lib/{api-key-validation-MnMf_ihX.mjs → api-key-validation-C_tZ7Y9a.mjs} +2 -2
- package/lib/behavior-BwvTUtag.mjs +111 -0
- package/lib/{browser-render-BZXt548F.mjs → browser-render-BQaEVK_n.mjs} +4 -4
- package/lib/{reference-CtvM4zLU.mjs → bundled-references-CrQ4dQcT.mjs} +57 -4
- package/lib/cascade-BWjRTD1q.mjs +30265 -0
- package/lib/{cline-pass-BT9CBEOj.mjs → cline-pass-Bjm-O8GG.mjs} +2 -2
- package/lib/cloudflare-CKXRJ0l9.mjs +37 -0
- package/lib/{cloudflare-ai-gateway-DReN6ohJ.mjs → cloudflare-ai-gateway-BpnBSjwV.mjs} +8 -4
- package/lib/cloudflare-workers-ai-odj9XswL.mjs +32 -0
- package/lib/collapse-_nkb2dn7.mjs +1945 -0
- package/lib/{coreweave-C82HrLpb.mjs → coreweave-CGDZOD8-.mjs} +1 -1
- package/lib/{coreweave-C68UgRQO.mjs → coreweave-DwffcgW5.mjs} +4 -4
- package/lib/cursor-BgOuV7Qf.mjs +794 -0
- package/lib/{cursor-_gHHq-Dq.mjs → cursor-CF1iHHbv.mjs} +68 -19
- package/lib/{cursor-proto-Cr_WEMuv.mjs → cursor-proto-BuNTjX3L.mjs} +823 -23
- package/lib/{devin-QpfTSl9H.mjs → devin-7OEeEb80.mjs} +112 -31
- package/lib/{devin-proto-DYhp4Ty6.mjs → devin-proto-C-QreeAv.mjs} +71 -5
- package/lib/{dom-DG3sPESc.mjs → dom-BmbZv4v9.mjs} +598 -182
- package/lib/{error-O63pC8zg.mjs → error-8LVo7tug.mjs} +725 -104
- package/lib/factory-droid-ChhmsGWb.mjs +71 -0
- package/lib/{fs-promises-shim-B2YFWkcd.mjs → fs-promises-shim-CANAxFWh.mjs} +10 -1
- package/lib/{github-copilot-BMEcw8N1.mjs → github-copilot-QwevdT30.mjs} +4 -4
- package/lib/{github-copilot-headers-DnjGEbko.mjs → github-copilot-headers-CAUZ0rUD.mjs} +138 -26
- package/lib/{gitlab-duo-DnHqJjFY.mjs → gitlab-duo-DigV7k_o.mjs} +2 -2
- package/lib/{google-antigravity-B1mSckrK.mjs → google-antigravity-D-oe0r8A.mjs} +6 -7
- package/lib/{google-gemini-cli-DJwThCCn.mjs → google-gemini-cli-CQBHgGJT.mjs} +5 -6
- package/lib/{google-oauth-shared-BctLpVUN.mjs → google-oauth-shared-CvgVIUwJ.mjs} +2 -2
- package/lib/google-validation-CdvRj4ec.mjs +126 -0
- package/lib/id-EJwVLDHE.mjs +2548 -0
- package/lib/index.mjs +248 -258
- package/lib/{kilo-BEgEwMJQ.mjs → kilo-5r6OffCa.mjs} +2 -2
- package/lib/{kimi-T9NldEIJ.mjs → kimi-DOOtbXmm.mjs} +9 -10
- package/lib/{launch-BIWNyrBB.mjs → launch-DkAjib7h.mjs} +1 -1
- package/lib/{markit-Dvp2JPdT.mjs → markit-CCcyeAds.mjs} +359 -140
- package/lib/{models-DnXMYGcJ.mjs → models-DCpDT_im.mjs} +199203 -112275
- package/lib/{muse-code-CNvqqAp6.mjs → muse-code-CYf8zj_B.mjs} +5 -5
- package/lib/{openai-codex-D5-CyTxa.mjs → openai-codex-Cj-TvSFh.mjs} +13 -10
- package/lib/{perplexity-A2PEoGaC.mjs → perplexity-Dh_Qgsmx.mjs} +68 -25
- package/lib/{pkce-BYjmugmw.mjs → pkce-DO-jBJLV.mjs} +1 -1
- package/lib/pricing-DusBEhwm.mjs +593 -0
- package/lib/{readability-uGhqb4GC.mjs → readability-XHK0o3bv.mjs} +287 -27
- package/lib/{scrapers-C1ujjV8k.mjs → scrapers-B6PADOVm.mjs} +13 -13
- package/lib/slugify-ynYWBIA5.mjs +11 -0
- package/lib/snowflake-B_jap6lz.mjs +666 -0
- package/lib/snowflake-Cu1ZyOp6.mjs +151 -0
- package/lib/{src-AELUUb5U.mjs → src-BhinCSpi.mjs} +1172 -458
- package/lib/{stream-AVOoe5Hj.mjs → stream-D2ivg3GH.mjs} +3740 -5311
- package/lib/{turndown-D6fof1OX.mjs → turndown-Vya8SavL.mjs} +82 -30
- package/lib/types/tools/hashline/native/index.d.ts +2 -2
- package/lib/types/tools/hashline/native/policy.d.ts +19 -21
- package/lib/types/tools/omp/session/agent-storage.d.ts +3 -3
- package/lib/types/tools/shared/bun-named-shim.d.ts +5 -0
- package/lib/{types-BLrLohwK.mjs → types-tH1v9uRm.mjs} +2 -2
- package/lib/{utils-cZX3f_Ls.mjs → utils-C_Ncya1r.mjs} +1737 -509
- package/lib/utils-D2tK38xq.mjs +34006 -0
- package/lib/{utils-I4I8azHN.mjs → utils-DrDwmNtu.mjs} +8 -3
- package/lib/{xai-oauth-CbeldUSV.mjs → xai-oauth-DLLzX6my.mjs} +4 -4
- package/lib/{xiaomi-Dl3VSs04.mjs → xiaomi-CqK-KivK.mjs} +2 -2
- package/lib/{zai-FyjGSypr.mjs → zai-DoJ2xPIH.mjs} +2 -2
- package/lib/{zip-DRrycScC.mjs → zip-DG1mUV6X.mjs} +1 -1
- package/package.json +8 -8
- package/lib/amazon-bedrock-Dx0rr9dY.mjs +0 -996
- package/lib/anthropic-ClCw0PV_.mjs +0 -3624
- package/lib/anthropic-wire-lbGAFzYb.mjs +0 -927
- package/lib/azure-openai-responses-WE_bgFWp.mjs +0 -285
- package/lib/bundled-references-DY3EJxZV.mjs +0 -56
- package/lib/cloudflare-ai-gateway-CrMa97Ap.mjs +0 -44
- package/lib/collapse-CkMNRNr7.mjs +0 -2304
- package/lib/cursor-C6jaIJDg.mjs +0 -283
- package/lib/cursor-D-XZYDf_.mjs +0 -4544
- package/lib/deterministic-id-CUEIMSqR.mjs +0 -17
- package/lib/devin-ExGIghmx.mjs +0 -69
- package/lib/devin-u0ljw5P6.mjs +0 -628
- package/lib/effort-BdtQMN-_.mjs +0 -23
- package/lib/event-stream-BKEtb45Y.mjs +0 -167
- package/lib/gemini-headers-Co9cVvex.mjs +0 -54
- package/lib/google-auth-qAN-6DMW.mjs +0 -232
- package/lib/google-gemini-cli-B2Aw1S5w.mjs +0 -844
- package/lib/google-lTxuq28n.mjs +0 -38
- package/lib/google-shared-Y01j9xOr.mjs +0 -856
- package/lib/google-validation-BEkNs-Bx.mjs +0 -18
- package/lib/google-vertex-DcM-FRC5.mjs +0 -99
- package/lib/hosts-00rjRzUD.mjs +0 -203
- package/lib/id-AL5obQzR.mjs +0 -71
- package/lib/idle-iterator-D0ZXEcUq.mjs +0 -366
- package/lib/ollama-6R0oXZks.mjs +0 -574
- package/lib/openai-codex-responses-C8bnBWOe.mjs +0 -3232
- package/lib/openai-completions-DNr3mUsn.mjs +0 -1430
- package/lib/openai-configuration-update-xMYUjaK_.mjs +0 -150
- package/lib/openai-reasoning-fallback-Ju-5pSR8.mjs +0 -380
- package/lib/openai-responses-CjpafAEr.mjs +0 -861
- package/lib/protobuf-BMgDYu_U.mjs +0 -717
- package/lib/provider-response-SqKSkkUc.mjs +0 -22
- package/lib/proxy-DxvCQOCA.mjs +0 -230
- package/lib/request-debug-BwPOqN_j.mjs +0 -183
- package/lib/sanitize-text-BCf5KmCf.mjs +0 -33
- package/lib/sse-debug-B-tkLCPW.mjs +0 -11
- package/lib/stream-markup-healing-CN3RsqG9.mjs +0 -267
- package/lib/strict-tool-validation-CBTz1dAA.mjs +0 -105
- package/lib/taxonomy-DSIM99rS.mjs +0 -15392
- package/lib/thinking-ngMwf_1Y.mjs +0 -336
- package/lib/type-guards-CttAANEx.mjs +0 -11
- package/lib/utils-CE3DSebh.mjs +0 -11763
- package/lib/vision-guard-BCpXWUj3.mjs +0 -77
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
import { installBunShim as __installBunShim } from "./bun-shim.mjs";
|
|
2
|
+
__installBunShim();
|
|
3
|
+
import { H as $env, k as warn } from "./src-BhinCSpi.mjs";
|
|
4
|
+
import { _ as LoginCancelledError, h as OAuthError, t as ConfigurationError } from "./error-8LVo7tug.mjs";
|
|
5
|
+
import { i as auth } from "./cascade-BWjRTD1q.mjs";
|
|
6
|
+
import { NativeOAuthCallback } from "@oh-my-pi/pi-natives";
|
|
7
|
+
import * as os from "node:os";
|
|
8
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-catalog@18.12.6/node_modules/@oh-my-pi/pi-catalog/src/compat/auth.ts
|
|
9
|
+
/**
|
|
10
|
+
* Typed accessors over the compiled auth stratum (`rules/auth/*.kdl`): per
|
|
11
|
+
* provider display metadata, env-var fallbacks and the declarative login /
|
|
12
|
+
* refresh flow that `@oh-my-pi/pi-ai`'s registry engines interpret.
|
|
13
|
+
*/
|
|
14
|
+
const providers = auth.providers;
|
|
15
|
+
const byId = Object.fromEntries(providers.map((p) => [p.id, p]));
|
|
16
|
+
/** Every compiled auth provider in `/login` display order. */
|
|
17
|
+
function authProviders() {
|
|
18
|
+
return providers;
|
|
19
|
+
}
|
|
20
|
+
/** The compiled auth policy for one provider id, if declared. */
|
|
21
|
+
function authPolicyFor(provider) {
|
|
22
|
+
return byId[provider];
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/registry/oauth/native-scheme-callback.ts
|
|
26
|
+
/** Connect OAuth's AbortSignals to the native, recoverable desktop callback receiver. */
|
|
27
|
+
async function createNativeSchemeCallbackReceiver(scheme, options = {}) {
|
|
28
|
+
options.signal?.throwIfAborted();
|
|
29
|
+
const receiver = new NativeOAuthCallback({ scheme });
|
|
30
|
+
const cancel = () => receiver.cancel();
|
|
31
|
+
const detach = () => options.signal?.removeEventListener("abort", cancel);
|
|
32
|
+
options.signal?.addEventListener("abort", cancel, { once: true });
|
|
33
|
+
try {
|
|
34
|
+
if (!await receiver.start()) {
|
|
35
|
+
await receiver.dispose();
|
|
36
|
+
detach();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
options.signal?.throwIfAborted();
|
|
40
|
+
} catch (error) {
|
|
41
|
+
try {
|
|
42
|
+
await receiver.dispose();
|
|
43
|
+
} catch (cleanupError) {
|
|
44
|
+
throw new AggregateError([error, cleanupError], "Native OAuth setup and recovery both failed");
|
|
45
|
+
} finally {
|
|
46
|
+
detach();
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
async dispose() {
|
|
52
|
+
try {
|
|
53
|
+
await receiver.dispose();
|
|
54
|
+
} finally {
|
|
55
|
+
detach();
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
async waitForCallback(signal, timeoutMs) {
|
|
59
|
+
signal?.throwIfAborted();
|
|
60
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
61
|
+
try {
|
|
62
|
+
return await receiver.waitForCallback(timeoutMs);
|
|
63
|
+
} finally {
|
|
64
|
+
signal?.removeEventListener("abort", cancel);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/registry/oauth/oauth.html
|
|
71
|
+
var oauth_default = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta name=\"color-scheme\" content=\"dark light\" />\n <title>omp · authentication</title>\n <style>\n :root {\n --bg: #000;\n --card: #0b0b0d;\n --ink-1: #f4f4f5;\n --ink-2: #b4b4bb;\n --ink-3: #7a7a83;\n --line-1: rgb(255 255 255 / 0.07);\n --line-2: rgb(255 255 255 / 0.12);\n --top-lit: rgb(255 255 255 / 0.22);\n --accent-a: #22d3ee;\n --accent-b: #f472b6;\n --accent-gradient: linear-gradient(135deg, var(--accent-a), var(--accent-b));\n --ok: #4ade80;\n --ok-soft: rgb(74 222 128 / 0.12);\n --bad: #f87171;\n --bad-soft: rgb(248 113 113 / 0.12);\n --pending: #a1a1aa;\n --pending-soft: rgb(161 161 170 / 0.12);\n --glow-a: rgb(34 211 238 / 0.1);\n --glow-b: rgb(244 114 182 / 0.08);\n --shadow: 0 30px 60px -20px rgb(0 0 0 / 0.8);\n --btn-bg: #f4f4f5;\n --btn-fg: #000;\n --r-control: 8px;\n --r-panel: 16px;\n --sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n color-scheme: dark;\n }\n\n @media (prefers-color-scheme: light) {\n :root {\n --bg: #fafafa;\n --card: #fff;\n --ink-1: #111113;\n --ink-2: #4b4b52;\n --ink-3: #83838b;\n --line-1: rgb(0 0 0 / 0.08);\n --line-2: rgb(0 0 0 / 0.12);\n --top-lit: rgb(0 0 0 / 0.04);\n --accent-a: #0891b2;\n --accent-b: #db2777;\n --ok: #16a34a;\n --ok-soft: rgb(22 163 74 / 0.1);\n --bad: #dc2626;\n --bad-soft: rgb(220 38 38 / 0.1);\n --pending: #71717a;\n --pending-soft: rgb(113 113 122 / 0.1);\n --glow-a: rgb(8 145 178 / 0.08);\n --glow-b: rgb(219 39 119 / 0.06);\n --shadow: 0 20px 50px -24px rgb(0 0 0 / 0.18);\n --btn-bg: #111113;\n --btn-fg: #fff;\n color-scheme: light;\n }\n }\n\n * {\n box-sizing: border-box;\n }\n\n body {\n margin: 0;\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1.5rem;\n font-family: var(--sans);\n background: var(--bg);\n color: var(--ink-1);\n -webkit-font-smoothing: antialiased;\n overflow-x: clip;\n }\n\n /* ambient glow */\n .glow {\n position: fixed;\n inset: 0;\n pointer-events: none;\n background:\n radial-gradient(ellipse 50vw 40vh at 38% 30%, var(--glow-a), transparent 70%),\n radial-gradient(ellipse 50vw 40vh at 64% 64%, var(--glow-b), transparent 70%);\n }\n\n .container {\n position: relative;\n width: 100%;\n max-width: 400px;\n padding: 2rem 2rem 1.75rem;\n background: var(--card);\n border: 1px solid var(--line-1);\n border-radius: var(--r-panel);\n box-shadow: var(--shadow);\n overflow: hidden;\n animation: rise 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;\n }\n /* top-lit hairline */\n .container::before {\n content: \"\";\n position: absolute;\n top: 0;\n left: 12%;\n right: 12%;\n height: 1px;\n background: linear-gradient(90deg, transparent, var(--top-lit), transparent);\n }\n @keyframes rise {\n from {\n opacity: 0;\n transform: translateY(8px);\n }\n }\n\n .brand {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n margin-bottom: 1.75rem;\n }\n .omp-mark {\n flex: none;\n display: block;\n }\n .wordmark {\n font-size: 13px;\n font-weight: 600;\n letter-spacing: -0.01em;\n }\n .wordmark-sub {\n margin-left: auto;\n font-family: var(--mono);\n font-size: 12px;\n color: var(--ink-3);\n }\n\n .status {\n display: inline-flex;\n align-items: center;\n gap: 0.45rem;\n padding: 0.22rem 0.6rem 0.22rem 0.5rem;\n margin-bottom: 1rem;\n border-radius: 999px;\n font-size: 12px;\n font-weight: 500;\n color: var(--pending);\n background: var(--pending-soft);\n }\n .status-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: currentColor;\n }\n .status-word {\n display: none;\n }\n .container:not(.success):not(.error) .status-pending,\n .success .status-success,\n .error .status-error {\n display: inline;\n }\n .container:not(.success):not(.error) .status-dot {\n animation: pulse 1.4s ease-in-out infinite;\n }\n @keyframes pulse {\n 50% {\n opacity: 0.35;\n }\n }\n .success .status {\n color: var(--ok);\n background: var(--ok-soft);\n }\n .error .status {\n color: var(--bad);\n background: var(--bad-soft);\n }\n\n h1 {\n margin: 0 0 0.5rem;\n font-size: 1.35rem;\n font-weight: 600;\n letter-spacing: -0.02em;\n line-height: 1.25;\n text-wrap: balance;\n }\n p {\n margin: 0 0 1.5rem;\n font-size: 0.9rem;\n line-height: 1.6;\n color: var(--ink-2);\n }\n p:last-of-type:not(:has(+ .btn)) {\n margin-bottom: 0.25rem;\n }\n .error p {\n font-family: var(--mono);\n font-size: 0.8rem;\n padding: 0.6rem 0.75rem;\n border: 1px solid var(--line-1);\n border-radius: var(--r-control);\n overflow-wrap: anywhere;\n }\n\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n padding: 0.6rem 1rem;\n font: inherit;\n font-size: 0.875rem;\n font-weight: 500;\n color: var(--btn-fg);\n background: var(--btn-bg);\n border: none;\n border-radius: var(--r-control);\n cursor: pointer;\n transition: opacity 0.15s;\n }\n .btn:hover {\n opacity: 0.86;\n }\n .btn:focus-visible {\n outline: 2px solid var(--accent-a);\n outline-offset: 2px;\n }\n .error .btn {\n color: var(--ink-1);\n background: transparent;\n border: 1px solid var(--line-2);\n }\n\n /* auto-close progress */\n .timer {\n position: absolute;\n left: 0;\n bottom: 0;\n height: 2px;\n width: 100%;\n background: var(--accent-gradient);\n transform-origin: left;\n transform: scaleX(0);\n }\n .countdown .timer {\n animation: countdown 3s linear forwards;\n }\n @keyframes countdown {\n from {\n transform: scaleX(1);\n }\n to {\n transform: scaleX(0);\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .container,\n .countdown .timer,\n .status-dot {\n animation: none !important;\n }\n }\n </style>\n </head>\n <body>\n <span aria-hidden=\"true\" class=\"glow\"></span>\n <div id=\"app\" class=\"container\">\n <div class=\"brand\">\n <svg class=\"omp-mark\" viewBox=\"0 0 64 64\" width=\"24\" height=\"24\" aria-hidden=\"true\">\n <defs>\n <linearGradient id=\"omp-mark-grad\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">\n <stop offset=\"0\" stop-color=\"oklch(0.7 0.24 340)\" />\n <stop offset=\".5\" stop-color=\"oklch(0.62 0.21 295)\" />\n <stop offset=\"1\" stop-color=\"oklch(0.81 0.14 200)\" />\n </linearGradient>\n </defs>\n <path fill=\"url(#omp-mark-grad)\" d=\"M10 14h44v9H43v33h-9V23h-9v22h-9V23H10z\" />\n </svg>\n <span class=\"wordmark\">omp</span>\n <span class=\"wordmark-sub\">oauth</span>\n </div>\n\n <div class=\"status\" role=\"status\">\n <span aria-hidden=\"true\" class=\"status-dot\"></span>\n <span class=\"status-word status-pending\">Waiting</span>\n <span class=\"status-word status-success\">Connected</span>\n <span class=\"status-word status-error\">Failed</span>\n </div>\n\n <h1 id=\"title\">Authentication</h1>\n <p id=\"message\"></p>\n <button onclick=\"window.close()\" class=\"btn\">Close window</button>\n <span aria-hidden=\"true\" class=\"timer\"></span>\n </div>\n\n <script id=\"server-state\" type=\"application/json\">\n __OAUTH_STATE__\n <\/script>\n\n <script>\n let serverState;\n try {\n serverState = JSON.parse(document.getElementById(\"server-state\").textContent);\n } catch {\n const params = new URLSearchParams(window.location.search);\n serverState = {\n ok: params.get(\"ok\") === \"1\",\n error: params.get(\"error\") || \"Authentication failed\",\n };\n }\n\n const app = document.getElementById(\"app\");\n const title = document.getElementById(\"title\");\n const message = document.getElementById(\"message\");\n\n if (serverState.ok) {\n const closeButton = document.querySelector(\".btn\");\n app.classList.add(\"success\", \"countdown\");\n title.textContent = \"Authentication successful\";\n message.textContent = \"You have successfully logged in.\";\n window.close();\n setTimeout(() => {\n app.classList.remove(\"countdown\");\n closeButton.remove();\n message.innerHTML = \"You have successfully logged in.<br>Please close this tab manually.\";\n }, 300);\n } else {\n app.classList.add(\"error\");\n title.textContent = \"Authentication failed\";\n message.textContent = serverState.error || \"An error occurred\";\n }\n <\/script>\n </body>\n</html>\n";
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/registry/oauth/callback-server.ts
|
|
74
|
+
/**
|
|
75
|
+
* Abstract base class for OAuth flows with local callback servers.
|
|
76
|
+
*
|
|
77
|
+
* Handles:
|
|
78
|
+
* - Port allocation (tries expected port, falls back to random)
|
|
79
|
+
* - Callback server setup and request handling
|
|
80
|
+
* - Common OAuth flow logic
|
|
81
|
+
*
|
|
82
|
+
* Providers extend this and implement:
|
|
83
|
+
* - generateAuthUrl(): Build provider-specific authorization URL
|
|
84
|
+
* - exchangeToken(): Exchange authorization code for tokens
|
|
85
|
+
*/
|
|
86
|
+
const DEFAULT_TIMEOUT = 3e5;
|
|
87
|
+
const DEFAULT_HOSTNAME = "localhost";
|
|
88
|
+
const CALLBACK_PATH = "/callback";
|
|
89
|
+
const IPV4_LOOPBACK = "127.0.0.1";
|
|
90
|
+
const IPV6_LOOPBACK = "::1";
|
|
91
|
+
/**
|
|
92
|
+
* How many times a random-port bind may be redrawn when the ephemeral port it
|
|
93
|
+
* landed on is already held on {@link IPV6_LOOPBACK} by that exact address.
|
|
94
|
+
* Small on purpose: each redraw picks a fresh port, so a repeat collision is
|
|
95
|
+
* vanishingly unlikely.
|
|
96
|
+
*/
|
|
97
|
+
const IPV6_COMPANION_ATTEMPTS = 4;
|
|
98
|
+
/**
|
|
99
|
+
* Path served by {@link OAuthCallbackFlow} that 302-redirects to the pending
|
|
100
|
+
* authorization URL. Kept out of {@link OAuthCallbackFlowOptions} because it
|
|
101
|
+
* lives on the loopback callback server alongside {@link CALLBACK_PATH} and
|
|
102
|
+
* must never clash with a provider-registered redirect URI (all known
|
|
103
|
+
* providers register `/callback`-shaped paths).
|
|
104
|
+
*/
|
|
105
|
+
const LAUNCH_PATH = "/launch";
|
|
106
|
+
/**
|
|
107
|
+
* Whether a failed bind means "another process already holds this port".
|
|
108
|
+
* Bun surfaces `EADDRINUSE` on the error's `code` where the platform reports
|
|
109
|
+
* it, and otherwise only in the message, so both are checked.
|
|
110
|
+
*/
|
|
111
|
+
function isAddressInUse(error) {
|
|
112
|
+
const code = error?.code;
|
|
113
|
+
if (typeof code === "string") return code === "EADDRINUSE";
|
|
114
|
+
return error instanceof Error && /EADDRINUSE|in use/i.test(error.message);
|
|
115
|
+
}
|
|
116
|
+
/** Keep JSON data inside the template's script element; JSON.parse restores the original text. */
|
|
117
|
+
function serializeCallbackState(state) {
|
|
118
|
+
return JSON.stringify(state).replaceAll("<", "\\u003c");
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Whether this host exposes an IPv6 loopback (`::1`) the companion listener can
|
|
122
|
+
* bind. A kernel with IPv6 disabled (`ipv6.disable=1`) lists no internal IPv6
|
|
123
|
+
* address, and the `::1` companion bind there fails with a generic Bun error
|
|
124
|
+
* {@link isAddressInUse} cannot distinguish from a real collision — Bun reuses
|
|
125
|
+
* its "Is port X in use?" message for every listen failure (oven-sh/bun#7187) —
|
|
126
|
+
* so the dual-bind path must be skipped up front rather than misread as a
|
|
127
|
+
* conflict (issue #8814).
|
|
128
|
+
*/
|
|
129
|
+
function ipv6LoopbackAvailable() {
|
|
130
|
+
const interfaces = os.networkInterfaces();
|
|
131
|
+
for (const name in interfaces) {
|
|
132
|
+
const addresses = interfaces[name];
|
|
133
|
+
if (!addresses) continue;
|
|
134
|
+
for (const address of addresses) if (address.internal && address.family === "IPv6") return true;
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
function parseNativeCallback(input, redirectUri, expectedState) {
|
|
139
|
+
let callback;
|
|
140
|
+
let expected;
|
|
141
|
+
try {
|
|
142
|
+
callback = new URL(input);
|
|
143
|
+
expected = new URL(redirectUri);
|
|
144
|
+
} catch {
|
|
145
|
+
throw new OAuthError("OAuth application callback was not a valid URL", { kind: "device-auth" });
|
|
146
|
+
}
|
|
147
|
+
if (callback.protocol !== expected.protocol || callback.host !== expected.host || callback.pathname !== expected.pathname) throw new OAuthError("OAuth application returned an unexpected callback target", { kind: "device-auth" });
|
|
148
|
+
const state = callback.searchParams.get("state") ?? "";
|
|
149
|
+
if (expectedState && state !== expectedState) throw new OAuthError("State mismatch - possible CSRF attack", { kind: "device-auth" });
|
|
150
|
+
const error = callback.searchParams.get("error");
|
|
151
|
+
if (error) {
|
|
152
|
+
const description = callback.searchParams.get("error_description") || error;
|
|
153
|
+
throw new OAuthError(`Authorization failed: ${description}`, { kind: "device-auth" });
|
|
154
|
+
}
|
|
155
|
+
const code = callback.searchParams.get("code") ?? callback.searchParams.get("authCode");
|
|
156
|
+
if (!code) throw new OAuthError("OAuth application callback omitted the authorization code", { kind: "device-auth" });
|
|
157
|
+
return {
|
|
158
|
+
code,
|
|
159
|
+
state
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Abstract base class for OAuth flows with local callback servers.
|
|
164
|
+
*/
|
|
165
|
+
var OAuthCallbackFlow = class {
|
|
166
|
+
ctrl;
|
|
167
|
+
preferredPort;
|
|
168
|
+
callbackPath;
|
|
169
|
+
callbackHostname;
|
|
170
|
+
redirectUri;
|
|
171
|
+
allowPortFallback;
|
|
172
|
+
#manualInputOnly;
|
|
173
|
+
#nativeScheme;
|
|
174
|
+
#callbackResolve;
|
|
175
|
+
#callbackReject;
|
|
176
|
+
/**
|
|
177
|
+
* Authorization URL the `/launch` route currently redirects to. Set by
|
|
178
|
+
* {@link login} after {@link generateAuthUrl} and before {@link OAuthController.onAuth}
|
|
179
|
+
* fires, cleared when the server stops. `undefined` before the flow reaches
|
|
180
|
+
* that point and after it finishes, so `/launch` returns 503 rather than
|
|
181
|
+
* a stale URL.
|
|
182
|
+
*/
|
|
183
|
+
#pendingAuthUrl;
|
|
184
|
+
constructor(ctrl, preferredPortOrOptions, callbackPath = CALLBACK_PATH) {
|
|
185
|
+
this.ctrl = ctrl;
|
|
186
|
+
if (typeof preferredPortOrOptions === "number") {
|
|
187
|
+
this.preferredPort = preferredPortOrOptions;
|
|
188
|
+
this.callbackPath = callbackPath;
|
|
189
|
+
this.callbackHostname = DEFAULT_HOSTNAME;
|
|
190
|
+
this.allowPortFallback = true;
|
|
191
|
+
this.#manualInputOnly = false;
|
|
192
|
+
this.#nativeScheme = false;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
this.preferredPort = preferredPortOrOptions.preferredPort;
|
|
196
|
+
this.callbackPath = preferredPortOrOptions.callbackPath ?? CALLBACK_PATH;
|
|
197
|
+
this.callbackHostname = preferredPortOrOptions.callbackHostname ?? DEFAULT_HOSTNAME;
|
|
198
|
+
this.redirectUri = preferredPortOrOptions.redirectUri;
|
|
199
|
+
this.allowPortFallback = preferredPortOrOptions.allowPortFallback ?? true;
|
|
200
|
+
this.#manualInputOnly = preferredPortOrOptions.manualInputOnly ?? false;
|
|
201
|
+
this.#nativeScheme = preferredPortOrOptions.nativeScheme ?? false;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Generate CSRF state token. Override if provider needs custom state generation.
|
|
205
|
+
*/
|
|
206
|
+
generateState() {
|
|
207
|
+
const bytes = /* @__PURE__ */ new Uint8Array(16);
|
|
208
|
+
crypto.getRandomValues(bytes);
|
|
209
|
+
return Array.from(bytes).map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
210
|
+
}
|
|
211
|
+
#loginCancelledError() {
|
|
212
|
+
return new LoginCancelledError(`OAuth callback cancelled: ${this.ctrl.signal?.reason}`);
|
|
213
|
+
}
|
|
214
|
+
#throwIfCancelled() {
|
|
215
|
+
if (this.ctrl.signal?.aborted) throw this.#loginCancelledError();
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Execute the OAuth login flow.
|
|
219
|
+
*/
|
|
220
|
+
async login() {
|
|
221
|
+
const state = this.generateState();
|
|
222
|
+
this.#throwIfCancelled();
|
|
223
|
+
const { server, redirectUri, launchUrl } = this.#manualInputOnly ? {
|
|
224
|
+
server: void 0,
|
|
225
|
+
redirectUri: this.#buildRedirectUri(),
|
|
226
|
+
launchUrl: void 0
|
|
227
|
+
} : await this.#startCallbackServer(state);
|
|
228
|
+
const receiverAbort = new AbortController();
|
|
229
|
+
let receiver;
|
|
230
|
+
let nativeCallback;
|
|
231
|
+
try {
|
|
232
|
+
this.#throwIfCancelled();
|
|
233
|
+
if (this.#nativeScheme) {
|
|
234
|
+
const redirect = new URL(redirectUri);
|
|
235
|
+
if (redirect.protocol !== "http:" && redirect.protocol !== "https:") try {
|
|
236
|
+
receiver = await createNativeSchemeCallbackReceiver(redirect.protocol.slice(0, -1), { signal: this.ctrl.signal });
|
|
237
|
+
} catch (error) {
|
|
238
|
+
this.#throwIfCancelled();
|
|
239
|
+
this.#reportNativeWarning("Native OAuth callback setup failed; paste the authorization code instead", error);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
this.#throwIfCancelled();
|
|
243
|
+
if (receiver) {
|
|
244
|
+
nativeCallback = receiver.waitForCallback(receiverAbort.signal).then((input) => parseNativeCallback(input, redirectUri, state));
|
|
245
|
+
nativeCallback.catch(() => void 0);
|
|
246
|
+
}
|
|
247
|
+
const { url: authUrl, instructions } = await this.generateAuthUrl(state, redirectUri);
|
|
248
|
+
this.#throwIfCancelled();
|
|
249
|
+
this.#pendingAuthUrl = authUrl;
|
|
250
|
+
this.ctrl.onAuth?.({
|
|
251
|
+
url: authUrl,
|
|
252
|
+
launchUrl,
|
|
253
|
+
instructions
|
|
254
|
+
});
|
|
255
|
+
this.#throwIfCancelled();
|
|
256
|
+
this.ctrl.onProgress?.(receiver || !this.#manualInputOnly ? "Waiting for browser authentication..." : "Waiting for pasted authorization code...");
|
|
257
|
+
this.#throwIfCancelled();
|
|
258
|
+
let callback;
|
|
259
|
+
try {
|
|
260
|
+
callback = await this.#waitForCallback(state, nativeCallback);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
this.#throwIfCancelled();
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
this.#throwIfCancelled();
|
|
266
|
+
receiverAbort.abort("OAuth callback received");
|
|
267
|
+
await nativeCallback?.catch(() => void 0);
|
|
268
|
+
this.ctrl.onProgress?.("Exchanging authorization code for tokens...");
|
|
269
|
+
this.#throwIfCancelled();
|
|
270
|
+
return await this.exchangeToken(callback.code, state, redirectUri);
|
|
271
|
+
} finally {
|
|
272
|
+
this.#pendingAuthUrl = void 0;
|
|
273
|
+
receiverAbort.abort("OAuth login finished");
|
|
274
|
+
await nativeCallback?.catch(() => void 0);
|
|
275
|
+
if (receiver) try {
|
|
276
|
+
await receiver.dispose();
|
|
277
|
+
} catch (error) {
|
|
278
|
+
this.#reportNativeWarning("Native OAuth callback cleanup failed; recovery information was retained", error);
|
|
279
|
+
}
|
|
280
|
+
server?.stop();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Surface native callback failures without allowing reporting hooks to
|
|
285
|
+
* replace the login result they are meant to describe.
|
|
286
|
+
*/
|
|
287
|
+
#reportNativeWarning(message, error) {
|
|
288
|
+
const warning = `${message}: ${error instanceof Error ? error.message : String(error)}`;
|
|
289
|
+
warn(warning, { error });
|
|
290
|
+
try {
|
|
291
|
+
this.ctrl.onProgress?.(warning);
|
|
292
|
+
} catch (progressError) {
|
|
293
|
+
warn("OAuth progress callback failed while reporting a native callback warning", { error: progressError });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
#buildRedirectUri() {
|
|
297
|
+
return this.redirectUri ?? `http://${this.callbackHostname}:${this.preferredPort}${this.callbackPath}`;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Start callback server, trying preferred port first, falling back to random.
|
|
301
|
+
* `launchUrl` is `undefined` when the caller configured `callbackPath` to
|
|
302
|
+
* collide with {@link LAUNCH_PATH} — the callback handler resolves the real
|
|
303
|
+
* callback in that case, so advertising a self-redirecting URL would be
|
|
304
|
+
* incorrect.
|
|
305
|
+
*/
|
|
306
|
+
async #startCallbackServer(expectedState) {
|
|
307
|
+
try {
|
|
308
|
+
const server = this.#createServer(this.preferredPort, expectedState);
|
|
309
|
+
const actualPort = this.#resolveServerPort(server);
|
|
310
|
+
const launchUrl = this.#launchUrlIfSafe(actualPort);
|
|
311
|
+
if (this.redirectUri) return {
|
|
312
|
+
server,
|
|
313
|
+
redirectUri: this.redirectUri,
|
|
314
|
+
launchUrl
|
|
315
|
+
};
|
|
316
|
+
return {
|
|
317
|
+
server,
|
|
318
|
+
redirectUri: `http://${this.callbackHostname}:${actualPort}${this.callbackPath}`,
|
|
319
|
+
launchUrl
|
|
320
|
+
};
|
|
321
|
+
} catch (cause) {
|
|
322
|
+
if (this.redirectUri) throw new ConfigurationError(`OAuth callback port ${this.preferredPort} is in use, but oauth.redirectUri (${this.redirectUri}) requires this exact port. Free port ${this.preferredPort} (e.g. stop the process bound to it) and retry, or change oauth.redirectUri to point at an available port.`, { cause });
|
|
323
|
+
if (!this.allowPortFallback) throw new ConfigurationError(`OAuth callback port ${this.preferredPort} is in use. The OAuth provider validates redirect URIs against its registered callback, so falling back to a random port would be rejected. Free port ${this.preferredPort} (e.g. stop the process bound to it) and retry, or set oauth.callbackPort/oauth.redirectUri to a port the provider has registered.`, { cause });
|
|
324
|
+
const server = this.#createServer(0, expectedState);
|
|
325
|
+
const actualPort = this.#resolveServerPort(server);
|
|
326
|
+
const redirectUri = `http://${this.callbackHostname}:${actualPort}${this.callbackPath}`;
|
|
327
|
+
const launchUrl = this.#launchUrlIfSafe(actualPort);
|
|
328
|
+
this.ctrl.onProgress?.(`Preferred port ${this.preferredPort} unavailable, using port ${actualPort}`);
|
|
329
|
+
return {
|
|
330
|
+
server,
|
|
331
|
+
redirectUri,
|
|
332
|
+
launchUrl
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Read the numeric port a callback server bound to. `Bun.Server.port` is
|
|
338
|
+
* declared `number | undefined` because Unix-socket servers have no port,
|
|
339
|
+
* but every callback flow uses TCP; a missing port here indicates a
|
|
340
|
+
* configuration error rather than a fallback case.
|
|
341
|
+
*/
|
|
342
|
+
#resolveServerPort(server) {
|
|
343
|
+
const port = server.port;
|
|
344
|
+
if (typeof port !== "number") throw new ConfigurationError("OAuth callback server bound to a non-TCP endpoint; expected a numeric port. Check `oauth.callbackPort`/`oauth.redirectUri`.");
|
|
345
|
+
return port;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Build the `/launch` URL served by the callback server bound to `port`, or
|
|
349
|
+
* `undefined` when it must not be advertised:
|
|
350
|
+
* - the configured `callbackPath` (or a `redirectUri` whose pathname
|
|
351
|
+
* resolves to {@link LAUNCH_PATH}) would collide with the launch route;
|
|
352
|
+
* - the flow's `redirectUri` never returns to this loopback server: fixed
|
|
353
|
+
* non-loopback hosts, or custom schemes like GitLab Duo's `vscode://`
|
|
354
|
+
* URI — which `new URL` parses without complaint, so a scheme/host check
|
|
355
|
+
* is required, not just the parse failure path. Advertising a localhost
|
|
356
|
+
* `/launch` target for such flows misrepresents the callback endpoint
|
|
357
|
+
* and hands remote users a URL that resolves nowhere.
|
|
358
|
+
* Kept short (~30 chars) so UIs can advertise it as a
|
|
359
|
+
* viewport-truncation-safe copy target for the full authorization URL.
|
|
360
|
+
*/
|
|
361
|
+
#launchUrlIfSafe(port) {
|
|
362
|
+
if (this.callbackPath === LAUNCH_PATH) return void 0;
|
|
363
|
+
if (this.redirectUri) try {
|
|
364
|
+
const parsed = new URL(this.redirectUri);
|
|
365
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return void 0;
|
|
366
|
+
if (parsed.hostname !== "localhost" && parsed.hostname !== "127.0.0.1" && parsed.hostname !== "[::1]") return;
|
|
367
|
+
if (parsed.pathname === LAUNCH_PATH) return void 0;
|
|
368
|
+
} catch {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
return `http://${this.callbackHostname}:${port}${LAUNCH_PATH}`;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Create the HTTP listener(s) for the OAuth callback.
|
|
375
|
+
*
|
|
376
|
+
* `localhost` is not a single endpoint: it resolves to both
|
|
377
|
+
* {@link IPV4_LOOPBACK} and {@link IPV6_LOOPBACK}, and clients commonly try
|
|
378
|
+
* `::1` first. Binding only the IPv4 literal hands the authorization code to
|
|
379
|
+
* whatever holds the IPv6 loopback on the same port — a dev server on
|
|
380
|
+
* `*:3000` is the common case — which answers from its own routes while this
|
|
381
|
+
* flow waits out the full {@link DEFAULT_TIMEOUT}. Nothing detects it either:
|
|
382
|
+
* a specific-address bind coexists with another process's wildcard bind, so
|
|
383
|
+
* `Bun.serve` reports the port as free and the random-port fallback in
|
|
384
|
+
* {@link #startCallbackServer} never runs.
|
|
385
|
+
*
|
|
386
|
+
* Binding both loopback literals fixes the delivery rather than dodging it:
|
|
387
|
+
* the kernel routes a connection to the most specific matching bind, so our
|
|
388
|
+
* `::1` listener receives `localhost` traffic that would otherwise reach a
|
|
389
|
+
* process bound to the `::` wildcard. Both listeners answer the same routes,
|
|
390
|
+
* so which family the client resolves stops mattering.
|
|
391
|
+
*
|
|
392
|
+
* A genuine collision — another process on exactly this loopback address and
|
|
393
|
+
* port — still raises EADDRINUSE and reaches the caller's in-use policy. A
|
|
394
|
+
* host that cannot bind `::1` at all (IPv6 disabled, address unavailable) is
|
|
395
|
+
* not a collision: the IPv4 listener is the only reachable endpoint there, so
|
|
396
|
+
* it serves alone.
|
|
397
|
+
*/
|
|
398
|
+
#createServer(port, expectedState) {
|
|
399
|
+
if (this.callbackHostname !== DEFAULT_HOSTNAME) return this.#serve(this.callbackHostname, port, expectedState);
|
|
400
|
+
const dualStack = ipv6LoopbackAvailable();
|
|
401
|
+
for (let attempt = 0;; attempt++) {
|
|
402
|
+
const primary = this.#serve(IPV4_LOOPBACK, port, expectedState);
|
|
403
|
+
const boundPort = primary.port;
|
|
404
|
+
if (typeof boundPort !== "number") return primary;
|
|
405
|
+
if (!dualStack) return primary;
|
|
406
|
+
let companion;
|
|
407
|
+
try {
|
|
408
|
+
companion = this.#serve(IPV6_LOOPBACK, boundPort, expectedState);
|
|
409
|
+
} catch (cause) {
|
|
410
|
+
if (!isAddressInUse(cause)) return primary;
|
|
411
|
+
primary.stop(true);
|
|
412
|
+
if (port !== 0 || attempt >= IPV6_COMPANION_ATTEMPTS) throw cause;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
get port() {
|
|
417
|
+
return primary.port;
|
|
418
|
+
},
|
|
419
|
+
stop: (closeActiveConnections) => {
|
|
420
|
+
companion.stop(closeActiveConnections);
|
|
421
|
+
return primary.stop(closeActiveConnections);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/** Bind one loopback listener serving the callback and launch routes. */
|
|
427
|
+
#serve(hostname, port, expectedState) {
|
|
428
|
+
return Bun.serve({
|
|
429
|
+
hostname,
|
|
430
|
+
port,
|
|
431
|
+
reusePort: false,
|
|
432
|
+
fetch: (req) => this.#handleCallback(req, expectedState)
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Handle OAuth callback HTTP request. Two routes on the same loopback server:
|
|
437
|
+
* - `callbackPath` (default `/callback`) — the provider redirect target.
|
|
438
|
+
* - {@link LAUNCH_PATH} (`/launch`) — 302 to the pending authorization URL so
|
|
439
|
+
* viewport-safe copy targets can survive TUI truncation.
|
|
440
|
+
*
|
|
441
|
+
* `callbackPath` wins any collision: an OMP config that pins the provider
|
|
442
|
+
* redirect at `/launch` (via `oauth.callbackPath` or a loopback
|
|
443
|
+
* `oauth.redirectUri`) must resolve the callback normally rather than
|
|
444
|
+
* self-redirect. `#startCallbackServer` also suppresses `launchUrl` in that
|
|
445
|
+
* case, so the launch route is never advertised when it would collide.
|
|
446
|
+
*/
|
|
447
|
+
#handleCallback(req, expectedState) {
|
|
448
|
+
const url = new URL(req.url);
|
|
449
|
+
if (url.pathname !== this.callbackPath) {
|
|
450
|
+
if (url.pathname === LAUNCH_PATH) {
|
|
451
|
+
const pending = this.#pendingAuthUrl;
|
|
452
|
+
if (!pending) return new Response("OAuth launch URL is no longer active", { status: 503 });
|
|
453
|
+
return Response.redirect(pending, 302);
|
|
454
|
+
}
|
|
455
|
+
return new Response("Not Found", { status: 404 });
|
|
456
|
+
}
|
|
457
|
+
const code = url.searchParams.get("code");
|
|
458
|
+
const state = url.searchParams.get("state") || "";
|
|
459
|
+
const error = url.searchParams.get("error") || "";
|
|
460
|
+
const errorDescription = url.searchParams.get("error_description") || error;
|
|
461
|
+
let resultState;
|
|
462
|
+
if (error) resultState = {
|
|
463
|
+
ok: false,
|
|
464
|
+
error: `Authorization failed: ${errorDescription}`
|
|
465
|
+
};
|
|
466
|
+
else if (!code) resultState = {
|
|
467
|
+
ok: false,
|
|
468
|
+
error: "Missing authorization code"
|
|
469
|
+
};
|
|
470
|
+
else if (expectedState && state !== expectedState) resultState = {
|
|
471
|
+
ok: false,
|
|
472
|
+
error: "State mismatch - possible CSRF attack"
|
|
473
|
+
};
|
|
474
|
+
else resultState = {
|
|
475
|
+
ok: true,
|
|
476
|
+
code,
|
|
477
|
+
state
|
|
478
|
+
};
|
|
479
|
+
if (resultState.ok) {
|
|
480
|
+
const resolve = this.#callbackResolve;
|
|
481
|
+
queueMicrotask(() => {
|
|
482
|
+
resolve?.({
|
|
483
|
+
code: resultState.code,
|
|
484
|
+
state: resultState.state
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
} else if (error && (!expectedState || state === expectedState)) {
|
|
488
|
+
const reject = this.#callbackReject;
|
|
489
|
+
const message = resultState.error ?? `Authorization failed: ${errorDescription}`;
|
|
490
|
+
queueMicrotask(() => {
|
|
491
|
+
reject?.(new OAuthError(message, { kind: "device-auth" }));
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
return new Response(oauth_default.replaceAll("__OAUTH_STATE__", () => serializeCallbackState(resultState)), {
|
|
495
|
+
status: resultState.ok ? 200 : 500,
|
|
496
|
+
headers: { "Content-Type": "text/html" }
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Wait for OAuth callback or manual input (whichever comes first).
|
|
501
|
+
*/
|
|
502
|
+
#waitForCallback(expectedState, nativeCallback) {
|
|
503
|
+
const timeoutSignal = AbortSignal.timeout(DEFAULT_TIMEOUT);
|
|
504
|
+
const settledSignal = new AbortController();
|
|
505
|
+
const signals = this.ctrl.signal ? [
|
|
506
|
+
this.ctrl.signal,
|
|
507
|
+
timeoutSignal,
|
|
508
|
+
settledSignal.signal
|
|
509
|
+
] : [timeoutSignal, settledSignal.signal];
|
|
510
|
+
const signal = AbortSignal.any(signals);
|
|
511
|
+
if (signal.aborted) return Promise.reject(this.#loginCancelledError());
|
|
512
|
+
const callback = Promise.withResolvers();
|
|
513
|
+
this.#callbackResolve = callback.resolve;
|
|
514
|
+
this.#callbackReject = callback.reject;
|
|
515
|
+
signal.addEventListener("abort", () => {
|
|
516
|
+
this.#callbackResolve = void 0;
|
|
517
|
+
this.#callbackReject = void 0;
|
|
518
|
+
callback.reject(new LoginCancelledError(`OAuth callback cancelled: ${signal.reason}`));
|
|
519
|
+
}, { once: true });
|
|
520
|
+
const callbackPromise = callback.promise;
|
|
521
|
+
const candidates = [callbackPromise];
|
|
522
|
+
if (nativeCallback) candidates.push(nativeCallback);
|
|
523
|
+
if (this.ctrl.onManualCodeInput) {
|
|
524
|
+
const requestManualInput = this.ctrl.onManualCodeInput;
|
|
525
|
+
const manualPromise = (async () => {
|
|
526
|
+
while (true) {
|
|
527
|
+
const result = await Promise.race([callbackPromise, requestManualInput(signal).then((input) => {
|
|
528
|
+
const parsed = parseCallbackInput(input);
|
|
529
|
+
if (!parsed.code) return null;
|
|
530
|
+
if (expectedState && parsed.state && parsed.state !== expectedState) return null;
|
|
531
|
+
return {
|
|
532
|
+
code: parsed.code,
|
|
533
|
+
state: parsed.state ?? ""
|
|
534
|
+
};
|
|
535
|
+
})]);
|
|
536
|
+
if (result) return result;
|
|
537
|
+
}
|
|
538
|
+
})();
|
|
539
|
+
candidates.push(manualPromise);
|
|
540
|
+
}
|
|
541
|
+
return Promise.race(candidates).finally(() => {
|
|
542
|
+
this.#callbackResolve = void 0;
|
|
543
|
+
this.#callbackReject = void 0;
|
|
544
|
+
settledSignal.abort("OAuth callback settled");
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
/**
|
|
549
|
+
* Parse a redirect URL or code string to extract code and state.
|
|
550
|
+
*/
|
|
551
|
+
function parseCallbackInput(input) {
|
|
552
|
+
const value = input.trim();
|
|
553
|
+
if (!value) return {};
|
|
554
|
+
try {
|
|
555
|
+
const url = new URL(value);
|
|
556
|
+
return {
|
|
557
|
+
code: url.searchParams.get("code") ?? void 0,
|
|
558
|
+
state: url.searchParams.get("state") ?? void 0
|
|
559
|
+
};
|
|
560
|
+
} catch {}
|
|
561
|
+
if (value.includes("code=")) {
|
|
562
|
+
const params = new URLSearchParams(value.replace(/^[?#]/, ""));
|
|
563
|
+
return {
|
|
564
|
+
code: params.get("code") ?? void 0,
|
|
565
|
+
state: params.get("state") ?? void 0
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
const [code, state] = value.split("#", 2);
|
|
569
|
+
return {
|
|
570
|
+
code,
|
|
571
|
+
state
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
//#endregion
|
|
575
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/registry/snowflake.ts
|
|
576
|
+
const SNOWFLAKE_ACCOUNT_ORIGIN_PLACEHOLDER = "https://snowflake-account.invalid";
|
|
577
|
+
const INVALID_ACCOUNT_MESSAGE = "Paste your Snowflake account identifier (orgname-accountname) or account URL";
|
|
578
|
+
const DNS_LABEL = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
579
|
+
const LEGACY_SNOWSIGHT_REGION = /^[a-z]{2}(?:-gov)?-[a-z]+-\d+$/;
|
|
580
|
+
function isDnsLabel(label) {
|
|
581
|
+
return label.length <= 63 && DNS_LABEL.test(label);
|
|
582
|
+
}
|
|
583
|
+
/** Normalize account identifiers and Snowsight links without accepting arbitrary credential-receiving hosts. */
|
|
584
|
+
function normalizeSnowflakeAccountUrl(input) {
|
|
585
|
+
const value = input.trim();
|
|
586
|
+
if (!value) throw new ConfigurationError("Snowflake account is required: run /login snowflake or set SNOWFLAKE_ACCOUNT");
|
|
587
|
+
const scheme = /^([a-z][a-z0-9+.-]*):\/\//i.exec(value);
|
|
588
|
+
if (scheme && scheme[1].toLowerCase() !== "https") throw new ConfigurationError("Snowflake account URL must use https");
|
|
589
|
+
if (value.includes("\\")) throw new ConfigurationError(INVALID_ACCOUNT_MESSAGE);
|
|
590
|
+
let host;
|
|
591
|
+
if (scheme) {
|
|
592
|
+
const url = /^https:\/\/([^/?#]+)(.*)$/i.exec(value);
|
|
593
|
+
if (!url) throw new ConfigurationError(INVALID_ACCOUNT_MESSAGE);
|
|
594
|
+
host = url[1].toLowerCase().replaceAll("_", "-");
|
|
595
|
+
if (host.endsWith(":443")) host = host.slice(0, -4);
|
|
596
|
+
if (host === "app.snowflake.com") {
|
|
597
|
+
const segments = url[2].split(/[?#]/, 1)[0].split("/");
|
|
598
|
+
const org = segments[1]?.toLowerCase().replaceAll("_", "-") ?? "";
|
|
599
|
+
const account = segments[2]?.toLowerCase().replaceAll("_", "-") ?? "";
|
|
600
|
+
if (!isDnsLabel(org) || !isDnsLabel(account)) throw new ConfigurationError(INVALID_ACCOUNT_MESSAGE);
|
|
601
|
+
if (LEGACY_SNOWSIGHT_REGION.test(org)) throw new ConfigurationError("Legacy Snowsight links don't identify the account host; paste your account identifier (orgname-accountname) or account URL");
|
|
602
|
+
host = `${org}-${account}.snowflakecomputing.com`;
|
|
603
|
+
}
|
|
604
|
+
} else {
|
|
605
|
+
if (/[/?#]/.test(value)) throw new ConfigurationError(INVALID_ACCOUNT_MESSAGE);
|
|
606
|
+
host = value.toLowerCase().replaceAll("_", "-");
|
|
607
|
+
if (host.endsWith(":443")) host = host.slice(0, -4);
|
|
608
|
+
if (!host.endsWith(".snowflakecomputing.com") && !host.endsWith(".snowflakecomputing.cn")) host += ".snowflakecomputing.com";
|
|
609
|
+
}
|
|
610
|
+
if (host.endsWith(".snowflakecomputing.cn")) throw new ConfigurationError("Snowflake Cortex REST API is not available in China-region accounts (.snowflakecomputing.cn)");
|
|
611
|
+
if (!host.endsWith(".snowflakecomputing.com") || host.length === 23 || !host.split(".").every(isDnsLabel)) throw new ConfigurationError(INVALID_ACCOUNT_MESSAGE);
|
|
612
|
+
return `https://${host}`;
|
|
613
|
+
}
|
|
614
|
+
const INVALID_CREDENTIAL_MESSAGE = "Invalid Snowflake credential; run /login snowflake again";
|
|
615
|
+
/** Decode the registry's structured OAuth key; malformed JSON must never become a bearer token. */
|
|
616
|
+
function parseSnowflakeCredential(value) {
|
|
617
|
+
const trimmed = value.trim();
|
|
618
|
+
if (!trimmed) return null;
|
|
619
|
+
if (!trimmed.startsWith("{")) return { token: trimmed };
|
|
620
|
+
let credential;
|
|
621
|
+
try {
|
|
622
|
+
credential = JSON.parse(trimmed);
|
|
623
|
+
} catch {
|
|
624
|
+
throw new ConfigurationError(INVALID_CREDENTIAL_MESSAGE);
|
|
625
|
+
}
|
|
626
|
+
if (!credential || typeof credential !== "object" || !("token" in credential) || typeof credential.token !== "string" || !credential.token.trim() || !("enterpriseUrl" in credential) || typeof credential.enterpriseUrl !== "string" || !credential.enterpriseUrl.trim()) throw new ConfigurationError(INVALID_CREDENTIAL_MESSAGE);
|
|
627
|
+
let accountUrl;
|
|
628
|
+
try {
|
|
629
|
+
accountUrl = normalizeSnowflakeAccountUrl(credential.enterpriseUrl);
|
|
630
|
+
} catch (error) {
|
|
631
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
632
|
+
throw new ConfigurationError(`Invalid Snowflake credential account: ${reason}; run /login snowflake again`);
|
|
633
|
+
}
|
|
634
|
+
return {
|
|
635
|
+
token: credential.token.trim(),
|
|
636
|
+
accountUrl
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
const snowflakeTransport = { prepareRequest: (model, options) => {
|
|
640
|
+
const credential = parseSnowflakeCredential(options.apiKey?.trim() || $env.SNOWFLAKE_PAT || "");
|
|
641
|
+
if (!credential) return {
|
|
642
|
+
model,
|
|
643
|
+
options
|
|
644
|
+
};
|
|
645
|
+
let baseUrl = model.baseUrl;
|
|
646
|
+
if (baseUrl === SNOWFLAKE_ACCOUNT_ORIGIN_PLACEHOLDER || baseUrl.startsWith(`${SNOWFLAKE_ACCOUNT_ORIGIN_PLACEHOLDER}/`)) baseUrl = (credential.accountUrl ?? normalizeSnowflakeAccountUrl($env.SNOWFLAKE_ACCOUNT ?? "")) + baseUrl.slice(33);
|
|
647
|
+
else if (credential.accountUrl) {
|
|
648
|
+
let origin;
|
|
649
|
+
try {
|
|
650
|
+
origin = new URL(baseUrl).origin;
|
|
651
|
+
} catch {}
|
|
652
|
+
if (origin !== credential.accountUrl) throw new ConfigurationError("Snowflake OAuth account does not match the model endpoint");
|
|
653
|
+
}
|
|
654
|
+
return {
|
|
655
|
+
model: baseUrl === model.baseUrl ? model : {
|
|
656
|
+
...model,
|
|
657
|
+
baseUrl
|
|
658
|
+
},
|
|
659
|
+
options: {
|
|
660
|
+
...options,
|
|
661
|
+
apiKey: credential.token
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
} };
|
|
665
|
+
//#endregion
|
|
666
|
+
export { authProviders as a, authPolicyFor as i, snowflakeTransport as n, OAuthCallbackFlow as r, normalizeSnowflakeAccountUrl as t };
|