UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

166 lines (165 loc) 7.21 kB
import { T as resolveExpiresAtMsFromDurationSeconds, x as positiveSecondsToSafeMilliseconds } from "./number-coercion-CJQ8TR--.js"; import "./number-runtime-DBLVDypr.js"; import { t as trimNonEmptyString } from "./openai-chatgpt-shared-gSKxAVif.js"; import { t as resolveCodexAccessTokenExpiry } from "./openai-chatgpt-auth-identity-Mk0NOf7H.js"; //#region extensions/openai/openai-chatgpt-device-code.ts const OPENAI_AUTH_BASE_URL = "https://auth.openai.com"; const OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann"; const OPENAI_CODEX_DEVICE_CODE_TIMEOUT_MS = 15 * 6e4; const OPENAI_CODEX_DEVICE_CODE_DEFAULT_INTERVAL_MS = 5e3; const OPENAI_CODEX_DEVICE_CODE_MIN_INTERVAL_MS = 1e3; const OPENAI_CODEX_DEVICE_CALLBACK_URL = `${OPENAI_AUTH_BASE_URL}/deviceauth/callback`; function resolveOpenAICodexDeviceCodeHeaders(contentType) { const version = process.env.OPENCLAW_VERSION?.trim(); return { "Content-Type": contentType, originator: "openclaw", ...version ? { version } : {}, "User-Agent": version ? `openclaw/${version}` : "openclaw" }; } function parseJsonObject(text) { try { const parsed = JSON.parse(text); return parsed && typeof parsed === "object" ? parsed : null; } catch { return null; } } function sanitizeDeviceCodeErrorText(value) { const esc = String.fromCharCode(27); const ansiCsiRegex = new RegExp(`${esc}\\[[\\u0020-\\u003f]*[\\u0040-\\u007e]`, "g"); const osc8Regex = new RegExp(`${esc}\\]8;;.*?${esc}\\\\|${esc}\\]8;;${esc}\\\\`, "g"); const controlCharsRegex = new RegExp(`[${String.fromCharCode(0)}-${String.fromCharCode(31)}${String.fromCharCode(127)}${String.fromCharCode(128)}-${String.fromCharCode(159)}]`, "g"); return value.replace(osc8Regex, "").replace(ansiCsiRegex, "").replace(controlCharsRegex, " ").replace(/\s+/g, " ").trim(); } function resolveNextDeviceCodePollDelayMs(intervalMs, deadlineMs) { const remainingMs = Math.max(0, deadlineMs - Date.now()); return Math.min(Math.max(intervalMs, OPENAI_CODEX_DEVICE_CODE_MIN_INTERVAL_MS), remainingMs); } function formatDeviceCodeError(params) { const body = parseJsonObject(params.bodyText); const error = trimNonEmptyString(body?.error); const description = trimNonEmptyString(body?.error_description); const safeError = error ? sanitizeDeviceCodeErrorText(error) : void 0; const safeDescription = description ? sanitizeDeviceCodeErrorText(description) : void 0; if (safeError && safeDescription) return `${params.prefix}: ${safeError} (${safeDescription})`; if (safeError) return `${params.prefix}: ${safeError}`; const bodyText = sanitizeDeviceCodeErrorText(params.bodyText); return bodyText ? `${params.prefix}: HTTP ${params.status} ${bodyText}` : `${params.prefix}: HTTP ${params.status}`; } async function requestOpenAICodexDeviceCode(fetchFn) { const response = await fetchFn(`${OPENAI_AUTH_BASE_URL}/api/accounts/deviceauth/usercode`, { method: "POST", headers: resolveOpenAICodexDeviceCodeHeaders("application/json"), body: JSON.stringify({ client_id: OPENAI_CODEX_CLIENT_ID }) }); const bodyText = await response.text(); if (!response.ok) { if (response.status === 404) throw new Error("OpenAI Codex device code login is not enabled for this server. Use ChatGPT OAuth instead."); throw new Error(formatDeviceCodeError({ prefix: "OpenAI device code request failed", status: response.status, bodyText })); } const body = parseJsonObject(bodyText); const deviceAuthId = trimNonEmptyString(body?.device_auth_id); const userCode = trimNonEmptyString(body?.user_code) ?? trimNonEmptyString(body?.usercode); if (!deviceAuthId || !userCode) throw new Error("OpenAI device code response was missing the device code or user code."); return { deviceAuthId, userCode, verificationUrl: `${OPENAI_AUTH_BASE_URL}/codex/device`, intervalMs: positiveSecondsToSafeMilliseconds(body?.interval) ?? OPENAI_CODEX_DEVICE_CODE_DEFAULT_INTERVAL_MS }; } async function pollOpenAICodexDeviceCode(params) { const deadline = Date.now() + OPENAI_CODEX_DEVICE_CODE_TIMEOUT_MS; while (Date.now() < deadline) { const response = await params.fetchFn(`${OPENAI_AUTH_BASE_URL}/api/accounts/deviceauth/token`, { method: "POST", headers: resolveOpenAICodexDeviceCodeHeaders("application/json"), body: JSON.stringify({ device_auth_id: params.deviceAuthId, user_code: params.userCode }) }); const bodyText = await response.text(); if (response.ok) { const body = parseJsonObject(bodyText); const authorizationCode = trimNonEmptyString(body?.authorization_code); const codeVerifier = trimNonEmptyString(body?.code_verifier); if (!authorizationCode || !codeVerifier) throw new Error("OpenAI device authorization response was missing the exchange code."); return { authorizationCode, codeVerifier }; } if (response.status === 403 || response.status === 404) { await new Promise((resolve) => { setTimeout(resolve, resolveNextDeviceCodePollDelayMs(params.intervalMs, deadline)); }); continue; } throw new Error(formatDeviceCodeError({ prefix: "OpenAI device authorization failed", status: response.status, bodyText })); } throw new Error("OpenAI device authorization timed out after 15 minutes."); } async function exchangeOpenAICodexDeviceCode(params) { const response = await params.fetchFn(`${OPENAI_AUTH_BASE_URL}/oauth/token`, { method: "POST", headers: resolveOpenAICodexDeviceCodeHeaders("application/x-www-form-urlencoded"), body: new URLSearchParams({ grant_type: "authorization_code", code: params.authorizationCode, redirect_uri: OPENAI_CODEX_DEVICE_CALLBACK_URL, client_id: OPENAI_CODEX_CLIENT_ID, code_verifier: params.codeVerifier }) }); const bodyText = await response.text(); if (!response.ok) throw new Error(formatDeviceCodeError({ prefix: "OpenAI device token exchange failed", status: response.status, bodyText })); const body = parseJsonObject(bodyText); const access = trimNonEmptyString(body?.access_token); const refresh = trimNonEmptyString(body?.refresh_token); if (!access || !refresh) throw new Error("OpenAI token exchange succeeded but did not return OAuth tokens."); return { access, refresh, expires: resolveExpiresAtMsFromDurationSeconds(body?.expires_in) ?? resolveCodexAccessTokenExpiry(access) ?? Date.now() }; } async function loginOpenAICodexDeviceCode(params) { const fetchFn = params.fetchFn ?? fetch; params.onProgress?.("Requesting device code…"); const deviceCode = await requestOpenAICodexDeviceCode(fetchFn); await params.onVerification({ verificationUrl: deviceCode.verificationUrl, userCode: deviceCode.userCode, expiresInMs: OPENAI_CODEX_DEVICE_CODE_TIMEOUT_MS }); params.onProgress?.("Waiting for device authorization…"); const authorization = await pollOpenAICodexDeviceCode({ fetchFn, deviceAuthId: deviceCode.deviceAuthId, userCode: deviceCode.userCode, intervalMs: deviceCode.intervalMs }); params.onProgress?.("Exchanging device code…"); return await exchangeOpenAICodexDeviceCode({ fetchFn, authorizationCode: authorization.authorizationCode, codeVerifier: authorization.codeVerifier }); } //#endregion export { loginOpenAICodexDeviceCode as t };