UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

205 lines (204 loc) 8.29 kB
import { r as createLazyRuntimeModule } from "./lazy-runtime-CgCh8H_K.js"; import { i as resolveOpenAICodexAuthIdentity } from "./provider-openai-chatgpt-auth-BoLGEnud.js"; import { a as oauthErrorHtml, d as withOAuthLoginAbort, n as createOAuthLoginCancelledError, o as oauthSuccessHtml, s as parseOAuthAuthorizationInput, u as throwIfOAuthLoginAborted } from "./provider-oauth-runtime-CduuU4e2.js"; import "./provider-auth-BeZ7NZUU.js"; import { n as resolveOpenAICallbackHost, r as resolveOpenAIRedirectUri, t as createOpenAIAuthorizationFlow } from "./openai-chatgpt-oauth-authorization.runtime.js"; import { n as refreshOpenAIAccessToken, t as exchangeOpenAIAuthorizationCode } from "./openai-chatgpt-oauth-token.runtime.js"; //#region extensions/openai/openai-chatgpt-oauth-flow.runtime.ts /** * OpenAI Codex (ChatGPT OAuth) flow * * NOTE: This module uses Node.js crypto and http for the OAuth callback. * It is only intended for CLI use, not browser environments. */ const CALLBACK_PORT = 1455; const CALLBACK_HOST = resolveOpenAICallbackHost(); const REDIRECT_URI = resolveOpenAIRedirectUri(CALLBACK_HOST); const MANUAL_PROMPT_FALLBACK_MS = 15e3; const loadNodeOAuthHttp = createLazyRuntimeModule(() => import("node:http")); function waitForManualPromptFallback(signal) { return new Promise((resolve, reject) => { if (signal?.aborted) { reject(createOAuthLoginCancelledError()); return; } const cleanup = () => { signal?.removeEventListener("abort", abort); }; const abort = () => { clearTimeout(timeout); cleanup(); reject(createOAuthLoginCancelledError()); }; const timeout = setTimeout(() => { cleanup(); resolve(null); }, MANUAL_PROMPT_FALLBACK_MS); signal?.addEventListener("abort", abort, { once: true }); timeout.unref?.(); }); } function parseAuthorizationCode(input, state) { const parsed = parseOAuthAuthorizationInput(input); if (parsed.state && parsed.state !== state) throw new Error("State mismatch"); return parsed.code; } async function promptForAuthorizationCode(onPrompt, state) { return parseAuthorizationCode(await onPrompt({ message: "Paste the authorization code (or full redirect URL):" }), state); } function sendOAuthHtmlResponse(res, statusCode, html) { res.statusCode = statusCode; res.setHeader("Connection", "close"); res.setHeader("Content-Type", "text/html; charset=utf-8"); res.end(html); } async function startLocalOAuthServer(state, assertCurrent) { const http = await loadNodeOAuthHttp(); assertCurrent?.(); let settleWait; const waitForCodePromise = new Promise((resolve) => { settleWait = resolve; }); const server = http.createServer((req, res) => { try { const url = new URL(req.url || "", "http://localhost"); if (url.pathname !== "/auth/callback") { sendOAuthHtmlResponse(res, 404, oauthErrorHtml("Callback route not found.")); return; } if (url.searchParams.get("state") !== state) { sendOAuthHtmlResponse(res, 400, oauthErrorHtml("State mismatch.")); return; } const code = url.searchParams.get("code"); if (!code) { sendOAuthHtmlResponse(res, 400, oauthErrorHtml("Missing authorization code.")); return; } sendOAuthHtmlResponse(res, 200, oauthSuccessHtml("OpenAI authentication completed. You can close this window.")); settleWait?.({ code }); } catch { sendOAuthHtmlResponse(res, 500, oauthErrorHtml("Internal error while processing OAuth callback.")); } }); return new Promise((resolve) => { server.listen(CALLBACK_PORT, CALLBACK_HOST, () => { resolve({ close: () => { server.close(); server.closeAllConnections(); }, cancelWait: () => { settleWait?.(null); }, waitForCode: () => waitForCodePromise }); }).on("error", () => { settleWait?.(null); resolve({ close: () => { try { server.close(); } catch {} }, cancelWait: () => {}, waitForCode: async () => null }); }); }); } function resolveOpenAICredentials(result) { if (result.type !== "success") { if (result.cancelled) throw createOAuthLoginCancelledError(); const facts = [ result.status ? `HTTP ${result.status}` : void 0, result.code ? `code=${result.code}` : void 0, result.errorType ? `type=${result.errorType}` : void 0 ].filter((value) => Boolean(value)); const diagnostic = facts.length > 0 ? `OpenAI Codex token ${result.operation} failed (${facts.join("; ")}).` : void 0; throw Object.assign(new Error([result.summary, diagnostic].filter(Boolean).join("\n\n")), { oauthRefreshFailure: { summary: result.summary, ...result.errorType ? { errorType: result.errorType } : {}, ...result.reason ? { reason: result.reason } : {}, ...result.status ? { status: result.status } : {} } }); } const accountId = resolveOpenAICodexAuthIdentity({ access: result.access }).accountId; if (!accountId) throw new Error("Failed to extract accountId from token"); return { access: result.access, refresh: result.refresh, expires: result.expires, accountId }; } /** * Login with OpenAI Codex OAuth * * @param options.onAuth - Called with URL and instructions when auth starts * @param options.onPrompt - Called to prompt user for manual code paste (fallback if no onManualCodeInput) * @param options.onProgress - Optional progress messages * @param options.onManualCodeInput - Optional promise that resolves with user-pasted code. * Races with browser callback - whichever completes first wins. * Useful for showing paste input immediately alongside browser flow. * @param options.originator - OAuth originator parameter (defaults to "openclaw") */ async function loginOpenAICodex(options) { options.assertCurrent?.(); throwIfOAuthLoginAborted(options.signal); const { verifier, redirectUri, state, url } = await createOpenAIAuthorizationFlow(options.originator ?? "openclaw", REDIRECT_URI); const server = await startLocalOAuthServer(state, options.assertCurrent); let code; try { options.assertCurrent?.(); throwIfOAuthLoginAborted(options.signal); await withOAuthLoginAbort(Promise.resolve(options.onAuth({ url, instructions: "A browser window should open. Complete login to finish." })), options.signal, server.cancelWait); throwIfOAuthLoginAborted(options.signal); if (options.onManualCodeInput) { let manualCode; let manualError; const manualPromise = options.onManualCodeInput().then((input) => { manualCode = input; server.cancelWait(); }).catch((err) => { manualError = err instanceof Error ? err : new Error(String(err)); server.cancelWait(); }); const result = await withOAuthLoginAbort(server.waitForCode(), options.signal, server.cancelWait); if (!result?.code && !manualCode && !manualError) await withOAuthLoginAbort(manualPromise, options.signal, server.cancelWait); if (manualError) throw manualError; if (result?.code) code = result.code; else if (manualCode) code = parseAuthorizationCode(manualCode, state); } else { const callbackPromise = server.waitForCode(); const result = await withOAuthLoginAbort(Promise.race([callbackPromise, waitForManualPromptFallback(options.signal)]), options.signal, server.cancelWait); if (result?.code) code = result.code; else { const promptCodePromise = promptForAuthorizationCode(options.onPrompt, state).then((promptCode) => { server.cancelWait(); return promptCode; }); code = await withOAuthLoginAbort(Promise.race([callbackPromise.then((callback) => callback?.code), promptCodePromise]), options.signal, server.cancelWait); } } if (!code) code = await withOAuthLoginAbort(promptForAuthorizationCode(options.onPrompt, state), options.signal, server.cancelWait); if (!code) throw new Error("Missing authorization code"); return resolveOpenAICredentials(await exchangeOpenAIAuthorizationCode(code, verifier, redirectUri, { signal: options.signal, assertCurrent: options.assertCurrent })); } finally { server.close(); } } /** * Refresh OpenAI Codex OAuth token */ async function refreshOpenAICodexToken(refreshToken) { return resolveOpenAICredentials(await refreshOpenAIAccessToken(refreshToken)); } //#endregion export { refreshOpenAICodexToken as n, loginOpenAICodex as t };