import { i as getModels } from "./models-k4kEajYR.js";
//#region node_modules/@mariozechner/pi-ai/dist/utils/oauth/oauth-page.js
const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" aria-hidden="true"><path fill="#fff" fill-rule="evenodd" d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"/><path fill="#fff" d="M517.36 400 H634.72 V634.72 H517.36 Z"/></svg>`;
function escapeHtml(value) {
	return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&#39;");
}
function renderPage(options) {
	const title = escapeHtml(options.title);
	const heading = escapeHtml(options.heading);
	const message = escapeHtml(options.message);
	const details = options.details ? escapeHtml(options.details) : void 0;
	return `<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>${title}</title>
  <style>
    :root {
      --text: #fafafa;
      --text-dim: #a1a1aa;
      --page-bg: #09090b;
      --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
      --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
    }
    * { box-sizing: border-box; }
    html { color-scheme: dark; }
    body {
      margin: 0;
      min-height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 24px;
      background: var(--page-bg);
      color: var(--text);
      font-family: var(--font-sans);
      text-align: center;
    }
    main {
      width: 100%;
      max-width: 560px;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
    }
    .logo {
      width: 72px;
      height: 72px;
      display: block;
      margin-bottom: 24px;
    }
    h1 {
      margin: 0 0 10px;
      font-size: 28px;
      line-height: 1.15;
      font-weight: 650;
      color: var(--text);
    }
    p {
      margin: 0;
      line-height: 1.7;
      color: var(--text-dim);
      font-size: 15px;
    }
    .details {
      margin-top: 16px;
      font-family: var(--font-mono);
      font-size: 13px;
      color: var(--text-dim);
      white-space: pre-wrap;
      word-break: break-word;
    }
  </style>
</head>
<body>
  <main>
    <div class="logo">${LOGO_SVG}</div>
    <h1>${heading}</h1>
    <p>${message}</p>
    ${details ? `<div class="details">${details}</div>` : ""}
  </main>
</body>
</html>`;
}
function oauthSuccessHtml(message) {
	return renderPage({
		title: "Authentication successful",
		heading: "Authentication successful",
		message
	});
}
function oauthErrorHtml(message, details) {
	return renderPage({
		title: "Authentication failed",
		heading: "Authentication failed",
		message,
		details
	});
}
//#endregion
//#region node_modules/@mariozechner/pi-ai/dist/utils/oauth/pkce.js
/**
* PKCE utilities using Web Crypto API.
* Works in both Node.js 20+ and browsers.
*/
/**
* Encode bytes as base64url string.
*/
function base64urlEncode(bytes) {
	let binary = "";
	for (const byte of bytes) binary += String.fromCharCode(byte);
	return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}
