UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

173 lines 6.61 kB
/** * TaskExecutor — Runs a single task execution against NeuroLink.generate(). * * Handles: * - Isolated mode: fresh generate() call with no history * - Continuation mode: loads conversation history, appends new exchange * - Retry with exponential backoff for transient errors * - Run result construction and logging */ import { nanoid } from "nanoid"; import { logger } from "../utils/logger.js"; import { executeAutoresearchTick } from "./autoresearchTaskExecutor.js"; /** Errors that are transient and should be retried */ const TRANSIENT_PATTERNS = [ "rate limit", "rate_limit", "too many requests", "429", "503", "502", "504", "timeout", "econnreset", "econnrefused", "network", "overloaded", ]; function isTransientError(error) { const msg = String(error).toLowerCase(); return TRANSIENT_PATTERNS.some((p) => msg.includes(p)); } export class TaskExecutor { neurolink; store; emitter; constructor(neurolink, store, emitter) { this.neurolink = neurolink; this.store = store; this.emitter = emitter; } /** * Execute a task once. Called by the backend on each scheduled tick. * Returns the run result (success or error). */ async execute(task) { const runId = `run_${nanoid(12)}`; const startTime = Date.now(); let lastError; for (let attempt = 1; attempt <= task.retry.maxAttempts; attempt++) { try { const result = await this.executeOnce(task, runId); return result; } catch (err) { lastError = String(err); const willRetry = attempt < task.retry.maxAttempts && isTransientError(err); logger.warn("[TaskExecutor] Execution attempt failed", { taskId: task.id, runId, attempt, willRetry, error: lastError, }); if (!willRetry) { break; } // Backoff before retry const backoffIndex = Math.min(attempt - 1, task.retry.backoffMs.length - 1); const delay = task.retry.backoffMs[backoffIndex]; await sleep(delay); } } // All retries exhausted or permanent error const errorResult = { taskId: task.id, runId, status: "error", error: lastError, durationMs: Date.now() - startTime, timestamp: new Date().toISOString(), }; return errorResult; } // ── Internal ────────────────────────────────────────── async executeOnce(task, runId) { // ── Autoresearch routing ── if (task.type === "autoresearch") { return executeAutoresearchTick(task, this.neurolink, this.emitter); } const startTime = Date.now(); // Build generate options const generateOptions = { input: { text: task.prompt }, ...(task.provider ? { provider: task.provider } : {}), ...(task.model ? { model: task.model } : {}), ...(task.systemPrompt ? { systemPrompt: task.systemPrompt } : {}), ...(task.maxTokens ? { maxTokens: task.maxTokens } : {}), ...(task.temperature !== undefined ? { temperature: task.temperature } : {}), ...(task.timeout ? { timeout: task.timeout } : {}), ...(!task.tools ? { disableTools: true } : {}), }; // Thinking level if (task.thinkingLevel) { generateOptions.thinkingConfig = { thinkingLevel: task.thinkingLevel }; } // Continuation mode: pass conversation history as proper multi-turn messages if (task.mode === "continuation" && task.sessionId) { const history = await this.store.getHistory(task.id); // Pass history as proper role-based conversation messages if (history.length > 0) { generateOptions.conversationMessages = history.map((entry, i) => ({ id: `${task.sessionId}_${i}`, role: entry.role, content: entry.content, timestamp: entry.timestamp, })); } // Add continuation context to system prompt const runCount = Math.floor(history.length / 2); const continuationHint = runCount > 0 ? `This is a continuation task (run ${runCount + 1}). Your previous ${runCount} exchange(s) are provided as conversation history.` : "This is a continuation task. This is the first execution — no prior history exists yet."; generateOptions.systemPrompt = task.systemPrompt ? `${task.systemPrompt}\n\n${continuationHint}` : continuationHint; } // Execute const result = await this.neurolink.generate(generateOptions); // Build run result const runResult = { taskId: task.id, runId, status: "success", output: result.content, toolCalls: result.toolExecutions?.map((te) => ({ name: te.name, input: te.input, output: te.output, })), tokensUsed: result.usage ? { input: result.usage.input ?? 0, output: result.usage.output ?? 0, } : undefined, durationMs: Date.now() - startTime, timestamp: new Date().toISOString(), }; // Continuation mode: append this exchange to history if (task.mode === "continuation" && task.sessionId) { const newEntries = [ { role: "user", content: task.prompt, timestamp: new Date(startTime).toISOString(), }, { role: "assistant", content: result.content, timestamp: runResult.timestamp, }, ]; await this.store.appendHistory(task.id, newEntries); } return runResult; } } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } //# sourceMappingURL=taskExecutor.js.map