UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

181 lines (180 loc) 7.31 kB
import { a as asOptionalRecord, c as isRecord } from "./record-coerce-DItp3I4t.js"; import { i as normalizeBoundedOptionalString, l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js"; import { p as redactSensitiveText } from "./redact-BtvPPfTi.js"; import { i as readResponseWithLimit } from "./http-response-body-CwT_cCNz.js"; import { i as fetchWithSsrFGuard } from "./fetch-guard-BMdGQhbb.js"; import { c as resolveOAuthTokenExpiresAt, l as resolveOAuthTokenLifetimeMs, u as throwIfOAuthLoginAborted } from "./provider-oauth-runtime-CduuU4e2.js"; import "./response-limit-runtime-BV0RL9tn.js"; import "./string-coerce-runtime-GQa0ehRA.js"; import "./ssrf-runtime-Bum5C6NN.js"; import "./security-runtime-Ckf0kc0h.js"; //#region extensions/openai/openai-chatgpt-oauth-token.runtime.ts const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann"; const TOKEN_URL = "https://auth.openai.com/oauth/token"; const OAUTH_TOKEN_SSRF_POLICY = { allowRfc2544BenchmarkRange: true, allowIpv6UniqueLocalRange: true, hostnameAllowlist: ["auth.openai.com"] }; const TOKEN_REQUEST_TIMEOUT_MS = 3e4; const OAUTH_TOKEN_RESPONSE_BODY_LIMIT_BYTES = 1048576; const OAUTH_TOKEN_ERROR_SUMMARY_MAX_CHARS = 500; const TOKEN_FAILURE_REASON_BY_CODE = { invalid_grant: "invalid_grant", invalid_refresh_token: "invalid_refresh_token", refresh_token_expired: "expired", refresh_token_invalidated: "token_invalidated", refresh_token_reused: "refresh_token_reused" }; const TOKEN_FAILURE_CODES = new Set(Object.keys(TOKEN_FAILURE_REASON_BY_CODE)); function normalizeTokenErrorSummary(value) { const normalized = normalizeBoundedOptionalString(value, OAUTH_TOKEN_ERROR_SUMMARY_MAX_CHARS * 4)?.replace(/\s+/gu, " ").trim(); return normalized ? normalizeBoundedOptionalString(redactSensitiveText(normalized, { mode: "tools" }), OAUTH_TOKEN_ERROR_SUMMARY_MAX_CHARS) : void 0; } function normalizeTokenErrorFact(value, allowlist) { const normalized = normalizeOptionalString(value)?.toLowerCase(); return normalized && allowlist.has(normalized) ? normalized : void 0; } function buildTokenResponseFailure(params) { let body; try { body = asOptionalRecord(JSON.parse(params.text)); } catch {} const error = asOptionalRecord(body?.error); const code = normalizeTokenErrorFact(error?.code ?? (typeof body?.error === "string" ? body.error : body?.code), TOKEN_FAILURE_CODES); const rawType = normalizeOptionalString(error?.type ?? body?.type)?.toLowerCase(); const errorType = rawType === "invalid_grant" || rawType === "invalid_request_error" ? rawType : void 0; const reason = code ? TOKEN_FAILURE_REASON_BY_CODE[code] : void 0; const summary = normalizeTokenErrorSummary(error?.message ?? body?.error_description ?? body?.message) ?? `OpenAI Codex token ${params.operation} failed (HTTP ${params.response.status}).`; return { type: "failed", operation: params.operation, summary, status: params.response.status, ...code ? { code } : {}, ...errorType ? { errorType } : {}, ...reason ? { reason } : {} }; } function formatMissingTokenResponseFields(json, existingRefreshToken) { const missing = []; if (!json.access_token) missing.push("access_token"); if (!json.refresh_token && !existingRefreshToken) missing.push("refresh_token"); if (resolveOAuthTokenLifetimeMs(json.expires_in) === void 0) missing.push("expires_in"); return missing.join(", "); } function formatTokenRequestError(operation, error, timeoutMs, signal) { if (signal?.aborted) return "Login cancelled"; if (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError")) return `OpenAI Codex token ${operation} timed out after ${timeoutMs}ms`; const detail = normalizeTokenErrorSummary(error instanceof Error ? error.message : String(error)); return normalizeTokenErrorSummary(`OpenAI Codex token ${operation} error${detail ? `: ${detail}` : ""}`) ?? `OpenAI Codex token ${operation} error`; } async function postTokenForm(body, options = {}) { const timeoutMs = options.timeoutMs ?? TOKEN_REQUEST_TIMEOUT_MS; throwIfOAuthLoginAborted(options.signal); const { response, release } = await fetchWithSsrFGuard({ url: TOKEN_URL, mode: "trusted_env_proxy", policy: OAUTH_TOKEN_SSRF_POLICY, init: { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body }, timeoutMs, signal: options.signal, beforeRequest: options.assertCurrent, auditContext: "openai-chatgpt-oauth-token" }); try { const responseBody = await readResponseWithLimit(response, OAUTH_TOKEN_RESPONSE_BODY_LIMIT_BYTES, { onOverflow: ({ size, maxBytes }) => /* @__PURE__ */ new Error(`OpenAI Codex OAuth token response body too large: ${size} bytes (limit: ${maxBytes} bytes)`) }); return new Response(new Uint8Array(responseBody), { status: response.status, statusText: response.statusText, headers: response.headers }); } finally { await release(); } } async function readOpenAITokenResponse(response, operation, existingRefreshToken) { if (!response.ok) return buildTokenResponseFailure({ response, operation, text: await response.text().catch(() => "") }); let json; try { json = await response.json(); } catch { return { type: "failed", operation, summary: `OpenAI Codex token ${operation} failed: response is not valid JSON` }; } if (!isRecord(json)) return { type: "failed", operation, summary: `OpenAI Codex token ${operation} failed: expected JSON object response` }; const expires = resolveOAuthTokenExpiresAt(json.expires_in); const refreshToken = json.refresh_token || existingRefreshToken; if (!json.access_token || !refreshToken || expires === void 0) return { type: "failed", operation, summary: `OpenAI Codex token ${operation} response missing fields: ${formatMissingTokenResponseFields(json, existingRefreshToken)}` }; return { type: "success", access: json.access_token, refresh: refreshToken, expires }; } async function exchangeOpenAIAuthorizationCode(code, verifier, redirectUri, options = {}) { const timeoutMs = options.timeoutMs ?? TOKEN_REQUEST_TIMEOUT_MS; let response; try { response = await postTokenForm(new URLSearchParams({ grant_type: "authorization_code", client_id: CLIENT_ID, code, code_verifier: verifier, redirect_uri: redirectUri }), { ...options, timeoutMs }); } catch (error) { return { type: "failed", operation: "exchange", ...options.signal?.aborted ? { cancelled: true } : {}, summary: formatTokenRequestError("exchange", error, timeoutMs, options.signal) }; } return await readOpenAITokenResponse(response, "exchange"); } async function refreshOpenAIAccessToken(refreshToken, options = {}) { const timeoutMs = options.timeoutMs ?? TOKEN_REQUEST_TIMEOUT_MS; try { return await readOpenAITokenResponse(await postTokenForm(new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: CLIENT_ID }), { ...options, timeoutMs }), "refresh", refreshToken); } catch (error) { return { type: "failed", operation: "refresh", ...options.signal?.aborted ? { cancelled: true } : {}, summary: formatTokenRequestError("refresh", error, timeoutMs, options.signal) }; } } //#endregion export { refreshOpenAIAccessToken as n, exchangeOpenAIAuthorizationCode as t };