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
JavaScript
/**
* 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