diff --git a/dist/constants.d.ts b/dist/constants.d.ts deleted file mode 100644 index 0a2e43f..0000000 --- a/dist/constants.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** The provider id Meta's Responses adapter registers under. */ -export declare const META_PROVIDER = "meta"; -export declare const META_CLIENT_ID = "1031625952748946"; -export declare const META_AUTH_HOST = "https://auth.meta.com"; -export declare const META_DEVICE_AUTHORIZATION_URL = "https://auth.meta.com/oidc/device/authorization/"; -export declare const META_DEVICE_TOKEN_URL = "https://auth.meta.com/oidc/device/token/"; -/** Exchanges a Meta identity token for a short-lived Model API key. */ -export declare const META_API_KEY_MINT_URL = "https://api.meta.ai/muse-code/key"; -/** Base URL for inference against Meta's Model API (Responses endpoint). */ -export declare const META_BASE_URL = "https://api.meta.ai/v1"; -/** Env var holding a plain Meta Model API key (`LLM|...` or equivalent). */ -export declare const META_API_KEY_ENV = "META_API_KEY"; -export declare const META_API_KEY_LIFETIME_MS: number; -export declare const META_DEFAULT_MODELS: readonly ["muse-spark-1.3", "muse-spark-1.2", "muse-spark-1.1", "muse-spark-1.2-contributor", "muse-spark-1.3-contributor"]; -export declare const META_RESPONSES_PATH = "/responses"; -export declare const META_TOKEN_TIMEOUT_MS = 30000; -/** `InferenceOptions.providerOptions` key carrying the reasoning effort. */ -export declare const META_REASONING_EFFORT_OPTION = "metaReasoningEffort"; diff --git a/dist/constants.js b/dist/constants.js deleted file mode 100644 index fa73a89..0000000 --- a/dist/constants.js +++ /dev/null @@ -1,34 +0,0 @@ -/** The provider id Meta's Responses adapter registers under. */ -export const META_PROVIDER = "meta"; -// Muse Code CLI client id (pi-pinned). -export const META_CLIENT_ID = "1031625952748946"; -export const META_AUTH_HOST = "https://auth.meta.com"; -export const META_DEVICE_AUTHORIZATION_URL = `${META_AUTH_HOST}/oidc/device/authorization/`; -export const META_DEVICE_TOKEN_URL = `${META_AUTH_HOST}/oidc/device/token/`; -/** Exchanges a Meta identity token for a short-lived Model API key. */ -export const META_API_KEY_MINT_URL = "https://api.meta.ai/muse-code/key"; -/** Base URL for inference against Meta's Model API (Responses endpoint). */ -export const META_BASE_URL = "https://api.meta.ai/v1"; -/** Env var holding a plain Meta Model API key (`LLM|...` or equivalent). */ -export const META_API_KEY_ENV = "META_API_KEY"; -// Client-side expiry policy for minted Model API keys: we hard-expire at -// `now + 24h`. This is NOT a server contract — Meta guarantees only "about -// 24h" for key lifetime, and the authorize response's `expires_in` is -// intentionally not used for the minted key's lifetime (see -// `META_API_KEY_LIFETIME_MS` usage in device-flow mint). -export const META_API_KEY_LIFETIME_MS = 24 * 60 * 60 * 1000; -// Order matches an authenticated GET /v1/models from Muse Code; the first -// entry is the host default. -export const META_DEFAULT_MODELS = [ - "muse-spark-1.3", - "muse-spark-1.2", - "muse-spark-1.1", - "muse-spark-1.2-contributor", - "muse-spark-1.3-contributor", -]; -export const META_RESPONSES_PATH = "/responses"; -// Bounds each device-flow / mint request so a stalled endpoint aborts rather -// than hanging the caller. -export const META_TOKEN_TIMEOUT_MS = 30_000; -/** `InferenceOptions.providerOptions` key carrying the reasoning effort. */ -export const META_REASONING_EFFORT_OPTION = "metaReasoningEffort"; diff --git a/dist/device-flow.d.ts b/dist/device-flow.d.ts deleted file mode 100644 index dfdae80..0000000 --- a/dist/device-flow.d.ts +++ /dev/null @@ -1,66 +0,0 @@ -export declare const META_DEVICE_FLOW_CANCEL_MESSAGE = "Login cancelled"; -/** A device authorization issued by Meta's RFC 8628 endpoint. */ -export type MetaDeviceAuthorization = { - deviceCode: string; - userCode: string; - verificationUri: string; - intervalSeconds?: number; - /** - * The authorize response's `expires_in` (RFC 8628 device-code lifetime). - * Surfaced for polling deadlines; it intentionally does NOT bound the - * minted Model API key's lifetime (that is `META_API_KEY_LIFETIME_MS`, - * a client-side policy — see {@link mintMetaApiKey}). - */ - expiresInSeconds?: number; -}; -/** Injectable clock so tests control the expiry baseline. */ -export type NowFn = () => number; -/** Injectable sleep so tests skip real wall-clock waits. */ -export type SleepFn = (ms: number, signal: AbortSignal) => Promise; -/** FetchLike mirrors `fetch`'s callable shape without importing it. */ -export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise; -/** - * Starts an RFC 8628 device authorization against Meta. Returns the device - * code, user code, and the trusted http(s) verification URI the user must - * open. Aborts with `META_DEVICE_FLOW_CANCEL_MESSAGE` when `signal` aborts. - */ -export declare function startMetaDeviceAuthorization(signal: AbortSignal, fetchImpl?: FetchLike): Promise; -export type DeviceTokenPollResult = { - status: "pending"; -} | { - status: "slow_down"; - intervalSeconds?: number; -} | { - status: "failed"; - message: string; -} | { - status: "complete"; - value: string; -}; -export type PollMetaDeviceTokenOptions = { - now?: NowFn; - sleep?: SleepFn; -}; -/** - * Polls Meta's device token endpoint until the user approves. Returns the - * identity token (`access_token`), which is NOT usable for inference — it - * must be exchanged for a Model API key via {@link mintMetaApiKey}. Handles - * `authorization_pending`, `slow_down`, `access_denied`, and `expired_token`. - * Aborts with `META_DEVICE_FLOW_CANCEL_MESSAGE` when `signal` aborts at any - * point during polling — during the initial wait, a poll interval sleep, or - * an in-flight token-poll request. - */ -export declare function pollMetaDeviceToken(device: MetaDeviceAuthorization, signal: AbortSignal, fetchImpl?: FetchLike, opts?: PollMetaDeviceTokenOptions): Promise; -export type MintMetaApiKeyResult = { - apiKey: string; - expiresAt: number; -}; -export type MintMetaApiKeyOptions = { - now?: NowFn; -}; -/** - * Exchanges a Meta identity token for a short-lived Model API key - * (`LLM|...`), valid about a day. 401/403 means the identity token is dead - * and the session must be restarted with a fresh device login. - */ -export declare function mintMetaApiKey(identityToken: string, signal: AbortSignal, fetchImpl?: FetchLike, opts?: MintMetaApiKeyOptions): Promise; diff --git a/dist/device-flow.js b/dist/device-flow.js deleted file mode 100644 index bcf6e67..0000000 --- a/dist/device-flow.js +++ /dev/null @@ -1,332 +0,0 @@ -import { type } from "arktype"; -import { META_API_KEY_LIFETIME_MS, META_API_KEY_MINT_URL, META_CLIENT_ID, META_DEVICE_AUTHORIZATION_URL, META_DEVICE_TOKEN_URL, META_TOKEN_TIMEOUT_MS, } from "./constants.js"; -export const META_DEVICE_FLOW_CANCEL_MESSAGE = "Login cancelled"; -const TIMEOUT_MESSAGE = "Device flow timed out"; -const SLOW_DOWN_TIMEOUT_MESSAGE = "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."; -const MINIMUM_INTERVAL_MS = 1000; -// RFC 8628 section 3.2: if the authorization server omits `interval`, the client must use 5 seconds. -const DEFAULT_POLL_INTERVAL_SECONDS = 5; -// RFC 8628 section 3.5: `slow_down` means the polling interval must increase by 5 seconds. -const SLOW_DOWN_INTERVAL_INCREMENT_MS = 5000; -// The device authorization endpoint's JSON payload. Unknown keys are -// rejected: a renamed `device_code` field must fail loudly rather than be -// silently treated as absent. -const DeviceAuthorizationPayload = type({ - device_code: "string > 0", - user_code: "string > 0", - "verification_uri?": "string", - "verification_uri_complete?": "string", - "interval?": "number", - "expires_in?": "number", - "+": "reject", -}); -// The token polling endpoint's JSON payload — either an access_token on -// success or an RFC 8628 error body. Fields are permissive here: the -// success/failure branch is decided by `error` presence, and both shapes -// carry extra fields the other branch does not. -const DeviceTokenPayload = type({ - "access_token?": "string", - "error?": "string", - "error_description?": "string", - "interval?": "number", -}); -// The mint endpoint's JSON payload. `action_url` is nullable: Meta sends it -// only when a setup step is outstanding, otherwise `null` is returned. The -// error detail keys are permissive because the exact failure shape varies. -const MintPayload = type({ - "api_key?": "string", - "action_url?": "string | null", - "error_description?": "string", - "detail?": "string", - "message?": "string", - "error?": "string", -}); -function requestSignal(signal) { - return AbortSignal.any([AbortSignal.timeout(META_TOKEN_TIMEOUT_MS), signal]); -} -async function readJson(response) { - try { - const json = await response.json(); - return json !== null && 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 ""; -} -/** The verification URI is opened in the user's browser; only http(s) URLs are trusted. */ -function trustedHttpUrl(value) { - if (typeof value !== "string" || !value) - return null; - try { - const url = new URL(value); - if (url.protocol !== "https:" && url.protocol !== "http:") - return null; - return url.href; - } - catch { - return null; - } -} -function positiveNumber(value) { - return typeof value === "number" && Number.isFinite(value) && value > 0 - ? value - : undefined; -} -/** - * Starts an RFC 8628 device authorization against Meta. Returns the device - * code, user code, and the trusted http(s) verification URI the user must - * open. Aborts with `META_DEVICE_FLOW_CANCEL_MESSAGE` when `signal` aborts. - */ -export async function startMetaDeviceAuthorization(signal, fetchImpl = fetch) { - let response; - try { - response = await fetchImpl(META_DEVICE_AUTHORIZATION_URL, { - method: "POST", - headers: { - "content-type": "application/x-www-form-urlencoded", - accept: "application/json", - }, - body: new URLSearchParams({ client_id: META_CLIENT_ID }).toString(), - signal: requestSignal(signal), - }); - } - catch (err) { - // An in-flight fetch rejects with a DOMException on abort; the login UI - // matches on this message. - if (signal.aborted) { - throw new Error(META_DEVICE_FLOW_CANCEL_MESSAGE, { cause: err }); - } - throw err; - } - const json = await readJson(response); - if (!response.ok) { - throw new Error(`Meta device authorization failed with status ${response.status}${errorDetail(json)}`); - } - const parsed = DeviceAuthorizationPayload(json ?? {}); - if (parsed instanceof type.errors) { - throw new Error(`Invalid Meta device authorization response: ${parsed.summary}`); - } - const verificationUri = trustedHttpUrl(parsed.verification_uri_complete) ?? - trustedHttpUrl(parsed.verification_uri); - if (verificationUri === null) { - throw new Error(`Invalid Meta device authorization response: missing trusted verification_uri`); - } - const intervalSeconds = positiveNumber(parsed.interval); - // The authorize `expires_in` bounds only the device-code polling window; - // it does not bound the minted Model API key's lifetime. The mint response - // carries no explicit lifetime, so the stored credential expires at - // now + META_API_KEY_LIFETIME_MS (see mintMetaApiKey). - const expiresInSeconds = positiveNumber(parsed.expires_in); - return { - deviceCode: parsed.device_code, - userCode: parsed.user_code, - verificationUri, - ...(intervalSeconds !== undefined ? { intervalSeconds } : {}), - ...(expiresInSeconds !== undefined ? { expiresInSeconds } : {}), - }; -} -function abortableSleep(ms, signal, cancelMessage, sleep) { - return sleep(ms, signal).then(() => undefined, (err) => { - if (signal.aborted) - throw new Error(cancelMessage); - throw err; - }); -} -async function pollOAuthDeviceCodeFlow(options) { - const deadline = typeof options.expiresInSeconds === "number" - ? options.now() + options.expiresInSeconds * 1000 - : Number.POSITIVE_INFINITY; - let intervalMs = Math.max(MINIMUM_INTERVAL_MS, Math.floor((options.intervalSeconds ?? DEFAULT_POLL_INTERVAL_SECONDS) * 1000)); - let slowDownResponses = 0; - if (options.waitBeforeFirstPoll === true) { - const remainingMs = deadline - options.now(); - if (remainingMs > 0) { - await abortableSleep(Math.min(intervalMs, remainingMs), options.signal, META_DEVICE_FLOW_CANCEL_MESSAGE, options.sleep); - } - } - while (options.now() < deadline) { - if (options.signal.aborted) { - throw new Error(META_DEVICE_FLOW_CANCEL_MESSAGE); - } - let result; - try { - result = await options.poll(); - } - catch (err) { - // An in-flight fetch rejects with a DOMException on abort; the login - // UI matches on this message. This keeps the abort contract - // deterministic regardless of whether the abort lands during sleep or - // during the token-poll fetch. - if (options.signal.aborted) { - throw new Error(META_DEVICE_FLOW_CANCEL_MESSAGE, { cause: err }); - } - throw err; - } - if (result.status === "complete") { - return result.value; - } - if (result.status === "failed") { - throw new Error(result.message); - } - if (result.status === "slow_down") { - slowDownResponses += 1; - // Use the server-provided interval when given; otherwise apply RFC 8628 - // section 3.5: increase by 5 seconds. - intervalMs = - typeof result.intervalSeconds === "number" && - Number.isFinite(result.intervalSeconds) && - result.intervalSeconds > 0 - ? Math.max(MINIMUM_INTERVAL_MS, Math.floor(result.intervalSeconds * 1000)) - : Math.max(MINIMUM_INTERVAL_MS, intervalMs + SLOW_DOWN_INTERVAL_INCREMENT_MS); - } - const remainingMs = deadline - options.now(); - if (remainingMs <= 0) { - break; - } - await abortableSleep(Math.min(intervalMs, remainingMs), options.signal, META_DEVICE_FLOW_CANCEL_MESSAGE, options.sleep); - } - throw new Error(slowDownResponses > 0 ? SLOW_DOWN_TIMEOUT_MESSAGE : TIMEOUT_MESSAGE); -} -/** - * Polls Meta's device token endpoint until the user approves. Returns the - * identity token (`access_token`), which is NOT usable for inference — it - * must be exchanged for a Model API key via {@link mintMetaApiKey}. Handles - * `authorization_pending`, `slow_down`, `access_denied`, and `expired_token`. - * Aborts with `META_DEVICE_FLOW_CANCEL_MESSAGE` when `signal` aborts at any - * point during polling — during the initial wait, a poll interval sleep, or - * an in-flight token-poll request. - */ -export async function pollMetaDeviceToken(device, signal, fetchImpl = fetch, opts = {}) { - const now = opts.now ?? Date.now; - const sleep = opts.sleep ?? - ((ms, sig) => abortableSleepNative(ms, sig, META_DEVICE_FLOW_CANCEL_MESSAGE)); - return pollOAuthDeviceCodeFlow({ - ...(device.intervalSeconds !== undefined - ? { intervalSeconds: device.intervalSeconds } - : {}), - ...(device.expiresInSeconds !== undefined - ? { expiresInSeconds: device.expiresInSeconds } - : {}), - waitBeforeFirstPoll: true, - signal, - now, - sleep, - poll: async () => { - const response = await fetchImpl(META_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: META_CLIENT_ID, - }).toString(), - signal: requestSignal(signal), - }); - const json = await readJson(response); - const parsed = DeviceTokenPayload(json ?? {}); - if (parsed instanceof type.errors) { - return { - status: "failed", - message: `Invalid Meta device token response: ${parsed.summary}`, - }; - } - if (response.ok && - typeof parsed.access_token === "string" && - parsed.access_token.length > 0) { - return { status: "complete", value: parsed.access_token }; - } - switch (parsed.error) { - case "authorization_pending": - return { status: "pending" }; - case "slow_down": { - const intervalSeconds = positiveNumber(parsed.interval); - return { - status: "slow_down", - ...(intervalSeconds !== undefined ? { intervalSeconds } : {}), - }; - } - 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)}`, - }; - } - }, - }); -} -function abortableSleepNative(ms, signal, cancelMessage) { - return new Promise((resolve, reject) => { - if (signal.aborted) { - reject(new Error(cancelMessage)); - return; - } - const onAbort = () => { - clearTimeout(timeout); - reject(new Error(cancelMessage)); - }; - const timeout = setTimeout(() => { - signal.removeEventListener("abort", onAbort); - resolve(); - }, ms); - signal.addEventListener("abort", onAbort, { once: true }); - }); -} -/** - * Exchanges a Meta identity token for a short-lived Model API key - * (`LLM|...`), valid about a day. 401/403 means the identity token is dead - * and the session must be restarted with a fresh device login. - */ -export async function mintMetaApiKey(identityToken, signal, fetchImpl = fetch, opts = {}) { - const now = opts.now ?? Date.now; - const response = await fetchImpl(META_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; only a fresh device flow helps. - throw new Error(`Meta session expired (status ${response.status}). Re-run device login to sign in again.${errorDetail(json)}`); - } - if (!response.ok) { - throw new Error(`Meta API key mint failed with status ${response.status}${errorDetail(json)}`); - } - const parsed = MintPayload(json ?? {}); - if (parsed instanceof type.errors) { - throw new Error(`Invalid Meta API key mint response: ${parsed.summary}`); - } - if (typeof parsed.api_key !== "string" || parsed.api_key.length === 0) { - const actionUrl = trustedHttpUrl(parsed.action_url); - throw new Error(`Meta did not issue an API key.${actionUrl ? ` Complete setup at ${actionUrl}` : ""}`); - } - return { - apiKey: parsed.api_key, - expiresAt: now() + META_API_KEY_LIFETIME_MS, - }; -} diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index 7ddc3f7..0000000 --- a/dist/index.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./constants.js"; -export { startMetaDeviceAuthorization, pollMetaDeviceToken, mintMetaApiKey, META_DEVICE_FLOW_CANCEL_MESSAGE, type MetaDeviceAuthorization, type NowFn, type SleepFn, type FetchLike, type MintMetaApiKeyResult, type MintMetaApiKeyOptions, type PollMetaDeviceTokenOptions, } from "./device-flow.js"; -export { metaDeviceLogin, metaTokensFromResponse, refreshMetaTokens, metaOAuthProvider, type MetaOAuthTokens, type MetaDeviceLoginOptions, type MetaDeviceLoginEvent, } from "./oauth.js"; -export { createMetaResponsesAdapter, metaResponsesQuirks, } from "./responses-adapter.js"; diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index 040f365..0000000 --- a/dist/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./constants.js"; -export { startMetaDeviceAuthorization, pollMetaDeviceToken, mintMetaApiKey, META_DEVICE_FLOW_CANCEL_MESSAGE, } from "./device-flow.js"; -export { metaDeviceLogin, metaTokensFromResponse, refreshMetaTokens, metaOAuthProvider, } from "./oauth.js"; -export { createMetaResponsesAdapter, metaResponsesQuirks, } from "./responses-adapter.js"; diff --git a/dist/oauth.d.ts b/dist/oauth.d.ts deleted file mode 100644 index d8ee634..0000000 --- a/dist/oauth.d.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { type BaseTokens, type TokenResponse } from "@corbits/oauth-core"; -import { type FetchLike, type NowFn, type SleepFn } from "./device-flow.js"; -/** - * Tokens for a Meta Muse subscription: the shared base shape plus an - * optional identity token carry-through. `access` is the minted Model API - * key (`LLM|...`); `refresh` is the identity token (not renewable — a - * 401/403 on mint means re-login). - */ -export type MetaOAuthTokens = BaseTokens & { - idToken?: string; -}; -export type MetaDeviceLoginOptions = { - /** Injectable clock for expiry math. */ - now?: NowFn; - /** Injectable sleep so tests skip real wall-clock waits. */ - sleep?: SleepFn; - /** Injectable fetch for tests. */ - fetchImpl?: FetchLike; - /** Signals the user opening the verification URL, and what to show them. */ - notify?: (event: MetaDeviceLoginEvent) => void; -}; -export type MetaDeviceLoginEvent = { - type: "device_code"; - userCode: string; - verificationUri: string; - intervalSeconds?: number; - expiresInSeconds?: number; -} | { - type: "progress"; - message: string; -}; -/** - * Runs the full Meta device login: device authorization, poll until - * approval, then mint a Model API key. The minted key is `access`, the - * identity token is `refresh`, and expiry is now + 24h. Aborts with - * `META_DEVICE_FLOW_CANCEL_MESSAGE` when `signal` aborts. - */ -export declare function metaDeviceLogin(signal: AbortSignal, opts?: MetaDeviceLoginOptions): Promise; -/** - * Maps a raw token endpoint response onto {@link MetaOAuthTokens}, carrying - * `id_token` through as `idToken`. - */ -export declare function metaTokensFromResponse(response: TokenResponse, now: number, previousRefresh?: string): MetaOAuthTokens; -/** - * Refreshes a Meta credential by re-minting a Model API key from the stored - * identity token (`refreshSecret`). The identity token itself is not - * renewable; a 401/403 on mint throws and the caller must re-login. An - * optional signal aborts the mint request; when it aborts, rejects with - * `META_DEVICE_FLOW_CANCEL_MESSAGE` (same contract as - * {@link metaDeviceLogin}). When omitted, a fresh (never aborted) signal is - * used so callers without one keep the previous behavior. - */ -export declare function refreshMetaTokens(refreshSecret: string, now: number, fetchImpl?: FetchLike, signal?: AbortSignal): Promise; -/** - * Optional host wrapper for the hub's background refresher. Meta's device - * flow is self-contained (no oauth-core PKCE machinery), so the wrapper - * exposes only `refresh` — the hub calls it with the stored identity token - * to re-mint the Model API key. `exchange` is deliberately absent: Meta has - * no authorization-code exchange. - */ -export declare function metaOAuthProvider(opts?: { - fetchImpl?: FetchLike; -}): Promise<{ - refresh: (refreshSecret: string, now: number, signal?: AbortSignal) => Promise; -}>; diff --git a/dist/oauth.js b/dist/oauth.js deleted file mode 100644 index f462857..0000000 --- a/dist/oauth.js +++ /dev/null @@ -1,106 +0,0 @@ -import { baseTokensFromResponse, } from "@corbits/oauth-core"; -import { META_DEVICE_FLOW_CANCEL_MESSAGE, mintMetaApiKey, pollMetaDeviceToken, startMetaDeviceAuthorization, } from "./device-flow.js"; -import { META_API_KEY_LIFETIME_MS } from "./constants.js"; -/** - * Runs the full Meta device login: device authorization, poll until - * approval, then mint a Model API key. The minted key is `access`, the - * identity token is `refresh`, and expiry is now + 24h. Aborts with - * `META_DEVICE_FLOW_CANCEL_MESSAGE` when `signal` aborts. - */ -export async function metaDeviceLogin(signal, opts = {}) { - const fetchImpl = opts.fetchImpl ?? fetch; - const now = opts.now ?? Date.now; - try { - const device = await startMetaDeviceAuthorization(signal, fetchImpl); - opts.notify?.({ - type: "device_code", - userCode: device.userCode, - verificationUri: device.verificationUri, - ...(device.intervalSeconds !== undefined - ? { intervalSeconds: device.intervalSeconds } - : {}), - ...(device.expiresInSeconds !== undefined - ? { expiresInSeconds: device.expiresInSeconds } - : {}), - }); - const identityToken = await pollMetaDeviceToken(device, signal, fetchImpl, { - now, - ...(opts.sleep !== undefined ? { sleep: opts.sleep } : {}), - }); - opts.notify?.({ - type: "progress", - message: "Enabling Meta Model API access...", - }); - const minted = await mintMetaApiKey(identityToken, signal, fetchImpl, { - now, - }); - // Normalize the minted credential into the standard BaseTokens / - // oauth_token shape: access = minted key, refresh = identity token, - // expires = now + 24h. - return metaTokensFromResponse({ - access_token: minted.apiKey, - refresh_token: identityToken, - expires_in: META_API_KEY_LIFETIME_MS / 1000, - }, now()); - } - catch (err) { - // An in-flight fetch rejects with a DOMException on abort; the login UI - // matches on this message. - if (signal.aborted) { - throw new Error(META_DEVICE_FLOW_CANCEL_MESSAGE, { cause: err }); - } - throw err; - } -} -/** - * Maps a raw token endpoint response onto {@link MetaOAuthTokens}, carrying - * `id_token` through as `idToken`. - */ -export function metaTokensFromResponse(response, now, previousRefresh) { - const base = baseTokensFromResponse(response, now, previousRefresh); - return { - ...base, - ...(response.id_token !== undefined ? { idToken: response.id_token } : {}), - }; -} -/** - * Refreshes a Meta credential by re-minting a Model API key from the stored - * identity token (`refreshSecret`). The identity token itself is not - * renewable; a 401/403 on mint throws and the caller must re-login. An - * optional signal aborts the mint request; when it aborts, rejects with - * `META_DEVICE_FLOW_CANCEL_MESSAGE` (same contract as - * {@link metaDeviceLogin}). When omitted, a fresh (never aborted) signal is - * used so callers without one keep the previous behavior. - */ -export async function refreshMetaTokens(refreshSecret, now, fetchImpl = fetch, signal) { - try { - const minted = await mintMetaApiKey(refreshSecret, signal ?? new AbortController().signal, fetchImpl, { now: () => now }); - return metaTokensFromResponse({ - access_token: minted.apiKey, - refresh_token: refreshSecret, - expires_in: META_API_KEY_LIFETIME_MS / 1000, - }, now, refreshSecret); - } - catch (err) { - // An in-flight mint fetch rejects with a DOMException on abort; the - // login UI matches on this message. When no signal is passed, aborts - // cannot occur and this branch is unreachable. - if (signal?.aborted) { - throw new Error(META_DEVICE_FLOW_CANCEL_MESSAGE, { cause: err }); - } - throw err; - } -} -/** - * Optional host wrapper for the hub's background refresher. Meta's device - * flow is self-contained (no oauth-core PKCE machinery), so the wrapper - * exposes only `refresh` — the hub calls it with the stored identity token - * to re-mint the Model API key. `exchange` is deliberately absent: Meta has - * no authorization-code exchange. - */ -export async function metaOAuthProvider(opts = {}) { - const fetchImpl = opts.fetchImpl ?? fetch; - return { - refresh: (refreshSecret, now, signal) => refreshMetaTokens(refreshSecret, now, fetchImpl, signal), - }; -} diff --git a/dist/responses-adapter.d.ts b/dist/responses-adapter.d.ts deleted file mode 100644 index 9bc51b8..0000000 --- a/dist/responses-adapter.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { type ResponsesQuirks } from "@corbits/openai-responses"; -import type { AdapterFactory } from "@intx/inference"; -export declare const metaResponsesQuirks: ResponsesQuirks; -/** - * `AdapterFactory` for Meta's Model API Responses endpoint, with Meta's - * system-prompt placement and reasoning shape baked in via - * `@corbits/openai-responses`. Per-source `quirks` are ignored: Meta's - * config is fixed here, not host-supplied. - */ -export declare const createMetaResponsesAdapter: AdapterFactory; diff --git a/dist/responses-adapter.js b/dist/responses-adapter.js deleted file mode 100644 index 4290d7c..0000000 --- a/dist/responses-adapter.js +++ /dev/null @@ -1,26 +0,0 @@ -import { responsesAdapterFactory, } from "@corbits/openai-responses"; -import { META_REASONING_EFFORT_OPTION, META_RESPONSES_PATH, } from "./constants.js"; -// Meta's Model API speaks the OpenAI Responses protocol natively at -// `POST /v1/responses`. The system prompt rides as a leading `developer` -// message with parts-shaped content (Meta's quickstart is developer-style -// and accepts both roles), reasoning is requested at "auto" summary depth -// with effort forwarded from the caller's `metaReasoningEffort` option, and -// `x-api-version` is NOT sent on /v1/responses (it is only required on the -// key-mint endpoint). max_output_tokens / temperature / store / -// parallel_tool_calls stay on their protocol-native defaults (forwarded -// when the caller sets them) because Meta is OpenAI-compatible. -export const metaResponsesQuirks = { - path: META_RESPONSES_PATH, - systemPrompt: { role: "developer", shape: "parts" }, - reasoning: { - summary: "auto", - effortOption: META_REASONING_EFFORT_OPTION, - }, -}; -/** - * `AdapterFactory` for Meta's Model API Responses endpoint, with Meta's - * system-prompt placement and reasoning shape baked in via - * `@corbits/openai-responses`. Per-source `quirks` are ignored: Meta's - * config is fixed here, not host-supplied. - */ -export const createMetaResponsesAdapter = responsesAdapterFactory(metaResponsesQuirks);