UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

756 lines (755 loc) 28.4 kB
import { E as resolveExpiresAtMsFromEpochSeconds, T as resolveExpiresAtMsFromDurationSeconds, h as nonNegativeSecondsToSafeMilliseconds, j as resolveTimerTimeoutMs, x as positiveSecondsToSafeMilliseconds } from "./number-coercion-CJQ8TR--.js"; import "./parse-finite-number-Z7n6tXLk.js"; import { r as loadActivatedBundledPluginPublicSurfaceModuleSync } from "./facade-runtime-BMJRpsns.js"; import { a as oauthErrorHtml, c as resolveOAuthTokenExpiresAt, d as withOAuthLoginAbort, i as generatePKCE, n as createOAuthLoginCancelledError, o as oauthSuccessHtml, r as generateOAuthState, s as parseOAuthAuthorizationInput, t as buildOAuthRequestSignal, u as throwIfOAuthLoginAborted } from "./provider-oauth-runtime-CHge3JIZ.js"; //#region src/llm/utils/oauth/anthropic.ts let nodeApis = null; let nodeApisPromise = null; const decode$1 = (s) => atob(s); const CLIENT_ID$1 = decode$1("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl"); const AUTHORIZE_URL = "https://claude.ai/oauth/authorize"; const TOKEN_URL = "https://platform.claude.com/v1/oauth/token"; const CALLBACK_HOST = process.env.OPENCLAW_OAUTH_CALLBACK_HOST || "127.0.0.1"; const CALLBACK_PORT = 53692; const CALLBACK_PATH = "/callback"; const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`; const SCOPES = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload"; async function getNodeApis() { if (nodeApis) return nodeApis; if (!nodeApisPromise) { if (typeof process === "undefined" || !process.versions?.node && !process.versions?.bun) throw new Error("Anthropic OAuth is only available in Node.js environments"); nodeApisPromise = import("node:http").then((httpModule) => ({ createServer: httpModule.createServer })); } nodeApis = await nodeApisPromise; return nodeApis; } function formatErrorDetails(error) { if (error instanceof Error) { const details = [`${error.name}: ${error.message}`]; const errorWithCode = error; if (errorWithCode.code) details.push(`code=${errorWithCode.code}`); if (errorWithCode.errno !== void 0) details.push(`errno=${String(errorWithCode.errno)}`); if (error.cause !== void 0) details.push(`cause=${formatErrorDetails(error.cause)}`); if (error.stack) details.push(`stack=${error.stack}`); return details.join("; "); } return String(error); } function formatTokenResponseParseContext(responseBody) { return `bodyBytes=${Buffer.byteLength(responseBody, "utf8")}`; } function parseTokenCredentials(responseBody, options) { let data; try { data = JSON.parse(responseBody); } catch (error) { throw new Error(`${options.invalidJsonMessage} url=${TOKEN_URL}; ${formatTokenResponseParseContext(responseBody)}; details=${formatErrorDetails(error)}`, { cause: error }); } if (!data || typeof data !== "object") throw new Error(`${options.invalidFieldsMessage} url=${TOKEN_URL}; ${formatTokenResponseParseContext(responseBody)}`); const record = data; const expires = resolveOAuthTokenExpiresAt(record.expires_in, { refreshSkewMs: 300 * 1e3 }); if (typeof record.access_token !== "string" || !record.access_token || typeof record.refresh_token !== "string" || !record.refresh_token || expires === void 0) throw new Error(`${options.invalidFieldsMessage} url=${TOKEN_URL}; ${formatTokenResponseParseContext(responseBody)}`); return { refresh: record.refresh_token, access: record.access_token, expires }; } async function startCallbackServer(expectedState) { const { createServer } = await getNodeApis(); return new Promise((resolve, reject) => { let settleWait; const waitForCodePromise = new Promise((resolveWait) => { let settled = false; settleWait = (value) => { if (settled) return; settled = true; resolveWait(value); }; }); const server = createServer((req, res) => { try { const url = new URL(req.url || "", "http://localhost"); if (url.pathname !== CALLBACK_PATH) { res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" }); res.end(oauthErrorHtml("Callback route not found.")); return; } const code = url.searchParams.get("code"); const state = url.searchParams.get("state"); const error = url.searchParams.get("error"); if (error) { res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" }); res.end(oauthErrorHtml("Anthropic authentication did not complete.", `Error: ${error}`)); return; } if (!code || !state) { res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" }); res.end(oauthErrorHtml("Missing code or state parameter.")); return; } if (state !== expectedState) { res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" }); res.end(oauthErrorHtml("State mismatch.")); return; } res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); res.end(oauthSuccessHtml("Anthropic authentication completed. You can close this window.")); settleWait?.({ code, state }); } catch { res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" }); res.end("Internal error"); } }); server.on("error", (err) => { reject(err); }); server.listen(CALLBACK_PORT, CALLBACK_HOST, () => { resolve({ server, redirectUri: REDIRECT_URI, cancelWait: () => { settleWait?.(null); }, waitForCode: () => waitForCodePromise }); }); }); } async function postJson(url, body, options = {}) { const timeoutMs = options.timeoutMs ?? 3e4; throwIfOAuthLoginAborted(options.signal); const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify(body), signal: buildOAuthRequestSignal({ signal: options.signal, timeoutMs }) }); const responseBody = await response.text(); if (!response.ok) throw new Error(`HTTP request failed. status=${response.status}; url=${url}; body=${responseBody}`); return responseBody; } async function exchangeAuthorizationCode(code, state, verifier, redirectUri, signal) { let responseBody; try { responseBody = await postJson(TOKEN_URL, { grant_type: "authorization_code", client_id: CLIENT_ID$1, code, state, redirect_uri: redirectUri, code_verifier: verifier }, { signal }); } catch (error) { if (signal?.aborted) throw createOAuthLoginCancelledError(); throw new Error(`Token exchange request failed. url=${TOKEN_URL}; redirect_uri=${redirectUri}; response_type=authorization_code; details=${formatErrorDetails(error)}`, { cause: error }); } return parseTokenCredentials(responseBody, { invalidJsonMessage: "Token exchange returned invalid JSON.", invalidFieldsMessage: "Token exchange returned invalid token fields." }); } /** * Login with Anthropic OAuth (authorization code + PKCE) */ async function loginAnthropic(options) { throwIfOAuthLoginAborted(options.signal); const { verifier, challenge } = await generatePKCE(); const expectedState = generateOAuthState(); const server = await startCallbackServer(expectedState); let code; let state; let redirectUriForExchange = REDIRECT_URI; try { throwIfOAuthLoginAborted(options.signal); const authParams = new URLSearchParams({ code: "true", client_id: CLIENT_ID$1, response_type: "code", redirect_uri: REDIRECT_URI, scope: SCOPES, code_challenge: challenge, code_challenge_method: "S256", state: expectedState }); options.onAuth({ url: `${AUTHORIZE_URL}?${authParams.toString()}`, instructions: "Complete login in your browser. If the browser is on another machine, paste the final redirect URL here." }); throwIfOAuthLoginAborted(options.signal); if (options.onManualCodeInput) { let manualInput; let manualError; const manualPromise = options.onManualCodeInput().then((input) => { manualInput = 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 (manualError) throw manualError; if (result?.code) { code = result.code; state = result.state; redirectUriForExchange = REDIRECT_URI; } else if (manualInput) { const parsed = parseOAuthAuthorizationInput(manualInput); if (parsed.state && parsed.state !== expectedState) throw new Error("OAuth state mismatch"); code = parsed.code; state = parsed.state ?? expectedState; } if (!code) { await withOAuthLoginAbort(manualPromise, options.signal, server.cancelWait); if (manualError) throw toLintErrorObject(manualError, "Non-Error thrown"); if (manualInput) { const parsed = parseOAuthAuthorizationInput(manualInput); if (parsed.state && parsed.state !== expectedState) throw new Error("OAuth state mismatch"); code = parsed.code; state = parsed.state ?? expectedState; } } } else { const result = await withOAuthLoginAbort(server.waitForCode(), options.signal, server.cancelWait); if (result?.code) { code = result.code; state = result.state; redirectUriForExchange = REDIRECT_URI; } } if (!code) { const parsed = parseOAuthAuthorizationInput(await withOAuthLoginAbort(options.onPrompt({ message: "Paste the authorization code or full redirect URL:", placeholder: REDIRECT_URI }), options.signal, server.cancelWait)); if (parsed.state && parsed.state !== expectedState) throw new Error("OAuth state mismatch"); code = parsed.code; state = parsed.state ?? expectedState; } if (!code) throw new Error("Missing authorization code"); if (!state) throw new Error("Missing OAuth state"); options.onProgress?.("Exchanging authorization code for tokens..."); return exchangeAuthorizationCode(code, state, verifier, redirectUriForExchange, options.signal); } finally { server.server.close(); } } /** * Refresh Anthropic OAuth token */ async function refreshAnthropicToken(refreshToken) { let responseBody; try { responseBody = await postJson(TOKEN_URL, { grant_type: "refresh_token", client_id: CLIENT_ID$1, refresh_token: refreshToken }); } catch (error) { throw new Error(`Anthropic token refresh request failed. url=${TOKEN_URL}; details=${formatErrorDetails(error)}`, { cause: error }); } return parseTokenCredentials(responseBody, { invalidJsonMessage: "Anthropic token refresh returned invalid JSON.", invalidFieldsMessage: "Anthropic token refresh returned invalid token fields." }); } const anthropicOAuthProvider = { id: "anthropic", name: "Anthropic (Claude Pro/Max)", usesCallbackServer: true, async login(callbacks) { return loginAnthropic({ onAuth: callbacks.onAuth, onPrompt: callbacks.onPrompt, onProgress: callbacks.onProgress, onManualCodeInput: callbacks.onManualCodeInput, signal: callbacks.signal }); }, async refreshToken(credentials) { return refreshAnthropicToken(credentials.refresh); }, getApiKey(credentials) { return credentials.access; } }; function toLintErrorObject(value, fallbackMessage) { if (value instanceof Error) return value; if (typeof value === "string") return new Error(value); const error = new Error(fallbackMessage, { cause: value }); if (typeof value === "object" && value !== null || typeof value === "function") Object.assign(error, value); return error; } //#endregion //#region src/llm/utils/oauth/github-copilot.ts /** * GitHub Copilot OAuth flow */ const decode = (s) => atob(s); const CLIENT_ID = decode("SXYxLmI1MDdhMDhjODdlY2ZlOTg="); const COPILOT_HEADERS = { "User-Agent": "GitHubCopilotChat/0.35.0", "Editor-Version": "vscode/1.107.0", "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" }; const INITIAL_POLL_INTERVAL_MULTIPLIER = 1.2; const SLOW_DOWN_POLL_INTERVAL_MULTIPLIER = 1.4; const COPILOT_ROUTER_ID_PREFIX = "accounts/"; const COPILOT_REQUEST_TIMEOUT_MS = 3e4; function resolveExpiresAtFromDurationSeconds(value) { return resolveExpiresAtMsFromDurationSeconds(value); } function resolveExpiresAtFromEpochSeconds(value) { return resolveExpiresAtMsFromEpochSeconds(value, { bufferMs: 300 * 1e3 }); } function normalizeDomain(input) { const trimmed = input.trim(); if (!trimmed) return null; try { return (trimmed.includes("://") ? new URL(trimmed) : new URL(`https://${trimmed}`)).hostname; } catch { return null; } } function getUrls(domain) { return { deviceCodeUrl: `https://${domain}/login/device/code`, accessTokenUrl: `https://${domain}/login/oauth/access_token`, copilotTokenUrl: `https://api.${domain}/copilot_internal/v2/token` }; } /** * Parse the proxy-ep from a Copilot token and convert to API base URL. * Token format: tid=...;exp=...;proxy-ep=proxy.individual.githubcopilot.com;... * Returns API URL like https://api.individual.githubcopilot.com */ function getBaseUrlFromToken(token) { const match = token.match(/proxy-ep=([^;]+)/); if (!match) return null; return `https://${match[1].replace(/^proxy\./, "api.")}`; } function getGitHubCopilotBaseUrl(token, enterpriseDomain) { if (token) { const urlFromToken = getBaseUrlFromToken(token); if (urlFromToken) return urlFromToken; } if (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`; return "https://api.individual.githubcopilot.com"; } function formatCopilotRequestError(operation, error, options) { if (options.signal?.aborted) return /* @__PURE__ */ new Error("Login cancelled"); if (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError")) return /* @__PURE__ */ new Error(`GitHub Copilot ${operation} timed out after ${options.timeoutMs}ms`); return error instanceof Error ? error : /* @__PURE__ */ new Error(`GitHub Copilot ${operation} failed: ${String(error)}`); } function buildCopilotRequestSignal(options) { const timeoutSignal = AbortSignal.timeout(resolveTimerTimeoutMs(options.timeoutMs, COPILOT_REQUEST_TIMEOUT_MS)); if (!options.signal) return timeoutSignal; return AbortSignal.any([options.signal, timeoutSignal]); } async function fetchResponse(url, init, operation, options = {}) { const timeoutMs = resolveTimerTimeoutMs(options.timeoutMs, COPILOT_REQUEST_TIMEOUT_MS); try { return await fetch(url, { ...init, signal: buildCopilotRequestSignal({ ...options, timeoutMs }) }); } catch (error) { throw formatCopilotRequestError(operation, error, { signal: options.signal, timeoutMs }); } } async function fetchJson(url, init, operation, options = {}) { const response = await fetchResponse(url, init, operation, options); if (!response.ok) { const text = await response.text(); throw new Error(`${response.status} ${response.statusText}: ${text}`); } return response.json(); } async function startDeviceFlow(domain, options = {}) { const data = await fetchJson(getUrls(domain).deviceCodeUrl, { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "GitHubCopilotChat/0.35.0" }, body: new URLSearchParams({ client_id: CLIENT_ID, scope: "read:user" }) }, "device code request", options); if (!data || typeof data !== "object") throw new Error("Invalid device code response"); const deviceCode = data.device_code; const userCode = data.user_code; const verificationUri = data.verification_uri; const interval = data.interval; const intervalMs = nonNegativeSecondsToSafeMilliseconds(interval); const expiresAt = resolveExpiresAtFromDurationSeconds(data.expires_in); if (typeof deviceCode !== "string" || typeof userCode !== "string" || typeof verificationUri !== "string" || intervalMs === void 0 || expiresAt === void 0) throw new Error("Invalid device code response fields"); return { device_code: deviceCode, user_code: userCode, verification_uri: verificationUri, intervalMs, expiresAt }; } /** * Sleep that can be interrupted by an AbortSignal */ function abortableSleep(ms, signal) { return new Promise((resolve, reject) => { if (signal?.aborted) { reject(/* @__PURE__ */ new Error("Login cancelled")); return; } const timeout = setTimeout(resolve, ms); signal?.addEventListener("abort", () => { clearTimeout(timeout); reject(/* @__PURE__ */ new Error("Login cancelled")); }, { once: true }); }); } async function pollForGitHubAccessToken(domain, deviceCode, intervalMs, deadline, signal) { const urls = getUrls(domain); let pollingIntervalMs = Math.max(1e3, intervalMs); let intervalMultiplier = INITIAL_POLL_INTERVAL_MULTIPLIER; let slowDownResponses = 0; while (Date.now() < deadline) { if (signal?.aborted) throw new Error("Login cancelled"); const remainingMs = deadline - Date.now(); await abortableSleep(Math.min(Math.ceil(pollingIntervalMs * intervalMultiplier), remainingMs), signal); const raw = await fetchJson(urls.accessTokenUrl, { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "GitHubCopilotChat/0.35.0" }, body: new URLSearchParams({ client_id: CLIENT_ID, device_code: deviceCode, grant_type: "urn:ietf:params:oauth:grant-type:device_code" }) }, "device token request", { signal }); if (raw && typeof raw === "object" && typeof raw.access_token === "string") return raw.access_token; if (raw && typeof raw === "object" && typeof raw.error === "string") { const { error, error_description: description, interval } = raw; if (error === "authorization_pending") continue; if (error === "slow_down") { slowDownResponses += 1; const slowDownIntervalMs = positiveSecondsToSafeMilliseconds(interval); pollingIntervalMs = slowDownIntervalMs === void 0 ? Math.max(1e3, pollingIntervalMs + 5e3) : Math.max(1e3, slowDownIntervalMs); intervalMultiplier = SLOW_DOWN_POLL_INTERVAL_MULTIPLIER; continue; } const descriptionSuffix = description ? `: ${description}` : ""; throw new Error(`Device flow failed: ${error}${descriptionSuffix}`); } } if (slowDownResponses > 0) throw new Error("Device flow timed out after one or more slow_down responses. This is often caused by clock drift in WSL or VM environments. Please sync or restart the VM clock and try again."); throw new Error("Device flow timed out"); } /** * Refresh GitHub Copilot token */ async function refreshGitHubCopilotToken(refreshToken, enterpriseDomain, options = {}) { const raw = await fetchJson(getUrls(enterpriseDomain || "github.com").copilotTokenUrl, { headers: { Accept: "application/json", Authorization: `Bearer ${refreshToken}`, ...COPILOT_HEADERS } }, "token refresh request", options); if (!raw || typeof raw !== "object") throw new Error("Invalid Copilot token response"); const token = raw.token; const expires = resolveExpiresAtFromEpochSeconds(raw.expires_at); if (typeof token !== "string" || expires === void 0) throw new Error("Invalid Copilot token response fields"); return { refresh: refreshToken, access: token, expires, enterpriseUrl: enterpriseDomain }; } /** * Enable a model for the user's GitHub Copilot account. * This is required for some models (like Claude, Grok) before they can be used. */ async function enableGitHubCopilotModel(token, modelId, enterpriseDomain, options = {}) { const url = `${getGitHubCopilotBaseUrl(token, enterpriseDomain)}/models/${modelId}/policy`; try { return (await fetchResponse(url, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, ...COPILOT_HEADERS, "openai-intent": "chat-policy", "x-interaction-type": "chat-policy" }, body: JSON.stringify({ state: "enabled" }) }, "model policy request", options)).ok; } catch { return false; } } async function listGitHubCopilotModelIds(token, enterpriseDomain, options = {}) { const url = `${getGitHubCopilotBaseUrl(token, enterpriseDomain)}/models`; try { const raw = await fetchJson(url, { headers: { Accept: "application/json", Authorization: `Bearer ${token}`, ...COPILOT_HEADERS } }, "model list request", options); const data = raw && typeof raw === "object" ? raw.data : void 0; if (!Array.isArray(data)) return []; return data.flatMap((entry) => { if (!entry || typeof entry !== "object") return []; const model = entry; const id = typeof model.id === "string" ? model.id.trim() : ""; if (!id || id.startsWith(COPILOT_ROUTER_ID_PREFIX)) return []; if (model.object && model.object !== "model") return []; if (model.capabilities?.type && model.capabilities.type !== "chat") return []; return [id]; }); } catch { return []; } } /** * Enable GitHub Copilot models visible to this account. * Called after successful login to ensure available models are policy-enabled. */ async function enableAllGitHubCopilotModels(token, enterpriseDomain, onProgress) { const modelIds = await listGitHubCopilotModelIds(token, enterpriseDomain); await Promise.all(modelIds.map(async (modelId) => { const success = await enableGitHubCopilotModel(token, modelId, enterpriseDomain); onProgress?.(modelId, success); })); } /** * Login with GitHub Copilot OAuth (device code flow) * * @param options.onAuth - Callback with URL and optional instructions (user code) * @param options.onPrompt - Callback to prompt user for input * @param options.onProgress - Optional progress callback * @param options.signal - Optional AbortSignal for cancellation */ async function loginGitHubCopilot(options) { const input = await options.onPrompt({ message: "GitHub Enterprise URL/domain (blank for github.com)", placeholder: "company.ghe.com", allowEmpty: true }); if (options.signal?.aborted) throw new Error("Login cancelled"); const trimmed = input.trim(); const enterpriseDomain = normalizeDomain(input); if (trimmed && !enterpriseDomain) throw new Error("Invalid GitHub Enterprise URL/domain"); const domain = enterpriseDomain || "github.com"; const device = await startDeviceFlow(domain, { signal: options.signal }); options.onAuth(device.verification_uri, `Enter code: ${device.user_code}`); const credentials = await refreshGitHubCopilotToken(await pollForGitHubAccessToken(domain, device.device_code, device.intervalMs, device.expiresAt, options.signal), enterpriseDomain ?? void 0); options.onProgress?.("Enabling models..."); await enableAllGitHubCopilotModels(credentials.access, enterpriseDomain ?? void 0); return credentials; } const githubCopilotOAuthProvider = { id: "github-copilot", name: "GitHub Copilot", async login(callbacks) { return loginGitHubCopilot({ onAuth: (url, instructions) => callbacks.onAuth({ url, instructions }), onPrompt: callbacks.onPrompt, onProgress: callbacks.onProgress, signal: callbacks.signal }); }, async refreshToken(credentials) { const creds = credentials; return refreshGitHubCopilotToken(creds.refresh, creds.enterpriseUrl); }, getApiKey(credentials) { return credentials.access; }, modifyModels(models, credentials) { const creds = credentials; const domain = creds.enterpriseUrl ? normalizeDomain(creds.enterpriseUrl) ?? void 0 : void 0; const baseUrl = getGitHubCopilotBaseUrl(creds.access, domain); return models.map((m) => m.provider === "github-copilot" ? { ...m, baseUrl } : m); } }; //#endregion //#region src/llm/utils/oauth/openai-chatgpt.ts const OPENAI_CODEX_PROVIDER_ID = "openai"; function loadOpenAICodexOAuthFacade() { return loadActivatedBundledPluginPublicSurfaceModuleSync({ dirName: "openai", artifactBasename: "api.js" }); } function createLegacyRuntime(callbacks) { return { log: (message) => callbacks.onProgress?.(String(message)), error: (message) => callbacks.onProgress?.(String(message)), exit: (code) => { throw new Error(`exit:${code}`); } }; } function createLegacyPrompter(callbacks) { const progress = { update: (message) => callbacks.onProgress?.(message), stop: (message) => { if (message) callbacks.onProgress?.(message); } }; return { intro: async () => {}, outro: async () => {}, note: async (message) => callbacks.onProgress?.(message), select: async (params) => params.options[0]?.value, multiselect: async (params) => params.initialValues ?? [], text: async (prompt) => { return await withOAuthLoginAbort(callbacks.onPrompt({ message: prompt.message, placeholder: prompt.placeholder }), callbacks.signal); }, confirm: async () => false, progress: () => progress }; } async function refreshViaProviderRuntime(refreshToken) { const { refreshProviderOAuthCredentialWithPlugin } = await import("./plugins/provider-runtime.runtime.js"); const refreshed = await refreshProviderOAuthCredentialWithPlugin({ provider: OPENAI_CODEX_PROVIDER_ID, context: { type: "oauth", provider: OPENAI_CODEX_PROVIDER_ID, access: "", refresh: refreshToken, expires: 0 } }); if (!refreshed) return await loadOpenAICodexOAuthFacade().refreshOpenAICodexToken(refreshToken); const credentials = { ...refreshed }; delete credentials.type; delete credentials.provider; return credentials; } /** Runs the ChatGPT/Codex OAuth login flow and returns normalized credentials. */ async function loginOpenAICodex(callbacks) { throwIfOAuthLoginAborted(callbacks.signal); const { loginOpenAICodexOAuth } = await import("./provider-openai-chatgpt-oauth-DwAB-ZVf.js"); const manualCodeInput = callbacks.onManualCodeInput; const onManualCodeInput = manualCodeInput ? async () => await withOAuthLoginAbort(manualCodeInput(), callbacks.signal) : void 0; const credentials = await withOAuthLoginAbort(loginOpenAICodexOAuth({ prompter: createLegacyPrompter(callbacks), runtime: createLegacyRuntime(callbacks), isRemote: false, signal: callbacks.signal, onManualCodeInput, openUrl: async (url) => { throwIfOAuthLoginAborted(callbacks.signal); callbacks.onAuth({ url }); } }), callbacks.signal); if (!credentials) throw new Error("OpenAI Codex OAuth login did not return credentials."); return credentials; } /** Refreshes a ChatGPT/Codex OAuth token through the provider runtime or bundled facade. */ async function refreshOpenAICodexToken(refreshToken) { return await refreshViaProviderRuntime(refreshToken); } //#endregion //#region src/llm/utils/oauth/index.ts const BUILT_IN_OAUTH_PROVIDERS = [ anthropicOAuthProvider, githubCopilotOAuthProvider, { id: OPENAI_CODEX_PROVIDER_ID, name: "ChatGPT Plus/Pro (Codex Subscription)", usesCallbackServer: true, async login(callbacks) { return await loginOpenAICodex(callbacks); }, async refreshToken(credentials) { return await refreshOpenAICodexToken(credentials.refresh); }, getApiKey(credentials) { return credentials.access; } } ]; const oauthProviderRegistry = new Map(BUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider])); /** * Get an OAuth provider by ID */ function getOAuthProvider(id) { return oauthProviderRegistry.get(id); } /** * Register a custom OAuth provider */ function registerOAuthProvider(provider) { oauthProviderRegistry.set(provider.id, provider); } /** * Reset OAuth providers to built-ins. */ function resetOAuthProviders() { oauthProviderRegistry.clear(); for (const provider of BUILT_IN_OAUTH_PROVIDERS) oauthProviderRegistry.set(provider.id, provider); } /** * Get all registered OAuth providers */ function getOAuthProviders() { return Array.from(oauthProviderRegistry.values()); } /** * Get API key for a provider from OAuth credentials. * Automatically refreshes expired tokens. * * @returns API key string and updated credentials, or null if no credentials * @throws Error if refresh fails */ async function getOAuthApiKey(providerId, credentials) { const provider = getOAuthProvider(providerId); if (!provider) throw new Error(`Unknown OAuth provider: ${providerId}`); let creds = credentials[providerId]; if (!creds) return null; if (Date.now() >= creds.expires) try { creds = await provider.refreshToken(creds); } catch (error) { throw new Error(`Failed to refresh OAuth token for ${providerId}`, { cause: error }); } const apiKey = provider.getApiKey(creds); return { newCredentials: creds, apiKey }; } //#endregion export { resetOAuthProviders as a, registerOAuthProvider as i, getOAuthProvider as n, getOAuthProviders as r, getOAuthApiKey as t };