UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

95 lines (94 loc) • 3.51 kB
/** * Extract a human-readable error string from an MCP isError result object. * * Shared utility — no side effects, no dependencies on other SDK modules — * so it can be imported from the neurolink.ts event loop, the telemetry * instrumentation (which loads earlier), and the MCP discovery layer without * creating circular imports. Any change to truncation or content-type parsing * must happen here and propagate to all three surfaces. */ export function extractMcpErrorText(raw) { let resultObj; try { resultObj = typeof raw === "string" ? JSON.parse(raw) : raw; } catch { return ""; } if (!resultObj || typeof resultObj !== "object") { return ""; } const content = resultObj.content; if (!Array.isArray(content)) { return ""; } // Fail closed on malformed entries (e.g. `content: [null]`) rather than // throwing — the caller expects an empty string for unparseable input. const texts = content .filter((c) => c !== null && typeof c === "object" && c.type === "text" && typeof c.text === "string" && c.text.length > 0) .map((c) => c.text); return texts.join(" ").substring(0, 500); } /** * MCP tools signal failure by RETURNING `{ isError: true, ... }`, not throwing, * so execute()'s try/catch never sees it. Returns a capped status message for * failures (undefined for success) for the caller to set the span error level. * * Generic over input shape: accepts either a result object or a JSON-stringified * envelope (different providers hand back different shapes), mirroring * `extractMcpErrorText`. A non-JSON string has no `isError` field, so it is * correctly treated as "not an error" (→ undefined). * * Layered on `extractMcpErrorText`: this adds the `isError === true` gate and * the human-readable "MCP tool returned isError: …" prefix, while the shared * helper owns the content parsing and the 500-char cap. When `isError` is set * but no readable text is present, falls back to a generic message. */ export function extractMcpToolErrorMessage(result) { let resultObj = result; if (typeof resultObj === "string") { try { resultObj = JSON.parse(resultObj); } catch { return undefined; } } if (!resultObj || typeof resultObj !== "object") { return undefined; } if (resultObj.isError !== true) { return undefined; } const text = extractMcpErrorText(resultObj); return text ? `MCP tool returned isError: ${text}` : "MCP tool returned isError: true"; } /** * Detect a tool result that REPORTS failure without throwing, for the native * loops' consecutive-failure breaker. Covers the two shapes NeuroLink itself * produces or forwards: * - MCP CallToolResult with `isError: true` (e.g. proxy-blocked tools) — * delegated to {@link extractMcpToolErrorMessage} * - our own `{ error: "..." }` payloads * Plain strings are never classified (too false-positive-prone). * Returns the error text, or null when the result looks like a success. */ export function extractToolFailureText(result) { const mcpError = extractMcpToolErrorMessage(result); if (mcpError) { return mcpError; } if (result !== null && typeof result === "object" && typeof result.error === "string" && result.error.length > 0) { return result.error; } return null; }