refactor(media): share the ComfyUI JSON request helpers

Queue, interrupt and prompt requests now go through one JSON helper, so the
cancel path reads like the rest of the client.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
This commit is contained in:
Claude 2026-10-03 13:45:50 +00:00
parent 4217e4b4af
commit 3b97805f20
No known key found for this signature in database

View File

@ -75,6 +75,7 @@ export interface ComfyUiProvider extends ImageGenerationProvider, VideoGeneratio
} }
type MediaKind = "image" | "video"; type MediaKind = "image" | "video";
type ReadBody<T> = (response: Response, signal: AbortSignal) => Promise<T>;
interface OutputFile { interface OutputFile {
filename: string; filename: string;
@ -259,20 +260,18 @@ class ComfyUiClient implements ComfyUiProvider {
} }
private async submit(graph: ComfyWorkflow, signal: AbortSignal): Promise<string> { private async submit(graph: ComfyWorkflow, signal: AbortSignal): Promise<string> {
const body = JSON.stringify({ prompt: graph, client_id: this.clientId }); const payload = { prompt: graph, client_id: this.clientId };
return this.send("prompt", { method: "POST", headers: JSON_HEADERS, body }, signal, { return this.postJson("prompt", payload, signal, async (response, readSignal) => {
read: async (response, readSignal) => { if (response.status === 400) {
if (response.status === 400) { const rejection = await readJson(response, readSignal).catch(() => undefined);
const rejection = await readJson(response, readSignal).catch(() => undefined); throw new Error(describeWorkflowRejection(rejection));
throw new Error(describeWorkflowRejection(rejection)); }
} const result = await readOkJson(response, readSignal);
const result = await readOkJson(response, readSignal); const promptId = isRecord(result) ? result.prompt_id : undefined;
const promptId = isRecord(result) ? result.prompt_id : undefined; if (typeof promptId !== "string" || !promptId) {
if (typeof promptId !== "string" || !promptId) { throw new Error("ComfyUI did not accept the workflow");
throw new Error("ComfyUI did not accept the workflow"); }
} return promptId;
return promptId;
},
}); });
} }
@ -291,29 +290,20 @@ class ComfyUiClient implements ComfyUiProvider {
/** Best effort: drops the prompt from the queue, or interrupts it when it is ours and running. */ /** Best effort: drops the prompt from the queue, or interrupts it when it is ours and running. */
private async cancelPrompt(promptId: string): Promise<void> { private async cancelPrompt(promptId: string): Promise<void> {
const options = { timeoutMs: CANCEL_TIMEOUT_MS, read: discardBody };
try { try {
const body = JSON.stringify({ delete: [promptId] }); await this.postJson(
await this.send("queue", { method: "POST", headers: JSON_HEADERS, body }, undefined, options);
const queue = await this.send(
"queue", "queue",
{ headers: { Accept: "application/json" } }, { delete: [promptId] },
undefined, undefined,
{ discardBody,
timeoutMs: CANCEL_TIMEOUT_MS, CANCEL_TIMEOUT_MS,
read: readOkJson,
},
); );
const queue = await this.getJson("queue", undefined, CANCEL_TIMEOUT_MS);
const running = const running =
isRecord(queue) && Array.isArray(queue.queue_running) ? queue.queue_running : []; isRecord(queue) && Array.isArray(queue.queue_running) ? queue.queue_running : [];
if (running.some((entry) => Array.isArray(entry) && entry[1] === promptId)) { if (running.some((entry) => Array.isArray(entry) && entry[1] === promptId)) {
const interrupt = JSON.stringify({ prompt_id: promptId }); const interrupt = { prompt_id: promptId };
await this.send( await this.postJson("interrupt", interrupt, undefined, discardBody, CANCEL_TIMEOUT_MS);
"interrupt",
{ method: "POST", headers: JSON_HEADERS, body: interrupt },
undefined,
options,
);
} }
} catch { } catch {
// The prompt then finishes on its own; nothing waits for it. // The prompt then finishes on its own; nothing waits for it.
@ -379,10 +369,20 @@ class ComfyUiClient implements ComfyUiProvider {
}); });
} }
private getJson(path: string, signal: AbortSignal | undefined): Promise<unknown> { private getJson(path: string, signal: AbortSignal | undefined, timeoutMs?: number) {
return this.send(path, { headers: { Accept: "application/json" } }, signal, { const init = { headers: { Accept: "application/json" } };
read: readOkJson, return this.send(path, init, signal, { timeoutMs, read: readOkJson });
}); }
private postJson<T>(
path: string,
payload: unknown,
signal: AbortSignal | undefined,
read: ReadBody<T>,
timeoutMs?: number,
): Promise<T> {
const init = { method: "POST", headers: JSON_HEADERS, body: JSON.stringify(payload) };
return this.send(path, init, signal, { timeoutMs, read });
} }
/** One request, with the timeout covering the response body too. */ /** One request, with the timeout covering the response body too. */
@ -394,10 +394,7 @@ class ComfyUiClient implements ComfyUiProvider {
body?: string | Uint8Array<ArrayBuffer>; body?: string | Uint8Array<ArrayBuffer>;
}, },
signal: AbortSignal | undefined, signal: AbortSignal | undefined,
options: { options: { timeoutMs?: number; read: ReadBody<T> },
timeoutMs?: number;
read: (response: Response, signal: AbortSignal) => Promise<T>;
},
): Promise<T> { ): Promise<T> {
const timeout = AbortSignal.timeout(options.timeoutMs ?? REQUEST_TIMEOUT_MS); const timeout = AbortSignal.timeout(options.timeoutMs ?? REQUEST_TIMEOUT_MS);
const requestSignal = signal ? AbortSignal.any([signal, timeout]) : timeout; const requestSignal = signal ? AbortSignal.any([signal, timeout]) : timeout;