UNPKG

comfyui-mcp

Version:

Local-first, agent-native control plane for ComfyUI — MCP server + autonomous sidebar agent that drives your live graph in natural language on ANY LLM: Claude/ChatGPT/Gemini on your subscription (no API key), free local models via Ollama (fully offline),

157 lines • 6.59 kB
/** * Turn the app-server's provider error payload into a useful, scrubbed panel * diagnostic (#2112). * * Codex has emitted both a plain `Bad Request` message and a JSON string such * as `{\"detail\":\"Bad Request\"}`. Forwarding either verbatim leaves the * user unable to distinguish an oversized context/tool history from an invalid * request or a transient provider rejection. Keep the extraction deliberately * narrow: provider error objects may contain request content, so only the * documented diagnostic-shaped fields are copied into the user-facing text. */ function record(value) { try { return value && typeof value === "object" && !Array.isArray(value) ? value : null; } catch { return null; } } function readValue(value, ...keys) { const obj = record(value); if (!obj) return undefined; for (const key of keys) { try { if (Object.prototype.hasOwnProperty.call(obj, key)) return obj[key]; } catch { // A hostile provider-shaped object must never break error reporting. } } return undefined; } function readRawString(value, ...keys) { const candidate = readValue(value, ...keys); return typeof candidate === "string" ? candidate : undefined; } function readString(value, ...keys) { const candidate = readRawString(value, ...keys); return candidate?.trim() || undefined; } function readNumber(value, ...keys) { const candidate = readValue(value, ...keys); if (typeof candidate === "number" && Number.isFinite(candidate)) return candidate; if (typeof candidate === "string" && /^\d{3}$/.test(candidate.trim())) return Number(candidate.trim()); return undefined; } function firstString(sources, ...keys) { for (const source of sources) { const value = readString(source, ...keys); if (value) return value; } return undefined; } function firstNumber(sources, ...keys) { for (const source of sources) { const value = readNumber(source, ...keys); if (value !== undefined) return value; } return undefined; } function safeLabel(value, max = 120) { if (!value) return undefined; const oneLine = value.replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim(); if (!oneLine) return undefined; // These fields are identifiers, not prose. Reject anything outside the // provider's ordinary request-id/code alphabet instead of reflecting an // attacker-controlled payload into the panel. if (!/^[A-Za-z0-9._:/-]+$/.test(oneLine)) return undefined; return oneLine.slice(0, max); } function parseMessageEnvelope(message) { if (typeof message !== "string") return record(message); const trimmed = message.trim(); if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null; try { return record(JSON.parse(trimmed)); } catch { return null; } } function hasBadRequestMessage(value) { const message = readString(value, "message", "detail"); return typeof message === "string" && /^bad request$/i.test(message); } /** * Format a terminal Codex app-server error. * * Non-400 errors retain their existing message exactly. A generic 400 gets a * bounded explanation and retry/session guidance, while any safe structured * fields are included without dumping the provider payload into the panel. */ export function formatCodexTurnError(error) { const root = record(error); // Keep the old non-400 behavior: an explicitly empty message stays empty, // while an absent message falls back to the generic label. const message = readRawString(error, "message") ?? (typeof error === "string" ? error : undefined); const envelope = parseMessageEnvelope(message) ?? root; const nestedSources = [ record(readValue(root, "data")), record(readValue(root, "details")), record(readValue(envelope, "data")), record(readValue(envelope, "details")), ]; const sources = [root, envelope, ...nestedSources]; const status = firstNumber(sources, "status", "statusCode", "httpStatus"); const codeCandidates = sources.map((source) => readValue(source, "code", "error_code", "errorCode")); // Keep the two code roles independent: a provider's string code wins the // displayed `code=...` field, while numeric transport/RPC markers still // participate in HTTP-status classification. An exact numeric 400 wins over // another numeric marker (for example JSON-RPC -32600). const providerCode = codeCandidates.find((value) => typeof value === "string" && Boolean(value.trim())); const numericCodes = codeCandidates .map((value) => { if (typeof value === "number" && Number.isFinite(value)) return value; if (typeof value === "string" && /^\d{3}$/.test(value.trim())) return Number(value.trim()); return undefined; }) .filter((value) => value !== undefined); const numericCode = numericCodes.find((value) => value === 400) ?? numericCodes[0]; const code = safeLabel(providerCode?.trim()); const type = safeLabel(firstString(sources, "type", "error_type", "errorType")); const requestId = safeLabel(firstString(sources, "request_id", "requestId")); const is400 = status === 400 || numericCode === 400 || (typeof message === "string" && /^bad request(?:\b|:)/i.test(message.trim())) || hasBadRequestMessage(root) || hasBadRequestMessage(envelope); if (!is400) return message !== undefined ? message : "Codex error"; const fields = [ code && `code=${code}`, type && `type=${type}`, requestId && `request_id=${requestId}`, ].filter(Boolean); const metadata = fields.length ? ` (${fields.join(", ")})` : ""; const missing = [!code && "error code", !requestId && "request id"].filter(Boolean).join(" or "); const missingText = missing ? ` The provider returned no ${missing}.` : ""; return (`Codex rejected this request with HTTP 400 Bad Request${metadata}.` + `${missingText} Possible causes include an oversized context or tool history, an invalid request ` + `payload, an unsupported model parameter, or a transient provider rejection. Retry once; if it ` + `repeats, start a new session or switch models, and check the orchestrator terminal for the raw diagnostic.`); } //# sourceMappingURL=codex-error.js.map