overmind-mcp
Version:
Orchestrateur universel agents IA multi-modeles via MCP. Inclut le protocole 'Custom-Nickname' pour identifier vos agents avec des surnoms originaux (The Chaos Prophet, Shadow Sniper, etc.), l'isolation mémoire (Private Memory Context) et le support pour
145 lines • 6 kB
JavaScript
/**
* ╔══════════════════════════════════════════════════════════════════════╗
* ║ OVERMIND BRIDGE — Helpers & Utilities ║
* ║ retry, logging, SSE parsing, validation ║
* ╚══════════════════════════════════════════════════════════════════════╝
*/
/** Crée un logger simple prefixé — peut être remplacé par pino/winston */
export function createBridgeLogger(prefix) {
const fmt = (level, msg, meta) => {
const ts = new Date().toISOString().slice(11, 19);
const metaStr = meta ? ` ${JSON.stringify(meta)}` : '';
const fn = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log;
fn(`[${ts}] [${prefix}] ${msg}${metaStr}`);
};
return {
debug: (msg, meta) => fmt('debug', msg, meta),
info: (msg, meta) => fmt('info', msg, meta),
warn: (msg, meta) => fmt('warn', msg, meta),
error: (msg, meta) => fmt('error', msg, meta),
};
}
// ─── SSE Parsing ───────────────────────────────────────────────────────────
/**
* Parse une réponse SSE (text/event-stream) ligne par ligne.
* Retourne le premier objet JSON-RPC valide trouvé dans les lignes "data:".
*/
export function parseSseText(text) {
for (const line of text.split('\n')) {
if (line.startsWith('data: ')) {
const dataStr = line.slice(6).trim();
if (dataStr) {
try {
return JSON.parse(dataStr);
}
catch {
// ligne data: invalide — on continue
}
}
}
}
return null;
}
/**
* Parse le body d'une réponse MCP : SSE d'abord, fallback JSON brut.
*/
export function parseMcpResponseBody(text) {
// 1) Essai SSE
const sse = parseSseText(text);
if (sse)
return sse;
// 2) Fallback JSON direct
const trimmed = text.trim();
if (trimmed) {
try {
return JSON.parse(trimmed);
}
catch {
throw new Error(`SSE response: no valid data: line. Body preview: ${trimmed.substring(0, 200)}`);
}
}
throw new Error('SSE response: empty body');
}
const DEFAULT_RETRY_CODES = ['ETIMEDOUT', 'EBODYREAD', 'ECONNRESET', 'ECONNREFUSED'];
export async function withRetry(fn, opts, logger) {
const codes = opts.retryableCodes ?? DEFAULT_RETRY_CODES;
let lastError;
for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
try {
return await fn();
}
catch (err) {
const error = err;
lastError = error;
const isRetryable = codes.includes(error?.code ?? '');
if (!isRetryable || attempt >= opts.maxAttempts) {
throw err;
}
logger?.warn(`🔁 Retry ${attempt}/${opts.maxAttempts} (${error.code}) — delay ${opts.delayMs}ms`);
await sleep(opts.delayMs);
}
}
throw lastError;
}
// ─── Validation ────────────────────────────────────────────────────────────
export function validatePrompt(prompt) {
if (typeof prompt !== 'string' || !prompt.trim()) {
throw new Error('Prompt is required and must be a non-empty string');
}
return prompt.trim();
}
export function validateAgentName(name) {
if (typeof name !== 'string' || !name.trim()) {
throw new Error('Agent name is required and must be a non-empty string');
}
const trimmed = name.trim();
if (!/^[a-zA-Z0-9_-]+$/.test(trimmed)) {
throw new Error('Agent name must only contain alphanumeric, underscores, and hyphens');
}
return trimmed;
}
// ─── Misc ──────────────────────────────────────────────────────────────────
export function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/** Extrait un sessionId depuis un marqueur textuel "SESSION_ID: xxx" */
export function extractSessionIdFromContent(content) {
for (const c of content) {
const m = c.text?.match(/SESSION_ID:\s*([a-zA-Z0-9_-]+)/i);
if (m)
return m[1];
}
return undefined;
}
/** Formate un contexte Discord pour l'injection dans le prompt agent */
export function formatDiscordContext(params) {
const { channelId, userId, username, message } = params;
if (!channelId && !username)
return message;
return [
'[DISCORD CONTEXT]',
`CHANNEL_ID: ${channelId ?? 'unknown'}`,
`USER_NAME: ${username ?? 'unknown'}`,
`USER_ID: ${userId ?? 'unknown'}`,
'=======================',
`MESSAGE: ${message}`,
].join('\n');
}
/**
* Interpolation de variables ${var} dans un template.
* Utilisé par ScenarioLoader, PromptSource, et le CLI.
*
* Supporte :
* - ${var} → remplacé par vars[var] si défini, sinon laissé tel quel
* - ${var:-def} → valeur par défaut si vars[var] est undefined
*/
export function interpolate(template, vars) {
if (!vars || Object.keys(vars).length === 0)
return template;
return template.replace(/\$\{([a-zA-Z0-9_]+)(?::-([^}]*))?\}/g, (_, key, def) => {
if (vars[key] !== undefined)
return vars[key];
return def !== undefined ? def : `\${${key}}`;
});
}
//# sourceMappingURL=utils.js.map