/**
* Generate PKCE code verifier and challenge.
* Uses Web Crypto API for cross-platform compatibility.
*/
async function generatePKCE() {
	const verifierBytes = new Uint8Array(32);
	crypto.getRandomValues(verifierBytes);
	const verifier = base64urlEncode(verifierBytes);
	const data = new TextEncoder().encode(verifier);
	const hashBuffer = await crypto.subtle.digest("SHA-256", data);
	return {
		verifier,
		challenge: base64urlEncode(new Uint8Array(hashBuffer))
	};
}
//#endregion
//#region node_modules/@mariozechner/pi-ai/dist/utils/oauth/anthropic.js
/**
* Anthropic OAuth flow (Claude Pro/Max)
*
* NOTE: This module uses Node.js http.createServer for the OAuth callback server.
* It is only intended for CLI use, not browser environments.
*/
let nodeApis = null;
let nodeApisPromise = null;
const decode$1 = (s) => atob(s);
const CLIENT_ID$2 = decode$1("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
const AUTHORIZE_URL$1 = "https://claude.ai/oauth/authorize";
const TOKEN_URL$1 = "https://platform.claude.com/v1/oauth/token";
const CALLBACK_HOST$1 = process.env.PI_OAUTH_CALLBACK_HOST || "127.0.0.1";
const CALLBACK_PORT = 53692;
const CALLBACK_PATH = "/callback";
const REDIRECT_URI$1 = `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 parseAuthorizationInput$1(input) {
	const value = input.trim();
	if (!value) return {};
	try {
		const url = new URL(value);
		return {
			code: url.searchParams.get("code") ?? void 0,
			state: url.searchParams.get("state") ?? void 0
		};
	} catch {}
	if (value.includes("#")) {
		const [code, state] = value.split("#", 2);
		return {
			code,
			state
		};
	}
	if (value.includes("code=")) {
		const params = new URLSearchParams(value);
		return {
			code: params.get("code") ?? void 0,
			state: params.get("state") ?? void 0
		};
	}
	return { code: value };
}
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 (typeof errorWithCode.errno !== "undefined") details.push(`errno=${String(errorWithCode.errno)}`);
		if (typeof error.cause !== "undefined") details.push(`cause=${formatErrorDetails(error.cause)}`);
		if (error.stack) details.push(`stack=${error.stack}`);
		return details.join("; ");
	}
	return String(error);
}
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$1, () => {
			resolve({
				server,
				redirectUri: REDIRECT_URI$1,
				cancelWait: () => {
					settleWait?.(null);
				},
				waitForCode: () => waitForCodePromise
			});
		});
	});
}
async function postJson(url, body) {
	const response = await fetch(url, {
		method: "POST",
		headers: {
			"Content-Type": "application/json",
			Accept: "application/json"
		},
		body: JSON.stringify(body),
		signal: AbortSignal.timeout(3e4)
	});
	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$1(code, state, verifier, redirectUri) {
	let responseBody;
	try {
		responseBody = await postJson(TOKEN_URL$1, {
			grant_type: "authorization_code",
			client_id: CLIENT_ID$2,
			code,
			state,
			redirect_uri: redirectUri,
			code_verifier: verifier
		});
	} catch (error) {
		throw new Error(`Token exchange request failed. url=${TOKEN_URL$1}; redirect_uri=${redirectUri}; response_type=authorization_code; details=${formatErrorDetails(error)}`);
	}
	let tokenData;
	try {
		tokenData = JSON.parse(responseBody);
	} catch (error) {
		throw new Error(`Token exchange returned invalid JSON. url=${TOKEN_URL$1}; body=${responseBody}; details=${formatErrorDetails(error)}`);
	}
	return {
		refresh: tokenData.refresh_token,
		access: tokenData.access_token,
		expires: Date.now() + tokenData.expires_in * 1e3 - 300 * 1e3
	};
}
/**
* Login with Anthropic OAuth (authorization code + PKCE)
*/
async function loginAnthropic(options) {
	const { verifier, challenge } = await generatePKCE();
	const server = await startCallbackServer(verifier);
	let code;
	let state;
	let redirectUriForExchange = REDIRECT_URI$1;
	try {
		const authParams = new URLSearchParams({
			code: "true",
			client_id: CLIENT_ID$2,
			response_type: "code",
			redirect_uri: REDIRECT_URI$1,
			scope: SCOPES,
			code_challenge: challenge,
			code_challenge_method: "S256",
			state: verifier
		});
		options.onAuth({
			url: `${AUTHORIZE_URL$1}?${authParams.toString()}`,
			instructions: "Complete login in your browser. If the browser is on another machine, paste the final redirect URL here."
		});
		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 server.waitForCode();
			if (manualError) throw manualError;
			if (result?.code) {
				code = result.code;
				state = result.state;
				redirectUriForExchange = REDIRECT_URI$1;
			} else if (manualInput) {
				const parsed = parseAuthorizationInput$1(manualInput);
				if (parsed.state && parsed.state !== verifier) throw new Error("OAuth state mismatch");
				code = parsed.code;
				state = parsed.state ?? verifier;
			}
			if (!code) {
				await manualPromise;
				if (manualError) throw manualError;
				if (manualInput) {
					const parsed = parseAuthorizationInput$1(manualInput);
					if (parsed.state && parsed.state !== verifier) throw new Error("OAuth state mismatch");
					code = parsed.code;
					state = parsed.state ?? verifier;
				}
			}
		} else {
			const result = await server.waitForCode();
			if (result?.code) {
				code = result.code;
				state = result.state;
				redirectUriForExchange = REDIRECT_URI$1;
			}
		}
		if (!code) {
			const parsed = parseAuthorizationInput$1(await options.onPrompt({
				message: "Paste the authorization code or full redirect URL:",
				placeholder: REDIRECT_URI$1
			}));
			if (parsed.state && parsed.state !== verifier) throw new Error("OAuth state mismatch");
			code = parsed.code;
			state = parsed.state ?? verifier;
		}
		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$1(code, state, verifier, redirectUriForExchange);
	} finally {
		server.server.close();
	}
}
/**
* Refresh Anthropic OAuth token
*/
async function refreshAnthropicToken(refreshToken) {
	let responseBody;
	try {
		responseBody = await postJson(TOKEN_URL$1, {
			grant_type: "refresh_token",
			client_id: CLIENT_ID$2,
			refresh_token: refreshToken
		});
	} catch (error) {
		throw new Error(`Anthropic token refresh request failed. url=${TOKEN_URL$1}; details=${formatErrorDetails(error)}`);
	}
	let data;
	try {
		data = JSON.parse(responseBody);
	} catch (error) {
		throw new Error(`Anthropic token refresh returned invalid JSON. url=${TOKEN_URL$1}; body=${responseBody}; details=${formatErrorDetails(error)}`);
	}
	return {
		refresh: data.refresh_token,
		access: data.access_token,
		expires: Date.now() + data.expires_in * 1e3 - 300 * 1e3
	};
}
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
		});
	},
	async refreshToken(credentials) {
		return refreshAnthropicToken(credentials.refresh);
	},
	getApiKey(credentials) {
		return credentials.access;
	}
};
//#endregion
//#region node_modules/@mariozechner/pi-ai/dist/utils/oauth/github-copilot.js
/**
* GitHub Copilot OAuth flow
*/
const decode = (s) => atob(s);
const CLIENT_ID$1 = 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;
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";
}
async function fetchJson(url, init) {
	const response = await fetch(url, init);
	if (!response.ok) {
		const text = await response.text();
		throw new Error(`${response.status} ${response.statusText}: ${text}`);
	}
	return response.json();
}
async function startDeviceFlow(domain) {
	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$1,
			scope: "read:user"
		})
	});
	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 expiresIn = data.expires_in;
	if (typeof deviceCode !== "string" || typeof userCode !== "string" || typeof verificationUri !== "string" || typeof interval !== "number" || typeof expiresIn !== "number") throw new Error("Invalid device code response fields");
	return {
		device_code: deviceCode,
		user_code: userCode,
		verification_uri: verificationUri,
		interval,
		expires_in: expiresIn
	};
}
/**
* 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, intervalSeconds, expiresIn, signal) {
	const urls = getUrls(domain);
	const deadline = Date.now() + expiresIn * 1e3;
	let intervalMs = Math.max(1e3, Math.floor(intervalSeconds * 1e3));
	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(intervalMs * 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$1,
				device_code: deviceCode,
				grant_type: "urn:ietf:params:oauth:grant-type:device_code"
			})
		});
		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;
				intervalMs = typeof interval === "number" && interval > 0 ? interval * 1e3 : Math.max(1e3, intervalMs + 5e3);
				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) {
	const raw = await fetchJson(getUrls(enterpriseDomain || "github.com").copilotTokenUrl, { headers: {
		Accept: "application/json",
		Authorization: `Bearer ${refreshToken}`,
		...COPILOT_HEADERS
	} });
	if (!raw || typeof raw !== "object") throw new Error("Invalid Copilot token response");
	const token = raw.token;
	const expiresAt = raw.expires_at;
	if (typeof token !== "string" || typeof expiresAt !== "number") throw new Error("Invalid Copilot token response fields");
	return {
		refresh: refreshToken,
		access: token,
		expires: expiresAt * 1e3 - 300 * 1e3,
		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) {
	const url = `${getGitHubCopilotBaseUrl(token, enterpriseDomain)}/models/${modelId}/policy`;
	try {
		return (await fetch(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" })
		})).ok;
	} catch {
		return false;
	}
}
/**
* Enable all known GitHub Copilot models that may require policy acceptance.
* Called after successful login to ensure all models are available.
*/
async function enableAllGitHubCopilotModels(token, enterpriseDomain, onProgress) {
	const models = getModels("github-copilot");
	await Promise.all(models.map(async (model) => {
		const success = await enableGitHubCopilotModel(token, model.id, enterpriseDomain);
		onProgress?.(model.id, 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);
	options.onAuth(device.verification_uri, `Enter code: ${device.user_code}`);
	const credentials = await refreshGitHubCopilotToken(await pollForGitHubAccessToken(domain, device.device_code, device.interval, device.expires_in, 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 node_modules/@mariozechner/pi-ai/dist/utils/oauth/openai-codex.js
/**
* 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.
*/
let _randomBytes = null;
let _http = null;
if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
	import("node:crypto").then((m) => {
		_randomBytes = m.randomBytes;
	});
	import("node:http").then((m) => {
		_http = m;
	});
}
const CALLBACK_HOST = process.env.PI_OAUTH_CALLBACK_HOST || "127.0.0.1";
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
const TOKEN_URL = "https://auth.openai.com/oauth/token";
const REDIRECT_URI = "http://localhost:1455/auth/callback";
const SCOPE = "openid profile email offline_access";
const JWT_CLAIM_PATH = "https://api.openai.com/auth";
function createState() {
	if (!_randomBytes) throw new Error("OpenAI Codex OAuth is only available in Node.js environments");
	return _randomBytes(16).toString("hex");
}
function parseAuthorizationInput(input) {
	const value = input.trim();
	if (!value) return {};
	try {
		const url = new URL(value);
		return {
			code: url.searchParams.get("code") ?? void 0,
			state: url.searchParams.get("state") ?? void 0
		};
	} catch {}
	if (value.includes("#")) {
		const [code, state] = value.split("#", 2);
		return {
			code,
			state
		};
	}
	if (value.includes("code=")) {
		const params = new URLSearchParams(value);
		return {
			code: params.get("code") ?? void 0,
			state: params.get("state") ?? void 0
		};
	}
	return { code: value };
}
function decodeJwt(token) {
	try {
		const parts = token.split(".");
		if (parts.length !== 3) return null;
		const payload = parts[1] ?? "";
		const decoded = atob(payload);
		return JSON.parse(decoded);
	} catch {
		return null;
	}
}
async function exchangeAuthorizationCode(code, verifier, redirectUri = REDIRECT_URI) {
	const response = await fetch(TOKEN_URL, {
		method: "POST",
		headers: { "Content-Type": "application/x-www-form-urlencoded" },
		body: new URLSearchParams({
			grant_type: "authorization_code",
			client_id: CLIENT_ID,
			code,
			code_verifier: verifier,
			redirect_uri: redirectUri
		})
	});
	if (!response.ok) {
		const text = await response.text().catch(() => "");
		console.error("[openai-codex] code->token failed:", response.status, text);
		return { type: "failed" };
	}
	const json = await response.json();
	if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
		console.error("[openai-codex] token response missing fields:", json);
		return { type: "failed" };
	}
	return {
		type: "success",
		access: json.access_token,
		refresh: json.refresh_token,
		expires: Date.now() + json.expires_in * 1e3
	};
}
async function refreshAccessToken(refreshToken) {
	try {
		const response = await fetch(TOKEN_URL, {
			method: "POST",
			headers: { "Content-Type": "application/x-www-form-urlencoded" },
			body: new URLSearchParams({
				grant_type: "refresh_token",
				refresh_token: refreshToken,
				client_id: CLIENT_ID
			})
		});
		if (!response.ok) {
			const text = await response.text().catch(() => "");
			console.error("[openai-codex] Token refresh failed:", response.status, text);
			return { type: "failed" };
		}
		const json = await response.json();
		if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
			console.error("[openai-codex] Token refresh response missing fields:", json);
			return { type: "failed" };
		}
		return {
			type: "success",
			access: json.access_token,
			refresh: json.refresh_token,
			expires: Date.now() + json.expires_in * 1e3
		};
	} catch (error) {
		console.error("[openai-codex] Token refresh error:", error);
		return { type: "failed" };
	}
}
async function createAuthorizationFlow(originator = "pi") {
	const { verifier, challenge } = await generatePKCE();
	const state = createState();
	const url = new URL(AUTHORIZE_URL);
	url.searchParams.set("response_type", "code");
	url.searchParams.set("client_id", CLIENT_ID);
	url.searchParams.set("redirect_uri", REDIRECT_URI);
	url.searchParams.set("scope", SCOPE);
	url.searchParams.set("code_challenge", challenge);
	url.searchParams.set("code_challenge_method", "S256");
	url.searchParams.set("state", state);
	url.searchParams.set("id_token_add_organizations", "true");
	url.searchParams.set("codex_cli_simplified_flow", "true");
	url.searchParams.set("originator", originator);
	return {
		verifier,
		state,
		url: url.toString()
	};
}
function startLocalOAuthServer(state) {
	if (!_http) throw new Error("OpenAI Codex OAuth is only available in Node.js environments");
	let settleWait;
	const waitForCodePromise = new Promise((resolve) => {
		let settled = false;
		settleWait = (value) => {
			if (settled) return;
			settled = true;
			resolve(value);
		};
	});
	const server = _http.createServer((req, res) => {
		try {
			const url = new URL(req.url || "", "http://localhost");
			if (url.pathname !== "/auth/callback") {
				res.statusCode = 404;
				res.setHeader("Content-Type", "text/html; charset=utf-8");
				res.end(oauthErrorHtml("Callback route not found."));
				return;
			}
			if (url.searchParams.get("state") !== state) {
				res.statusCode = 400;
				res.setHeader("Content-Type", "text/html; charset=utf-8");
				res.end(oauthErrorHtml("State mismatch."));
				return;
			}
			const code = url.searchParams.get("code");
			if (!code) {
				res.statusCode = 400;
				res.setHeader("Content-Type", "text/html; charset=utf-8");
				res.end(oauthErrorHtml("Missing authorization code."));
				return;
			}
			res.statusCode = 200;
			res.setHeader("Content-Type", "text/html; charset=utf-8");
			res.end(oauthSuccessHtml("OpenAI authentication completed. You can close this window."));
			settleWait?.({ code });
		} catch {
			res.statusCode = 500;
			res.setHeader("Content-Type", "text/html; charset=utf-8");
			res.end(oauthErrorHtml("Internal error while processing OAuth callback."));
		}
	});
	return new Promise((resolve) => {
		server.listen(1455, CALLBACK_HOST, () => {
			resolve({
				close: () => server.close(),
				cancelWait: () => {
					settleWait?.(null);
				},
				waitForCode: () => waitForCodePromise
			});
		}).on("error", (err) => {
			console.error(`[openai-codex] Failed to bind http://${CALLBACK_HOST}:1455 (`, err.code, ") Falling back to manual paste.");
			settleWait?.(null);
			resolve({
				close: () => {
					try {
						server.close();
					} catch {}
				},
				cancelWait: () => {},
				waitForCode: async () => null
			});
		});
	});
}
function getAccountId(accessToken) {
	const accountId = (decodeJwt(accessToken)?.[JWT_CLAIM_PATH])?.chatgpt_account_id;
	return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
}
/**
* 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 "pi")
*/
async function loginOpenAICodex(options) {
	const { verifier, state, url } = await createAuthorizationFlow(options.originator);
	const server = await startLocalOAuthServer(state);
	options.onAuth({
		url,
		instructions: "A browser window should open. Complete login to finish."
	});
	let code;
	try {
		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 server.waitForCode();
			if (manualError) throw manualError;
			if (result?.code) code = result.code;
			else if (manualCode) {
				const parsed = parseAuthorizationInput(manualCode);
				if (parsed.state && parsed.state !== state) throw new Error("State mismatch");
				code = parsed.code;
			}
			if (!code) {
				await manualPromise;
				if (manualError) throw manualError;
				if (manualCode) {
					const parsed = parseAuthorizationInput(manualCode);
					if (parsed.state && parsed.state !== state) throw new Error("State mismatch");
					code = parsed.code;
				}
			}
		} else {
			const result = await server.waitForCode();
			if (result?.code) code = result.code;
		}
		if (!code) {
			const parsed = parseAuthorizationInput(await options.onPrompt({ message: "Paste the authorization code (or full redirect URL):" }));
			if (parsed.state && parsed.state !== state) throw new Error("State mismatch");
			code = parsed.code;
		}
		if (!code) throw new Error("Missing authorization code");
		const tokenResult = await exchangeAuthorizationCode(code, verifier);
		if (tokenResult.type !== "success") throw new Error("Token exchange failed");
		const accountId = getAccountId(tokenResult.access);
		if (!accountId) throw new Error("Failed to extract accountId from token");
		return {
			access: tokenResult.access,
			refresh: tokenResult.refresh,
			expires: tokenResult.expires,
			accountId
		};
	} finally {
		server.close();
	}
}
/**
* Refresh OpenAI Codex OAuth token
*/
async function refreshOpenAICodexToken(refreshToken) {
	const result = await refreshAccessToken(refreshToken);
	if (result.type !== "success") throw new Error("Failed to refresh OpenAI Codex token");
	const accountId = getAccountId(result.access);
	if (!accountId) throw new Error("Failed to extract accountId from token");
	return {
		access: result.access,
		refresh: result.refresh,
		expires: result.expires,
		accountId
	};
}
const oauthProviderRegistry = new Map([
	anthropicOAuthProvider,
	githubCopilotOAuthProvider,
	{
		id: "openai-codex",
		name: "ChatGPT Plus/Pro (Codex Subscription)",
		usesCallbackServer: true,
		async login(callbacks) {
			return loginOpenAICodex({
				onAuth: callbacks.onAuth,
				onPrompt: callbacks.onPrompt,
				onProgress: callbacks.onProgress,
				onManualCodeInput: callbacks.onManualCodeInput
			});
		},
		async refreshToken(credentials) {
			return refreshOpenAICodexToken(credentials.refresh);
		},
		getApiKey(credentials) {
			return credentials.access;
		}
	}
].map((provider) => [provider.id, provider]));
/**
* Get an OAuth provider by ID
*/
function getOAuthProvider(id) {
	return oauthProviderRegistry.get(id);
}
/**
* 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}`);
	}
	const apiKey = provider.getApiKey(creds);
	return {
		newCredentials: creds,
		apiKey
	};
}
//#endregion
export { refreshOpenAICodexToken as i, getOAuthProviders as n, loginOpenAICodex as r, getOAuthApiKey as t };
