Spaces:
Running
Running
Johnsteve Costanos
Add Meta AI fast (think_fast) and thinking (think_hard) modes + model variants
b3ae621 | import axios from 'axios'; | |
| import crypto from 'crypto'; | |
| import fs from 'fs'; | |
| import path from 'path'; | |
| import { fileURLToPath } from 'url'; | |
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); | |
| const CONFIG_PATH = path.join(__dirname, '../../data/meta.json'); | |
| const DOC_ID = '3337abb17af05c667363c229aaf0d1b3'; | |
| const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0'; | |
| const ENDPOINT = 'https://www.meta.ai/api/graphql'; | |
| export const META_MODELS = { | |
| 'meta-ai': { id: 'meta-ai', name: 'Meta AI (Llama)' }, | |
| }; | |
| const MODE_MAP = { | |
| normal: 'chat', | |
| fast: 'think_fast', | |
| thinking: 'think_hard', | |
| }; | |
| function resolveGraphMode(mode) { | |
| return MODE_MAP[mode] || 'chat'; | |
| } | |
| export class MetaError extends Error { | |
| constructor(message, code, details) { | |
| super(message); | |
| this.code = code || 'meta_error'; | |
| this.details = details || null; | |
| } | |
| } | |
| function readMetaConfig() { | |
| try { | |
| if (!fs.existsSync(CONFIG_PATH)) return { cookies: {}, accessToken: null }; | |
| return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); | |
| } catch { return { cookies: {}, accessToken: null }; } | |
| } | |
| function saveMetaConfig(cfg) { | |
| const dir = path.dirname(CONFIG_PATH); | |
| if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); | |
| fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2), 'utf8'); | |
| } | |
| function getCookieHeader(cookies) { | |
| return Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; '); | |
| } | |
| function extractValue(text, startStr, endStr) { | |
| const start = text.indexOf(startStr); | |
| if (start === -1) return ''; | |
| const valueStart = start + startStr.length; | |
| const end = text.indexOf(endStr, valueStart); | |
| if (end === -1) return ''; | |
| return text.slice(valueStart, end); | |
| } | |
| function detectChallengePage(htmlText) { | |
| if (htmlText.includes('executeChallenge') || htmlText.includes('__rd_verify')) { | |
| const match = htmlText.match(/fetch\('(\/__rd_verify[^']+)'/); | |
| if (match) return match[1]; | |
| } | |
| return null; | |
| } | |
| async function handleMetaAIChallenge(session, cookies, challengeUrl) { | |
| const headers = { | |
| 'User-Agent': USER_AGENT, | |
| 'Accept': '*/*', | |
| 'Referer': 'https://meta.ai', | |
| 'Origin': 'https://meta.ai', | |
| 'Cookie': getCookieHeader(cookies), | |
| }; | |
| for (let attempt = 0; attempt < 3; attempt++) { | |
| try { | |
| const response = await session.post(`https://meta.ai${challengeUrl}`, null, { headers, timeout: 10000 }); | |
| if (response.status === 200) { | |
| const setCookie = response.headers['set-cookie']; | |
| if (setCookie) { | |
| const all = Array.isArray(setCookie) ? setCookie.join('; ') : setCookie; | |
| const rdMatch = all.match(/rd_challenge=([^;]+)/); | |
| if (rdMatch) cookies.rd_challenge = rdMatch[1]; | |
| } | |
| return true; | |
| } | |
| } catch {} | |
| await new Promise(r => setTimeout(r, 1500)); | |
| } | |
| return false; | |
| } | |
| let _cachedToken = null; | |
| async function extractAccessToken(cookies) { | |
| if (_cachedToken) return _cachedToken; | |
| const session = axios.create({ timeout: 30000 }); | |
| const headers = { 'User-Agent': USER_AGENT, 'Cookie': getCookieHeader(cookies) }; | |
| let response; | |
| try { | |
| response = await session.get('https://meta.ai', { headers, validateStatus: () => true }); | |
| } catch (e) { | |
| if (e.response) response = e.response; | |
| else throw new MetaError(`Failed to fetch meta.ai: ${e.message}`, 'network_error'); | |
| } | |
| if (response.status === 403 || detectChallengePage(response.data)) { | |
| const challengeUrl = detectChallengePage(response.data); | |
| if (challengeUrl) { | |
| const ok = await handleMetaAIChallenge(session, cookies, challengeUrl); | |
| if (ok) { | |
| headers['Cookie'] = getCookieHeader(cookies); | |
| response = await session.get('https://meta.ai', { headers, validateStatus: () => true }); | |
| if (response.status !== 200) return null; | |
| const match = response.data?.match(/accessToken":"(ecto1:[^"\\]+)/); | |
| if (match) { _cachedToken = match[1]; return _cachedToken; } | |
| } | |
| } | |
| return null; | |
| } | |
| if (response.status !== 200) return null; | |
| if (detectChallengePage(response.data)) { | |
| const challengeUrl = detectChallengePage(response.data); | |
| if (challengeUrl) { | |
| const ok = await handleMetaAIChallenge(session, cookies, challengeUrl); | |
| if (ok) { | |
| headers['Cookie'] = getCookieHeader(cookies); | |
| response = await session.get('https://meta.ai', { headers, validateStatus: () => true }); | |
| if (response.status !== 200) return null; | |
| const match = response.data?.match(/accessToken":"(ecto1:[^"\\]+)/); | |
| if (match) { _cachedToken = match[1]; return _cachedToken; } | |
| } | |
| } | |
| return null; | |
| } | |
| const match = response.data?.match(/accessToken":"(ecto1:[^"\\]+)/); | |
| if (match) { _cachedToken = match[1]; return _cachedToken; } | |
| return null; | |
| } | |
| async function ensureToken(forceRefresh = false) { | |
| const cfg = readMetaConfig(); | |
| if (forceRefresh) _cachedToken = null; | |
| if (cfg.accessToken && !forceRefresh) { | |
| _cachedToken = cfg.accessToken; | |
| return cfg; | |
| } | |
| const token = await extractAccessToken(cfg.cookies); | |
| if (token) { | |
| cfg.accessToken = token; | |
| saveMetaConfig(cfg); | |
| } else if (!cfg.accessToken) { | |
| throw new MetaError('Meta AI not configured — need cookies and accessToken in data/meta.json', 'auth_error'); | |
| } | |
| _cachedToken = cfg.accessToken; | |
| return cfg; | |
| } | |
| function extractChatSnapshot(event) { | |
| const snapshots = []; | |
| const add = (v) => { if (v && typeof v === 'string' && v.trim()) snapshots.push(v.trim()); }; | |
| const stream = event?.data?.sendMessageStream; | |
| if (stream) { | |
| add(stream?.content); | |
| if (stream?.message) { add(stream.message?.content); add(stream.message?.text); } | |
| } | |
| const msg = event?.data?.message; | |
| if (msg) { | |
| add(msg?.content); add(msg?.text); add(msg?.streaming_text); | |
| if (msg?.composed_text?.content) { | |
| for (const item of msg.composed_text.content) { if (item?.text) add(item.text); } | |
| } | |
| } | |
| const node = event?.data?.node; | |
| if (node?.bot_response_message) { | |
| const brm = node.bot_response_message; | |
| add(brm?.content); add(brm?.text); add(brm?.streaming_text); | |
| if (brm?.composed_text?.content) { | |
| for (const item of brm.composed_text.content) { if (item?.text) add(item.text); } | |
| } | |
| if (brm?.content?.agent_steps) { | |
| for (const step of brm.content.agent_steps) { | |
| if (step?.composed_text?.content) { | |
| for (const item of step.composed_text.content) { if (item?.text) add(item.text); } | |
| } | |
| } | |
| } | |
| } | |
| return snapshots.length ? snapshots.reduce((a, b) => a.length >= b.length ? a : b) : ''; | |
| } | |
| function extractGraphQLErrors(event) { | |
| const errors = []; | |
| if (Array.isArray(event?.errors)) errors.push(...event.errors); | |
| const dataObj = event?.data; | |
| if (dataObj && typeof dataObj === 'object') { | |
| if (Array.isArray(dataObj.errors)) errors.push(...dataObj.errors); | |
| if (dataObj?.sendMessageStream && Array.isArray(dataObj.sendMessageStream.errors)) { | |
| errors.push(...dataObj.sendMessageStream.errors); | |
| } | |
| } | |
| return errors; | |
| } | |
| function buildVariables(content, conversationId, graphMode = 'chat', extra = {}) { | |
| return { | |
| conversationId, | |
| content, | |
| userMessageId: crypto.randomUUID(), | |
| assistantMessageId: crypto.randomUUID(), | |
| userUniqueMessageId: String(BigInt(`0x${crypto.randomBytes(8).toString('hex')}`) % BigInt(10 ** 13)), | |
| turnId: crypto.randomUUID(), | |
| mode: graphMode, | |
| isNewConversation: true, | |
| clientTimezone: 'Asia/Kolkata', | |
| entryPoint: 'KADABRA__UNKNOWN', | |
| promptSessionId: crypto.randomUUID(), | |
| userAgent: USER_AGENT, | |
| currentBranchPath: '0', | |
| promptEditType: 'new_message', | |
| userLocale: 'en-US', | |
| attachmentsV2: null, | |
| imagineOperationRequest: null, | |
| ...extra, | |
| }; | |
| } | |
| function buildHeaders(cookieStr, accessToken) { | |
| return { | |
| 'Cookie': cookieStr, | |
| 'Authorization': `OAuth ${accessToken}`, | |
| 'User-Agent': USER_AGENT, | |
| 'Content-Type': 'application/json', | |
| 'Origin': 'https://www.meta.ai', | |
| 'Referer': 'https://www.meta.ai/', | |
| 'Accept': 'text/event-stream, application/json', | |
| }; | |
| } | |
| async function postChat(session, cookieStr, accessToken, content, conversationId, graphMode = 'chat') { | |
| const response = await session.post(ENDPOINT, { | |
| doc_id: DOC_ID, | |
| variables: buildVariables(content, conversationId, graphMode), | |
| }, { | |
| headers: buildHeaders(cookieStr, accessToken), | |
| responseType: 'stream', | |
| timeout: 120000, | |
| }); | |
| if (response.status === 403) throw new MetaError('Meta AI session expired — refresh cookies', 'auth_error'); | |
| return response; | |
| } | |
| function normalizeText(text) { | |
| return (text || '').replace(/<inline>\{.*?\}<\/inline>/g, '').replace(/<inline>(.*?)<\/inline>/g, '').trim(); | |
| } | |
| export async function metaChat(messages, options = {}) { | |
| const { onDelta, mode } = options; | |
| const graphMode = resolveGraphMode(mode); | |
| const cfg = await ensureToken(); | |
| if (!cfg.accessToken) throw new MetaError('Meta AI not configured — need cookies and accessToken in data/meta.json', 'auth_error'); | |
| if (!cfg.cookies?.ecto_1_sess) throw new MetaError('Meta AI cookies missing ecto_1_sess', 'auth_error'); | |
| const content = messages.map(m => m.content).filter(Boolean).join('\n') || 'Hello'; | |
| const conversationId = crypto.randomUUID(); | |
| const cookieStr = getCookieHeader(cfg.cookies); | |
| const session = axios.create({ timeout: 120000 }); | |
| try { | |
| const response = await postChat(session, cookieStr, cfg.accessToken, content, conversationId, graphMode); | |
| let lastContent = ''; | |
| let buffer = ''; | |
| let graphqlErrors = []; | |
| for await (const chunk of response.data) { | |
| buffer += chunk.toString('utf-8'); | |
| const lines = buffer.split('\n'); | |
| buffer = lines.pop() || ''; | |
| for (const rawLine of lines) { | |
| const line = rawLine.trim(); | |
| if (!line || line.startsWith('event:') || line === '[DONE]') continue; | |
| const payloadLine = line.startsWith('data:') ? line.slice(5).trim() : line; | |
| if (!payloadLine) continue; | |
| try { | |
| const parsed = JSON.parse(payloadLine); | |
| const gqlErrors = extractGraphQLErrors(parsed); | |
| if (gqlErrors.length) graphqlErrors.push(...gqlErrors); | |
| const snapshot = extractChatSnapshot(parsed); | |
| if (snapshot) { | |
| lastContent = snapshot; | |
| if (onDelta) onDelta(snapshot); | |
| } | |
| } catch {} | |
| } | |
| } | |
| if (!lastContent) { | |
| if (graphqlErrors.length) { | |
| throw new MetaError(`Meta AI GraphQL error: ${graphqlErrors.map(e => e.message).join('; ')}`, 'api_error'); | |
| } | |
| throw new MetaError('No response from Meta AI', 'api_error'); | |
| } | |
| const finalMsg = normalizeText(lastContent); | |
| return { text: finalMsg, model: 'meta-ai', usage: {}, metadata: { conversationId } }; | |
| } catch (e) { | |
| if (e instanceof MetaError) throw e; | |
| if (e.code === 'ERR_BAD_RESPONSE' || e.code === 'ERR_BAD_REQUEST') { | |
| throw new MetaError(`Meta AI API error: ${e.message}`, 'api_error'); | |
| } | |
| throw new MetaError(`Meta AI request failed: ${e.message}`, 'network_error'); | |
| } | |
| } | |
| export async function metaChatV1(messages, options = {}) { | |
| const { model, system, max_tokens } = options; | |
| const msgs = []; | |
| if (system) msgs.push({ role: 'system', content: system }); | |
| msgs.push(...messages); | |
| try { | |
| const result = await metaChat(msgs, { maxTokens: max_tokens }); | |
| return { | |
| id: `msg_${Date.now()}`, | |
| type: 'message', | |
| role: 'assistant', | |
| content: [{ type: 'text', text: result.text }], | |
| model: 'meta-ai', | |
| stop_reason: 'end_turn', | |
| stop_sequence: null, | |
| usage: { input_tokens: -1, output_tokens: -1 }, | |
| }; | |
| } catch (e) { | |
| throw e; | |
| } | |
| } | |
| export async function metaImageGen(prompt, options = {}) { | |
| const cfg = await ensureToken(); | |
| if (!cfg.accessToken) throw new MetaError('Meta AI not configured', 'auth_error'); | |
| if (!cfg.cookies?.ecto_1_sess) throw new MetaError('Meta AI cookies missing ecto_1_sess', 'auth_error'); | |
| const orientation = options.orientation || 'VERTICAL'; | |
| const cookieStr = getCookieHeader(cfg.cookies); | |
| const headers = buildHeaders(cookieStr, cfg.accessToken); | |
| try { | |
| const response = await axios.post(ENDPOINT, { | |
| doc_id: DOC_ID, | |
| variables: buildVariables(prompt, crypto.randomUUID(), { | |
| imagineOperationRequest: { orientation }, | |
| }), | |
| }, { headers, timeout: 60000 }); | |
| const data = response.data; | |
| let imageUrls = []; | |
| if (data?.data?.sendMessageStream?.imagineMedia) { | |
| const media = data.data.sendMessageStream.imagineMedia; | |
| if (Array.isArray(media)) { | |
| for (const m of media) { | |
| if (m?.url) imageUrls.push(m.url); | |
| } | |
| } | |
| } | |
| return { | |
| success: !!imageUrls.length, | |
| image_urls: imageUrls, | |
| status: imageUrls.length ? 'READY' : 'PROCESSING', | |
| prompt, | |
| orientation, | |
| }; | |
| } catch (e) { | |
| return { success: false, status: 'FAILED', error: e.message, prompt }; | |
| } | |
| } | |
| export async function metaVideoGen(prompt, options = {}) { | |
| const cfg = await ensureToken(); | |
| if (!cfg.accessToken) throw new MetaError('Meta AI not configured', 'auth_error'); | |
| if (!cfg.cookies?.ecto_1_sess) throw new MetaError('Meta AI cookies missing ecto_1_sess', 'auth_error'); | |
| const cookieStr = getCookieHeader(cfg.cookies); | |
| const headers = buildHeaders(cookieStr, cfg.accessToken); | |
| try { | |
| const response = await axios.post(ENDPOINT, { | |
| doc_id: DOC_ID, | |
| variables: buildVariables(prompt, crypto.randomUUID(), { | |
| imagineOperationRequest: { mediaType: 'VIDEO' }, | |
| }), | |
| }, { headers, timeout: 120000 }); | |
| return { | |
| success: true, | |
| status: 'PROCESSING', | |
| prompt, | |
| response: response.data, | |
| }; | |
| } catch (e) { | |
| return { success: false, status: 'FAILED', error: e.message, prompt }; | |
| } | |
| } | |
| export async function metaDeleteConversation(conversationId) { | |
| const cfg = readMetaConfig(); | |
| if (!cfg.cookies?.ecto_1_sess) throw new MetaError('Meta AI not configured', 'auth_error'); | |
| try { | |
| const response = await axios.post(ENDPOINT, { | |
| doc_id: 'ad35bda8475e29ba4264ef0d6cc0958a', | |
| variables: { input: { id: conversationId } }, | |
| }, { | |
| headers: { | |
| 'Cookie': getCookieHeader(cfg.cookies), | |
| 'Content-Type': 'application/json', | |
| 'Origin': 'https://www.meta.ai', | |
| 'Referer': 'https://www.meta.ai/', | |
| }, | |
| timeout: 30000, | |
| }); | |
| const ok = response?.data?.data?.deleteConversation?.success === true; | |
| return { success: ok, status_code: response.status }; | |
| } catch (e) { | |
| return { success: false, error: e.message }; | |
| } | |
| } | |
| export async function metaListModels() { | |
| return [{ id: 'meta-ai', name: 'Meta AI (Llama)' }]; | |
| } | |
| export function loadMetaConfig() { | |
| const cfg = readMetaConfig(); | |
| return { | |
| configured: !!(cfg.cookies?.ecto_1_sess), | |
| hasAccessToken: !!cfg.accessToken, | |
| cookieKeys: Object.keys(cfg.cookies), | |
| }; | |
| } | |