UNPKG

@earendil-works/pi-ai

Version:

Unified LLM API with automatic model discovery and provider configuration

177 lines • 7.63 kB
/** * Meta Model API OAuth flow * * RFC 8628 device authorization grant against https://auth.meta.com (JSON * responses). Meta splits identity from API access: the resulting identity * token is not accepted for inference, so it is exchanged for a Model API * key via the Muse Code key-mint endpoint (minted keys live about a day). * The identity token is stored as `refresh` and the minted key as `access`, * so the standard OAuth scheduler re-mints the key when it expires with no * bespoke renewal machinery. The identity token itself is not renewable * (auth.meta.com answers grant_type=refresh_token with 404 and issues no * refresh_token), so a 401/403 from mint means the session is dead and the * user must sign in again. */ import { pollOAuthDeviceCodeFlow } from "./device-code.js"; import { trustedHttpUrl } from "./trusted-url.js"; // Muse Code CLI client id. const CLIENT_ID = "1031625952748946"; const AUTH_HOST = "https://auth.meta.com"; const DEVICE_AUTHORIZATION_URL = `${AUTH_HOST}/oidc/device/authorization/`; const DEVICE_TOKEN_URL = `${AUTH_HOST}/oidc/device/token/`; const API_KEY_MINT_URL = "https://api.meta.ai/muse-code/key"; const API_KEY_LIFETIME_MS = 24 * 60 * 60 * 1000; const REQUEST_TIMEOUT_MS = 30 * 1000; function requestSignal(signal) { return AbortSignal.any([AbortSignal.timeout(REQUEST_TIMEOUT_MS), signal]); } async function readJson(response) { try { const json = await response.json(); return json && typeof json === "object" ? json : null; } catch { return null; } } function errorDetail(json) { for (const key of ["error_description", "detail", "message", "error"]) { const value = json?.[key]; if (typeof value === "string" && value.trim()) return `: ${value.trim()}`; } return ""; } function positiveNumber(value) { return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined; } async function startDeviceAuthorization(signal) { const response = await fetch(DEVICE_AUTHORIZATION_URL, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", }, body: new URLSearchParams({ client_id: CLIENT_ID }).toString(), signal: requestSignal(signal), }); const json = await readJson(response); if (!response.ok) { throw new Error(`Meta device authorization failed with status ${response.status}${errorDetail(json)}`); } const deviceCode = json?.device_code; const userCode = json?.user_code; const verificationUri = trustedHttpUrl(json?.verification_uri_complete) ?? trustedHttpUrl(json?.verification_uri); if (typeof deviceCode !== "string" || !deviceCode || typeof userCode !== "string" || !userCode || !verificationUri) { throw new Error(`Invalid Meta device authorization response: ${JSON.stringify(json)}`); } return { deviceCode, userCode, verificationUri, intervalSeconds: positiveNumber(json?.interval), expiresInSeconds: positiveNumber(json?.expires_in), }; } async function pollForIdentityToken(device, signal) { return pollOAuthDeviceCodeFlow({ intervalSeconds: device.intervalSeconds, expiresInSeconds: device.expiresInSeconds, waitBeforeFirstPoll: true, signal, poll: async () => { const response = await fetch(DEVICE_TOKEN_URL, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", }, body: new URLSearchParams({ grant_type: "urn:ietf:params:oauth:grant-type:device_code", device_code: device.deviceCode, client_id: CLIENT_ID, }).toString(), signal: requestSignal(signal), }); const json = await readJson(response); if (response.ok && typeof json?.access_token === "string" && json.access_token) { return { status: "complete", value: json.access_token }; } switch (json?.error) { case "authorization_pending": return { status: "pending" }; case "slow_down": return { status: "slow_down", intervalSeconds: positiveNumber(json?.interval) }; case "access_denied": return { status: "failed", message: "Meta login was denied." }; case "expired_token": return { status: "failed", message: "Meta device authorization expired. Please restart login." }; default: return { status: "failed", message: `Meta device token request failed with status ${response.status}${errorDetail(json)}`, }; } }, }); } /** Exchange an identity token for a Model API key. Keys are valid for about a day. */ async function mintApiKey(identityToken, signal) { const response = await fetch(API_KEY_MINT_URL, { method: "POST", headers: { Accept: "application/json", Authorization: `Bearer ${identityToken}`, "Content-Type": "application/json", "x-api-version": "1.0.0", }, body: "{}", signal: requestSignal(signal), }); const json = await readJson(response); if (response.status === 401 || response.status === 403) { // Identity token is not renewable (see file header); only a fresh device flow helps. throw new Error(`Meta session expired (status ${response.status}). Run \`/login meta\` to sign in again.${errorDetail(json)}`); } if (!response.ok) { throw new Error(`Meta API key mint failed with status ${response.status}${errorDetail(json)}`); } const apiKey = json?.api_key; if (typeof apiKey !== "string" || !apiKey) { const actionUrl = trustedHttpUrl(json?.action_url); throw new Error(`Meta did not issue an API key.${actionUrl ? ` Complete setup at ${actionUrl}` : ""}`); } return { type: "oauth", refresh: identityToken, access: apiKey, expires: Date.now() + API_KEY_LIFETIME_MS }; } async function loginMeta(interaction) { try { const device = await startDeviceAuthorization(interaction.signal); interaction.notify({ type: "device_code", userCode: device.userCode, verificationUri: device.verificationUri, intervalSeconds: device.intervalSeconds, expiresInSeconds: device.expiresInSeconds, }); const identityToken = await pollForIdentityToken(device, interaction.signal); interaction.notify({ type: "progress", message: "Enabling Meta Model API access..." }); return await mintApiKey(identityToken, interaction.signal); } catch (e) { // An in-flight fetch rejects with a DOMException on abort; the login UI matches on this message. if (interaction.signal.aborted) throw new Error("Login cancelled"); throw e; } } export const metaOAuth = { name: "Meta (Muse subscription)", isSubscription: true, loginLabel: "Sign in with Meta", login: loginMeta, refresh: (credential, signal) => mintApiKey(credential.refresh, signal), async toAuth(credential) { return { apiKey: credential.access }; }, }; //# sourceMappingURL=meta.js.map