Exocore-gateway / src /core /meta.js
Johnsteve Costanos
Add Meta AI fast (think_fast) and thinking (think_hard) modes + model variants
b3ae621
Raw
History Blame Contribute Delete
15 kB
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),
};
}