@fre4x/comfyui 1.0.60 → 1.0.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +663 -1363
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -6801,40 +6801,13 @@ var require_dist = __commonJS({
6801
6801
 
6802
6802
  // src/index.ts
6803
6803
  import * as fs from "node:fs/promises";
6804
+ import * as crypto from "node:crypto";
6804
6805
  import { realpathSync } from "node:fs";
6805
6806
  import { homedir } from "node:os";
6806
6807
  import path from "node:path";
6807
6808
  import { fileURLToPath } from "node:url";
6808
6809
 
6809
6810
  // ../packages/shared/dist/errors.js
6810
- function createApiError(message, statusCode) {
6811
- let hint = "Check your network connection and retry.";
6812
- let type = "Service Error";
6813
- if (statusCode === 429) {
6814
- type = "Rate Limit";
6815
- hint = "Request volume is too high. Suggestion: Wait briefly before retrying or reduce concurrent calls.";
6816
- } else if (statusCode === 401 || statusCode === 403) {
6817
- type = "Authentication Error";
6818
- hint = "The request could not be authorized. Suggestion: Verify your API key or token in the environment configuration.";
6819
- } else if (statusCode && statusCode >= 500) {
6820
- type = "Upstream Error";
6821
- hint = "The remote service is experiencing temporary issues. Suggestion: Try again in a few minutes.";
6822
- } else if (statusCode === 404) {
6823
- type = "Not Found";
6824
- hint = "The requested information could not be found. Suggestion: Check if the ID, Ticker, or query parameters are correct.";
6825
- }
6826
- return {
6827
- isError: true,
6828
- content: [
6829
- {
6830
- type: "text",
6831
- text: `${type}: ${message}
6832
-
6833
- **Next Action**: ${hint}`
6834
- }
6835
- ]
6836
- };
6837
- }
6838
6811
  function createValidationError(field, message) {
6839
6812
  return {
6840
6813
  isError: true,
@@ -6876,6 +6849,20 @@ Suggestion: If the problem persists, check server logs.`
6876
6849
  };
6877
6850
  }
6878
6851
 
6852
+ // ../packages/shared/dist/format.js
6853
+ function formatListItems(items, renderer, emptyMessage = "_No results found._") {
6854
+ if (items.length === 0)
6855
+ return emptyMessage;
6856
+ return items.map((item, i) => renderer(item, i)).join("\n\n");
6857
+ }
6858
+ function formatPaginationFooter(offset, limit, total) {
6859
+ const start = offset + 1;
6860
+ const end = Math.min(offset + limit, total);
6861
+ return `
6862
+ ---
6863
+ _Showing ${start}\u2013${end} of ${total}. Use \`offset: ${offset + limit}\` for the next page._`;
6864
+ }
6865
+
6879
6866
  // ../node_modules/zod/index.js
6880
6867
  var zod_exports = {};
6881
6868
  __export(zod_exports, {
@@ -30582,801 +30569,229 @@ var MOCK_FIXTURES = {
30582
30569
  }
30583
30570
  };
30584
30571
 
30585
- // src/index.ts
30586
- var zNamespace = zod_exports;
30587
- var z3 = zNamespace.z ?? zNamespace.default ?? zNamespace;
30588
- var COMFYUI_SERVER_URL = process.env.COMFYUI_SERVER_URL || "http://localhost:8188";
30589
- var COMFYUI_WORKFLOWS_DIR = process.env.COMFYUI_WORKFLOWS_DIR || path.join(homedir(), ".fre4x-comfyui", "workflows");
30590
- var PACKAGE_VERSION = getPackageVersion(import.meta.url);
30591
- var BYTES_PER_GIB = 1024 ** 3;
30592
- var PaginationMetadataSchema = z3.object({
30593
- total_count: z3.number(),
30594
- limit: z3.number(),
30595
- offset: z3.number(),
30596
- has_more: z3.boolean()
30597
- });
30598
- var SystemInfoSchema = z3.object({
30599
- system: z3.object({
30600
- os: z3.string(),
30601
- python_version: z3.string(),
30602
- embedded_python: z3.boolean()
30603
- }),
30604
- devices: z3.array(
30605
- z3.object({
30606
- name: z3.string(),
30607
- type: z3.string(),
30608
- index: z3.number(),
30609
- vram_total: z3.number(),
30610
- vram_free: z3.number()
30611
- })
30612
- )
30613
- });
30614
- var ObjectInfoSchema = z3.object({
30615
- nodes: z3.record(z3.string(), z3.unknown()),
30616
- pagination: PaginationMetadataSchema.optional()
30617
- });
30618
- var HistoryImageSchema = z3.object({
30619
- filename: z3.string(),
30620
- subfolder: z3.string(),
30621
- type: z3.string(),
30622
- view_url: z3.string().url()
30623
- });
30624
- var WorkflowResponseSchema = z3.object({
30625
- prompt_id: z3.string(),
30626
- number: z3.number(),
30627
- node_errors: z3.record(z3.string(), z3.unknown()),
30628
- status: z3.string().optional(),
30629
- completed: z3.boolean().optional(),
30630
- timed_out: z3.boolean().optional(),
30631
- elapsed_seconds: z3.number().optional(),
30632
- image_count: z3.number().optional(),
30633
- images: z3.array(HistoryImageSchema).optional(),
30634
- history_entry: z3.record(z3.string(), z3.unknown()).nullable().optional()
30635
- });
30636
- var HistorySchema = z3.object({
30637
- history: z3.record(z3.string(), z3.unknown()),
30638
- pagination: PaginationMetadataSchema.optional()
30639
- });
30640
- var WaitForWorkflowSchema = z3.object({
30641
- prompt_id: z3.string(),
30642
- status: z3.string(),
30643
- completed: z3.boolean(),
30644
- timed_out: z3.boolean(),
30645
- elapsed_seconds: z3.number(),
30646
- image_count: z3.number(),
30647
- images: z3.array(HistoryImageSchema),
30648
- history_entry: z3.record(z3.string(), z3.unknown()).nullable()
30649
- });
30650
- var WorkflowTagSchema = z3.string().min(1).max(50);
30651
- var EditableWorkflowValueSchema = z3.union([
30652
- z3.string(),
30653
- z3.number(),
30654
- z3.boolean()
30572
+ // src/converter.ts
30573
+ var WIDGET_TYPES = /* @__PURE__ */ new Set(["INT", "FLOAT", "STRING", "BOOLEAN"]);
30574
+ var UI_CONTROL_VALUES = /* @__PURE__ */ new Set([
30575
+ "fixed",
30576
+ "increment",
30577
+ "decrement",
30578
+ "randomize"
30655
30579
  ]);
30656
- var WorkflowEditableInputSchema = z3.object({
30657
- node_id: z3.string(),
30658
- node_class: z3.string(),
30659
- input_name: z3.string(),
30660
- value_type: z3.enum(["string", "number", "boolean", "enum"]),
30661
- current_value: EditableWorkflowValueSchema,
30662
- options: z3.array(z3.string()).default([])
30663
- });
30664
- var StoredWorkflowMetadataSchema = z3.object({
30665
- id: z3.string(),
30666
- name: z3.string(),
30667
- description: z3.string().optional(),
30668
- tags: z3.array(WorkflowTagSchema),
30669
- created_at: z3.string(),
30670
- updated_at: z3.string(),
30671
- node_count: z3.number(),
30672
- editable_input_count: z3.number(),
30673
- source: z3.enum(["local", "mock"])
30674
- });
30675
- var StoredWorkflowSchema = z3.object({
30676
- workflow: StoredWorkflowMetadataSchema,
30677
- prompt: z3.record(z3.string(), z3.unknown()),
30678
- editable_inputs: z3.array(WorkflowEditableInputSchema)
30679
- });
30680
- var WorkflowListSchema = z3.object({
30681
- workflows: z3.array(StoredWorkflowMetadataSchema),
30682
- pagination: PaginationMetadataSchema.optional()
30683
- });
30684
- var WorkflowOverrideSchema = z3.object({
30685
- node_id: z3.string().min(1).describe("Node ID to override."),
30686
- input_name: z3.string().min(1).describe("Input field name to override."),
30687
- value: EditableWorkflowValueSchema.describe("Replacement scalar value.")
30688
- });
30689
- var RunStoredWorkflowSchema = WorkflowResponseSchema.extend({
30690
- workflow_id: z3.string(),
30691
- workflow_name: z3.string(),
30692
- applied_overrides: z3.array(WorkflowOverrideSchema)
30693
- });
30694
- var QueueSchema = z3.object({
30695
- queue_running: z3.array(z3.unknown()),
30696
- queue_pending: z3.array(z3.unknown())
30697
- });
30698
- var ViewUrlSchema = z3.object({
30699
- url: z3.string().url()
30700
- });
30701
- var NoArgsSchema = z3.object({}).strict();
30702
- var ListObjectInfoInputSchema = z3.object({
30703
- node_class: z3.string().optional().describe(
30704
- "Specific node class to inspect (for example 'KSampler')."
30705
- ),
30706
- limit: z3.number().int().min(1).max(100).default(50).describe("Pagination limit."),
30707
- offset: z3.number().int().min(0).default(0).describe("Pagination offset.")
30708
- }).strict();
30709
- var ExecuteWorkflowInputSchema = z3.object({
30710
- prompt: z3.record(z3.string(), z3.unknown()).describe("Workflow in ComfyUI API JSON format."),
30711
- client_id: z3.string().optional().describe("Optional unique client identifier."),
30712
- await: z3.boolean().default(false).describe("Wait for completion before returning."),
30713
- timeout: z3.number().int().min(1).max(600).default(120).describe("Wait timeout in seconds.")
30714
- }).strict();
30715
- var GetHistoryInputSchema = z3.object({
30716
- prompt_id: z3.string().optional().describe("Specific prompt ID to retrieve. Omit for full history."),
30717
- limit: z3.number().int().min(1).max(100).default(20).describe("Pagination limit."),
30718
- offset: z3.number().int().min(0).default(0).describe("Pagination offset.")
30719
- }).strict();
30720
- var GetViewUrlInputSchema = z3.object({
30721
- filename: z3.string().min(1).describe("Name of the file to view."),
30722
- subfolder: z3.string().optional().describe("Subfolder within the output directory."),
30723
- type: z3.enum(["output", "input", "temp"]).default("output").describe("Storage type.")
30724
- }).strict();
30725
- var ListWorkflowsInputSchema = z3.object({
30726
- limit: z3.number().int().min(1).max(100).default(50).describe("Pagination limit."),
30727
- offset: z3.number().int().min(0).default(0).describe("Pagination offset.")
30728
- }).strict();
30729
- var SearchWorkflowsInputSchema = z3.object({
30730
- query: z3.string().min(1).describe("Search text for name, tags, or description."),
30731
- limit: z3.number().int().min(1).max(100).default(50).describe("Pagination limit."),
30732
- offset: z3.number().int().min(0).default(0).describe("Pagination offset.")
30733
- }).strict();
30734
- var GetWorkflowInputSchema = z3.object({
30735
- workflow_id: z3.string().min(1).describe("Stored workflow ID.")
30736
- }).strict();
30737
- var SaveWorkflowInputSchema = z3.object({
30738
- workflow_id: z3.string().min(1).optional().describe("Optional stable workflow ID."),
30739
- name: z3.string().min(1).describe("Human-readable workflow name."),
30740
- description: z3.string().optional().describe("Optional workflow summary."),
30741
- tags: z3.array(WorkflowTagSchema).default([]).describe("Optional workflow tags."),
30742
- prompt: z3.record(z3.string(), z3.unknown()).describe("Workflow in ComfyUI API JSON format."),
30743
- overwrite: z3.boolean().default(false).describe("Overwrite an existing workflow with the same ID.")
30744
- }).strict();
30745
- var RunWorkflowInputSchema = z3.object({
30746
- workflow_id: z3.string().min(1).describe("Stored workflow ID to execute."),
30747
- overrides: z3.array(WorkflowOverrideSchema).default([]).describe("Editable input overrides to apply before execution."),
30748
- client_id: z3.string().optional().describe("Optional unique client identifier."),
30749
- await: z3.boolean().default(false).describe("Wait for completion before returning."),
30750
- timeout: z3.number().int().min(1).max(600).default(120).describe("Wait timeout in seconds."),
30751
- require_images: z3.boolean().default(false).describe("When awaiting, require generated images before return.")
30752
- }).strict();
30753
- var WaitForWorkflowInputSchema = z3.object({
30754
- prompt_id: z3.string().min(1).describe("Prompt ID to wait for."),
30755
- timeout_seconds: z3.number().int().min(1).max(600).default(120).describe("Maximum wait time in seconds."),
30756
- poll_interval_ms: z3.number().int().min(100).max(1e4).default(1e3).describe("Polling interval in milliseconds."),
30757
- require_images: z3.boolean().default(false).describe("Wait until images are available too.")
30758
- }).strict();
30759
- var ComfyApiError = class extends Error {
30760
- constructor(message, statusCode, details) {
30761
- super(message);
30762
- this.statusCode = statusCode;
30763
- this.details = details;
30764
- this.name = "ComfyApiError";
30765
- }
30766
- };
30767
- function isRecord(value) {
30768
- return typeof value === "object" && value !== null && !Array.isArray(value);
30769
- }
30770
- function ensureRecord(value, label) {
30771
- if (!isRecord(value)) {
30772
- throw new Error(`Invalid ${label} response from ComfyUI.`);
30773
- }
30774
- return value;
30775
- }
30776
- function isEditableWorkflowValue(value) {
30777
- return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
30778
- }
30779
- function slugifyWorkflowId(value) {
30780
- const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
30781
- return slug.length > 0 ? slug : "workflow";
30782
- }
30783
- function getWorkflowFilePath(workflowId) {
30784
- return path.join(COMFYUI_WORKFLOWS_DIR, `${workflowId}.json`);
30580
+ function isWebUIFormat(json2) {
30581
+ return typeof json2 === "object" && json2 !== null && Array.isArray(json2.nodes) && Array.isArray(json2.links);
30785
30582
  }
30786
- async function ensureWorkflowStoreDir() {
30787
- await fs.mkdir(COMFYUI_WORKFLOWS_DIR, { recursive: true });
30583
+ function isLinkData(link) {
30584
+ return Array.isArray(link) && link.length >= 6 && typeof link[0] === "number" && typeof link[1] === "number" && typeof link[2] === "number" && typeof link[3] === "number" && typeof link[4] === "number" && typeof link[5] === "string";
30788
30585
  }
30789
- async function readStoredWorkflowFile(workflowId) {
30790
- try {
30791
- const content = await fs.readFile(
30792
- getWorkflowFilePath(workflowId),
30793
- "utf8"
30794
- );
30795
- return StoredWorkflowSchema.parse(JSON.parse(content));
30796
- } catch (error48) {
30797
- if (isRecord(error48) && error48.code === "ENOENT") {
30798
- return void 0;
30799
- }
30800
- throw error48;
30586
+ function getOrderedInputEntries(group, orderedNames) {
30587
+ if (!group) {
30588
+ return [];
30801
30589
  }
30802
- }
30803
- async function writeStoredWorkflowFile(workflow) {
30804
- await ensureWorkflowStoreDir();
30805
- await fs.writeFile(
30806
- getWorkflowFilePath(workflow.workflow.id),
30807
- `${JSON.stringify(workflow, null, 2)}
30808
- `,
30809
- "utf8"
30810
- );
30811
- }
30812
- async function listStoredWorkflowFiles() {
30813
- await ensureWorkflowStoreDir();
30814
- const entries = await fs.readdir(COMFYUI_WORKFLOWS_DIR, {
30815
- withFileTypes: true
30816
- });
30817
- const workflows = [];
30818
- for (const entry of entries) {
30819
- if (!entry.isFile() || !entry.name.endsWith(".json")) {
30590
+ const seen = /* @__PURE__ */ new Set();
30591
+ const orderedEntries = [];
30592
+ for (const inputName of orderedNames ?? []) {
30593
+ const inputDefinition = group[inputName];
30594
+ if (!inputDefinition) {
30820
30595
  continue;
30821
30596
  }
30822
- const content = await fs.readFile(
30823
- path.join(COMFYUI_WORKFLOWS_DIR, entry.name),
30824
- "utf8"
30825
- );
30826
- workflows.push(StoredWorkflowSchema.parse(JSON.parse(content)));
30827
- }
30828
- workflows.sort(
30829
- (left, right) => right.workflow.updated_at.localeCompare(left.workflow.updated_at)
30830
- );
30831
- return workflows;
30832
- }
30833
- function unwrapNamedNode(nodeClass, value) {
30834
- if (!isRecord(value)) {
30835
- return value;
30836
- }
30837
- if (nodeClass in value) {
30838
- return value[nodeClass];
30839
- }
30840
- return value;
30841
- }
30842
- function getErrorMessageFromDetails(details) {
30843
- if (!isRecord(details)) {
30844
- return void 0;
30845
- }
30846
- const errorValue = details.error;
30847
- if (typeof errorValue === "string") {
30848
- return errorValue;
30597
+ orderedEntries.push([inputName, inputDefinition]);
30598
+ seen.add(inputName);
30849
30599
  }
30850
- if (isRecord(errorValue) && typeof errorValue.message === "string") {
30851
- return errorValue.message;
30852
- }
30853
- return void 0;
30854
- }
30855
- function getNodeErrors(details) {
30856
- if (!isRecord(details) || !isRecord(details.node_errors)) {
30857
- return void 0;
30600
+ for (const [inputName, inputDefinition] of Object.entries(group)) {
30601
+ if (seen.has(inputName)) {
30602
+ continue;
30603
+ }
30604
+ orderedEntries.push([inputName, inputDefinition]);
30858
30605
  }
30859
- return details.node_errors;
30860
- }
30861
- function normalizeSystemInfo(source) {
30862
- const record2 = ensureRecord(source, "system info");
30863
- const systemRecord = ensureRecord(record2.system, "system info.system");
30864
- const devicesValue = Array.isArray(record2.devices) ? record2.devices : [];
30865
- return {
30866
- system: {
30867
- os: typeof systemRecord.os === "string" ? systemRecord.os : "unknown",
30868
- python_version: typeof systemRecord.python_version === "string" ? systemRecord.python_version : "unknown",
30869
- embedded_python: systemRecord.embedded_python === true
30870
- },
30871
- devices: devicesValue.map((device) => {
30872
- const deviceRecord = ensureRecord(device, "system info.devices[]");
30873
- return {
30874
- name: typeof deviceRecord.name === "string" ? deviceRecord.name : "unknown",
30875
- type: typeof deviceRecord.type === "string" ? deviceRecord.type : "unknown",
30876
- index: typeof deviceRecord.index === "number" ? deviceRecord.index : -1,
30877
- vram_total: typeof deviceRecord.vram_total === "number" ? deviceRecord.vram_total : 0,
30878
- vram_free: typeof deviceRecord.vram_free === "number" ? deviceRecord.vram_free : 0
30879
- };
30880
- })
30881
- };
30882
- }
30883
- function formatGiB(bytes) {
30884
- return `${(bytes / BYTES_PER_GIB).toFixed(1)} GiB`;
30606
+ return orderedEntries;
30885
30607
  }
30886
- function formatJsonCodeBlock(value) {
30887
- return `\`\`\`json
30888
- ${JSON.stringify(value, null, 2)}
30889
- \`\`\``;
30608
+ function getWidgetTypeName(inputTypeData) {
30609
+ return Array.isArray(inputTypeData) ? "COMBO" : String(inputTypeData).toUpperCase();
30890
30610
  }
30891
- function formatMarkdownBulletList(items) {
30892
- return items.length > 0 ? items.map((item) => `- ${item}`).join("\n") : "- none";
30611
+ function isWidgetInput(inputTypeData) {
30612
+ const typeName = getWidgetTypeName(inputTypeData);
30613
+ return typeName === "COMBO" || WIDGET_TYPES.has(typeName);
30893
30614
  }
30894
- async function sleep(ms) {
30895
- await new Promise((resolve) => setTimeout(resolve, ms));
30896
- }
30897
- function getEnumLikeOptions(value) {
30898
- if (!Array.isArray(value) || value.length === 0) {
30899
- return [];
30900
- }
30901
- const firstEntry = value[0];
30902
- if (!Array.isArray(firstEntry)) {
30903
- return [];
30904
- }
30905
- return firstEntry.filter(
30906
- (entry) => typeof entry === "string" || typeof entry === "number" || typeof entry === "boolean"
30907
- );
30908
- }
30909
- function buildViewUrl(filename, type, subfolder) {
30910
- const params = new URLSearchParams({
30911
- filename,
30912
- type
30913
- });
30914
- if (subfolder) {
30915
- params.set("subfolder", subfolder);
30615
+ function isSkippableUiArtifact(value, expectedTypeName) {
30616
+ if (expectedTypeName !== "STRING" && typeof value === "string" && UI_CONTROL_VALUES.has(value)) {
30617
+ return true;
30916
30618
  }
30917
- return new URL(`/view?${params.toString()}`, COMFYUI_SERVER_URL).toString();
30619
+ return typeof value === "object" && value !== null && !Array.isArray(value);
30918
30620
  }
30919
- async function fetchNodeDefinition(nodeClass) {
30920
- if (IS_MOCK) {
30921
- return MOCK_FIXTURES.object_info[nodeClass];
30922
- }
30923
- try {
30924
- const rawInfo = await fetchComfyJson(
30925
- `/object_info/${encodeURIComponent(nodeClass)}`
30926
- );
30927
- return unwrapNamedNode(nodeClass, rawInfo);
30928
- } catch (error48) {
30929
- if (error48 instanceof ComfyApiError && error48.statusCode === 404) {
30930
- return void 0;
30931
- }
30932
- throw error48;
30621
+ function normalizeWidgetValue(value, typeName, extraInfo) {
30622
+ const defaultValue = extraInfo?.default;
30623
+ const shouldUseDefaultForNumeric = (typeName === "INT" || typeName === "FLOAT") && (value === "" || value === void 0 || value === null);
30624
+ if (shouldUseDefaultForNumeric && defaultValue !== void 0) {
30625
+ return defaultValue;
30933
30626
  }
30627
+ return value;
30934
30628
  }
30935
- async function deriveEditableWorkflowInputs(prompt) {
30936
- const nodeDefinitions = /* @__PURE__ */ new Map();
30937
- const editableInputs = [];
30938
- for (const [nodeId, nodeValue] of Object.entries(prompt)) {
30939
- if (!isRecord(nodeValue)) {
30940
- continue;
30941
- }
30942
- const nodeClass = typeof nodeValue.class_type === "string" ? nodeValue.class_type : void 0;
30943
- const inputs = isRecord(nodeValue.inputs) ? nodeValue.inputs : void 0;
30944
- if (!nodeClass || !inputs) {
30945
- continue;
30946
- }
30947
- if (!nodeDefinitions.has(nodeClass)) {
30948
- nodeDefinitions.set(
30949
- nodeClass,
30950
- await fetchNodeDefinition(nodeClass)
30951
- );
30952
- }
30953
- const nodeDefinition = nodeDefinitions.get(nodeClass);
30954
- const nodeInputRecord = isRecord(nodeDefinition) && isRecord(nodeDefinition.input) ? nodeDefinition.input : void 0;
30955
- const requiredInputs = isRecord(nodeInputRecord?.required) ? nodeInputRecord.required : void 0;
30956
- const optionalInputs = isRecord(nodeInputRecord?.optional) ? nodeInputRecord.optional : void 0;
30957
- for (const [inputName, inputValue] of Object.entries(inputs)) {
30958
- if (!isEditableWorkflowValue(inputValue)) {
30959
- continue;
30629
+ function convertWebUIToAPI(webUIJson, objectInfo) {
30630
+ const prompt = {};
30631
+ const links = webUIJson.links || [];
30632
+ const nodes = webUIJson.nodes || [];
30633
+ const linkMap = /* @__PURE__ */ new Map();
30634
+ for (const link of links) {
30635
+ if (isLinkData(link)) {
30636
+ linkMap.set(link[0], link);
30637
+ }
30638
+ }
30639
+ for (const node of nodes) {
30640
+ if (!node.id || !node.type) continue;
30641
+ const objInfo = objectInfo[node.type];
30642
+ if (!objInfo) continue;
30643
+ const nodeIdStr = String(node.id);
30644
+ const promptNode = {
30645
+ class_type: node.type,
30646
+ inputs: {}
30647
+ };
30648
+ const connectedInputNames = /* @__PURE__ */ new Set();
30649
+ if (Array.isArray(node.inputs)) {
30650
+ for (const input of node.inputs) {
30651
+ if (input.name && input.link !== null && input.link !== void 0) {
30652
+ const linkData = linkMap.get(input.link);
30653
+ if (Array.isArray(linkData) && linkData.length >= 6) {
30654
+ const originNodeId = String(linkData[1]);
30655
+ const originSlot = linkData[2];
30656
+ promptNode.inputs[input.name] = [
30657
+ originNodeId,
30658
+ originSlot
30659
+ ];
30660
+ connectedInputNames.add(input.name);
30661
+ }
30662
+ }
30960
30663
  }
30961
- const definition = requiredInputs?.[inputName] ?? optionalInputs?.[inputName];
30962
- const options = getEnumLikeOptions(definition).map(String);
30963
- const valueType = typeof inputValue === "boolean" ? "boolean" : typeof inputValue === "number" ? "number" : options.length > 0 ? "enum" : "string";
30964
- editableInputs.push({
30965
- node_id: nodeId,
30966
- node_class: nodeClass,
30967
- input_name: inputName,
30968
- value_type: valueType,
30969
- current_value: inputValue,
30970
- options
30971
- });
30972
30664
  }
30973
- }
30974
- return editableInputs;
30975
- }
30976
- function cloneWorkflowPrompt(prompt) {
30977
- return JSON.parse(JSON.stringify(prompt));
30978
- }
30979
- function applyWorkflowOverrides(prompt, editableInputs, overrides) {
30980
- const nextPrompt = cloneWorkflowPrompt(prompt);
30981
- const editableKeys = new Set(
30982
- editableInputs.map((input) => `${input.node_id}.${input.input_name}`)
30983
- );
30984
- for (const override of overrides) {
30985
- const key = `${override.node_id}.${override.input_name}`;
30986
- if (!editableKeys.has(key)) {
30987
- throw new Error(
30988
- `Override ${key} is not editable for this stored workflow.`
30665
+ if (objInfo) {
30666
+ const widgets = Array.isArray(node.widgets_values) ? node.widgets_values : [];
30667
+ let widgetIndex = 0;
30668
+ const processInputGroup = (group, orderedNames) => {
30669
+ for (const [inputName, inputDataRaw] of getOrderedInputEntries(
30670
+ group,
30671
+ orderedNames
30672
+ )) {
30673
+ const [inputTypeData, inputExtraInfo] = inputDataRaw;
30674
+ if (!isWidgetInput(inputTypeData)) {
30675
+ continue;
30676
+ }
30677
+ const typeName = getWidgetTypeName(inputTypeData);
30678
+ while (widgetIndex < widgets.length && isSkippableUiArtifact(widgets[widgetIndex], typeName)) {
30679
+ widgetIndex++;
30680
+ }
30681
+ const isConnectedWidget = connectedInputNames.has(inputName);
30682
+ if (widgetIndex >= widgets.length) {
30683
+ if (!isConnectedWidget && inputExtraInfo?.default !== void 0) {
30684
+ promptNode.inputs[inputName] = inputExtraInfo.default;
30685
+ }
30686
+ continue;
30687
+ }
30688
+ const value = normalizeWidgetValue(
30689
+ widgets[widgetIndex],
30690
+ typeName,
30691
+ inputExtraInfo
30692
+ );
30693
+ widgetIndex++;
30694
+ if (!isConnectedWidget) {
30695
+ promptNode.inputs[inputName] = value;
30696
+ }
30697
+ }
30698
+ };
30699
+ processInputGroup(
30700
+ objInfo.input?.required,
30701
+ objInfo.input_order?.required
30989
30702
  );
30990
- }
30991
- const node = nextPrompt[override.node_id];
30992
- if (!isRecord(node) || !isRecord(node.inputs)) {
30993
- throw new Error(
30994
- `Stored workflow node ${override.node_id} is invalid.`
30703
+ processInputGroup(
30704
+ objInfo.input?.optional,
30705
+ objInfo.input_order?.optional
30995
30706
  );
30996
30707
  }
30997
- node.inputs[override.input_name] = override.value;
30708
+ prompt[nodeIdStr] = promptNode;
30998
30709
  }
30999
- return nextPrompt;
30710
+ return prompt;
31000
30711
  }
31001
- function summarizeOptionLines(inputSection) {
31002
- if (!inputSection) {
31003
- return [];
30712
+
30713
+ // src/index.ts
30714
+ var zNamespace = zod_exports;
30715
+ var z3 = zNamespace.z ?? zNamespace.default ?? zNamespace;
30716
+ var COMFYUI_SERVER_URL = process.env.COMFYUI_SERVER_URL || "http://localhost:8188";
30717
+ var PACKAGE_VERSION = getPackageVersion(import.meta.url);
30718
+ var ComfyApiError = class extends Error {
30719
+ constructor(message, statusCode, details) {
30720
+ super(message);
30721
+ this.statusCode = statusCode;
30722
+ this.details = details;
30723
+ this.name = "ComfyApiError";
31004
30724
  }
31005
- return Object.entries(inputSection).flatMap(([inputName, definition]) => {
31006
- const options = getEnumLikeOptions(definition);
31007
- if (options.length === 0) {
31008
- return [];
31009
- }
31010
- return [`${inputName}: ${options.map(String).join(", ")}`];
31011
- });
31012
- }
31013
- function summarizeSystemInfoText(structuredContent) {
31014
- const deviceSummary = formatMarkdownBulletList(
31015
- structuredContent.devices.map(
31016
- (device) => `[${device.index}] ${device.name} (${device.type}, ${formatGiB(device.vram_free)} free / ${formatGiB(device.vram_total)} total VRAM)`
31017
- )
31018
- );
31019
- return [
31020
- "# ComfyUI System Info",
31021
- "",
31022
- `ComfyUI server is running on ${structuredContent.system.os} with ${structuredContent.devices.length} device(s).`,
31023
- "",
31024
- "## Summary",
31025
- formatMarkdownBulletList([
31026
- `OS: ${structuredContent.system.os}`,
31027
- `Python: ${structuredContent.system.python_version}`,
31028
- `Embedded Python: ${structuredContent.system.embedded_python ? "yes" : "no"}`,
31029
- `Devices: ${structuredContent.devices.length}`
31030
- ]),
31031
- "",
31032
- "## Devices",
31033
- deviceSummary,
31034
- "",
31035
- "## JSON",
31036
- formatJsonCodeBlock(structuredContent)
31037
- ].join("\n");
31038
- }
31039
- function summarizeNodeDefinitionText(nodeClass, nodeDefinition) {
31040
- if (!isRecord(nodeDefinition)) {
31041
- return `Schema retrieved for node: ${nodeClass}`;
31042
- }
31043
- const inputRecord = isRecord(nodeDefinition.input) ? nodeDefinition.input : void 0;
31044
- const requiredInputs = isRecord(inputRecord?.required) ? inputRecord.required : void 0;
31045
- const optionalInputs = isRecord(inputRecord?.optional) ? inputRecord.optional : void 0;
31046
- const outputNames = Array.isArray(nodeDefinition.output) ? nodeDefinition.output.filter(
31047
- (output) => typeof output === "string"
31048
- ) : [];
31049
- const optionLines = [
31050
- ...summarizeOptionLines(requiredInputs),
31051
- ...summarizeOptionLines(optionalInputs)
31052
- ];
31053
- return [
31054
- "# ComfyUI Node Definition",
31055
- "",
31056
- `Schema retrieved for node: ${nodeClass}`,
31057
- "",
31058
- "## Summary",
31059
- formatMarkdownBulletList([
31060
- `Node Class: ${nodeClass}`,
31061
- `Display Name: ${typeof nodeDefinition.display_name === "string" ? nodeDefinition.display_name : typeof nodeDefinition.name === "string" ? nodeDefinition.name : nodeClass}`,
31062
- `Category: ${typeof nodeDefinition.category === "string" ? nodeDefinition.category : "unknown"}`,
31063
- `Required Inputs: ${requiredInputs ? Object.keys(requiredInputs).join(", ") || "none" : "none"}`,
31064
- `Optional Inputs: ${optionalInputs ? Object.keys(optionalInputs).join(", ") || "none" : "none"}`,
31065
- `Outputs: ${outputNames.join(", ") || "none"}`
31066
- ]),
31067
- "",
31068
- optionLines.length > 0 ? `## Enumerated Options
31069
- ${optionLines.map((line) => `- ${line}`).join("\n")}
31070
- ` : "",
31071
- "## JSON",
31072
- formatJsonCodeBlock({
31073
- nodes: { [nodeClass]: nodeDefinition }
31074
- }),
31075
- ""
31076
- ].filter(Boolean).join("\n");
31077
- }
31078
- function summarizeHistoryEntryText(promptId, entry) {
31079
- if (!isRecord(entry)) {
31080
- return `History retrieved for job: ${promptId}`;
31081
- }
31082
- const statusRecord = isRecord(entry.status) ? entry.status : void 0;
31083
- const outputsRecord = isRecord(entry.outputs) ? entry.outputs : void 0;
31084
- const imageRefs = [];
31085
- if (outputsRecord) {
31086
- for (const output of Object.values(outputsRecord)) {
31087
- if (!isRecord(output) || !Array.isArray(output.images)) {
31088
- continue;
31089
- }
31090
- for (const image of output.images) {
31091
- if (!isRecord(image) || typeof image.filename !== "string") {
31092
- continue;
31093
- }
31094
- const subfolder = typeof image.subfolder === "string" && image.subfolder ? `${image.subfolder}/` : "";
31095
- imageRefs.push(`${subfolder}${image.filename}`);
31096
- }
31097
- }
31098
- }
31099
- return [
31100
- "# ComfyUI History Entry",
31101
- "",
31102
- `History retrieved for job: ${promptId}`,
31103
- "",
31104
- "## Summary",
31105
- formatMarkdownBulletList([
31106
- `Prompt ID: ${promptId}`,
31107
- `Status: ${typeof statusRecord?.status_str === "string" ? statusRecord.status_str : "unknown"}`,
31108
- `Completed: ${statusRecord?.completed === true ? "yes" : statusRecord?.completed === false ? "no" : "unknown"}`,
31109
- `Images: ${imageRefs.length}`
31110
- ]),
31111
- "",
31112
- "## Images",
31113
- formatMarkdownBulletList(imageRefs),
31114
- "",
31115
- "## JSON",
31116
- formatJsonCodeBlock({
31117
- history: { [promptId]: entry }
31118
- })
31119
- ].join("\n");
30725
+ };
30726
+ function isRecord(value) {
30727
+ return typeof value === "object" && value !== null;
31120
30728
  }
31121
- function extractHistoryImages(entry) {
31122
- if (!isRecord(entry) || !isRecord(entry.outputs)) {
30729
+ function summarizeNodeErrors(nodeErrors) {
30730
+ if (!isRecord(nodeErrors)) {
31123
30731
  return [];
31124
30732
  }
31125
- const images = [];
31126
- for (const output of Object.values(entry.outputs)) {
31127
- if (!isRecord(output) || !Array.isArray(output.images)) {
30733
+ const lines = [];
30734
+ for (const [nodeId, rawNodeError] of Object.entries(nodeErrors)) {
30735
+ if (!isRecord(rawNodeError)) {
31128
30736
  continue;
31129
30737
  }
31130
- for (const image of output.images) {
31131
- if (!isRecord(image) || typeof image.filename !== "string" || typeof image.type !== "string") {
30738
+ const classType = typeof rawNodeError.class_type === "string" ? ` (${rawNodeError.class_type})` : "";
30739
+ const rawErrors = Array.isArray(rawNodeError.errors) ? rawNodeError.errors : [];
30740
+ const missingInputs = [];
30741
+ const otherMessages = [];
30742
+ for (const rawError of rawErrors) {
30743
+ if (!isRecord(rawError)) {
31132
30744
  continue;
31133
30745
  }
31134
- const subfolder = typeof image.subfolder === "string" ? image.subfolder : "";
31135
- images.push({
31136
- filename: image.filename,
31137
- subfolder,
31138
- type: image.type,
31139
- view_url: buildViewUrl(
31140
- image.filename,
31141
- image.type,
31142
- subfolder || void 0
31143
- )
31144
- });
30746
+ const errorType = rawError.type;
30747
+ const errorMessage = rawError.message;
30748
+ const errorDetails = rawError.details;
30749
+ if (errorType === "required_input_missing" && typeof errorDetails === "string") {
30750
+ missingInputs.push(errorDetails);
30751
+ continue;
30752
+ }
30753
+ if (typeof errorMessage === "string") {
30754
+ otherMessages.push(
30755
+ typeof errorDetails === "string" && errorDetails.length > 0 ? `${errorMessage}: ${errorDetails}` : errorMessage
30756
+ );
30757
+ }
30758
+ }
30759
+ const parts = [];
30760
+ if (missingInputs.length > 0) {
30761
+ parts.push(`missing inputs: ${missingInputs.join(", ")}`);
30762
+ }
30763
+ if (otherMessages.length > 0) {
30764
+ parts.push(otherMessages.join("; "));
30765
+ }
30766
+ if (parts.length > 0) {
30767
+ lines.push(`Node ${nodeId}${classType}: ${parts.join("; ")}`);
31145
30768
  }
31146
30769
  }
31147
- return images;
30770
+ return lines;
31148
30771
  }
31149
- function getHistoryStatus(entry) {
31150
- if (!isRecord(entry) || !isRecord(entry.status)) {
31151
- return "unknown";
30772
+ function summarizeComfyErrorDetails(details) {
30773
+ if (!isRecord(details)) {
30774
+ return "";
31152
30775
  }
31153
- return typeof entry.status.status_str === "string" ? entry.status.status_str : "unknown";
31154
- }
31155
- function isHistoryCompleted(entry) {
31156
- if (!isRecord(entry) || !isRecord(entry.status)) {
31157
- return false;
30776
+ const lines = [];
30777
+ const errorDetails = isRecord(details.error) ? details.error : void 0;
30778
+ if (typeof errorDetails?.message === "string") {
30779
+ lines.push(errorDetails.message);
30780
+ } else if (typeof details.message === "string") {
30781
+ lines.push(details.message);
31158
30782
  }
31159
- if (entry.status.completed === true) {
31160
- return true;
30783
+ const nodeLines = summarizeNodeErrors(details.node_errors);
30784
+ if (nodeLines.length > 0) {
30785
+ lines.push(...nodeLines);
31161
30786
  }
31162
- return entry.status.status_str === "error";
31163
- }
31164
- function summarizeWaitForWorkflowText(structuredContent) {
31165
- return [
31166
- "# ComfyUI Workflow Wait Result",
31167
- "",
31168
- "## Summary",
31169
- formatMarkdownBulletList([
31170
- `Prompt ID: ${structuredContent.prompt_id}`,
31171
- `Status: ${structuredContent.status}`,
31172
- `Completed: ${structuredContent.completed ? "yes" : "no"}`,
31173
- `Timed Out: ${structuredContent.timed_out ? "yes" : "no"}`,
31174
- `Elapsed Seconds: ${structuredContent.elapsed_seconds}`,
31175
- `Images: ${structuredContent.image_count}`
31176
- ]),
31177
- "",
31178
- "## Images",
31179
- formatMarkdownBulletList(
31180
- structuredContent.images.map(
31181
- (image) => `${image.subfolder ? `${image.subfolder}/` : ""}${image.filename} (${image.type}) -> ${image.view_url}`
31182
- )
31183
- ),
31184
- "",
31185
- "## JSON",
31186
- formatJsonCodeBlock(structuredContent)
31187
- ].join("\n");
31188
- }
31189
- function summarizeWorkflowListText(heading, workflows, pagination) {
31190
- return [
31191
- heading,
31192
- "",
31193
- "## Pagination",
31194
- formatMarkdownBulletList([
31195
- `Returned: ${workflows.length}`,
31196
- `Total: ${pagination.total_count}`,
31197
- `Limit: ${pagination.limit}`,
31198
- `Offset: ${pagination.offset}`,
31199
- `Has More: ${pagination.has_more ? "yes" : "no"}`
31200
- ]),
31201
- "",
31202
- "## Workflows",
31203
- formatMarkdownBulletList(
31204
- workflows.map(
31205
- (workflow) => `${workflow.id} \u2014 ${workflow.name} | tags: ${workflow.tags.join(", ") || "none"} | editable inputs: ${workflow.editable_input_count}`
31206
- )
31207
- ),
31208
- "",
31209
- "## JSON",
31210
- formatJsonCodeBlock({
31211
- workflows,
31212
- pagination
31213
- })
31214
- ].join("\n");
31215
- }
31216
- function summarizeStoredWorkflowText(workflow) {
31217
- return [
31218
- "# ComfyUI Stored Workflow",
31219
- "",
31220
- "## Summary",
31221
- formatMarkdownBulletList([
31222
- `Workflow ID: ${workflow.workflow.id}`,
31223
- `Name: ${workflow.workflow.name}`,
31224
- `Description: ${workflow.workflow.description ?? "none"}`,
31225
- `Tags: ${workflow.workflow.tags.join(", ") || "none"}`,
31226
- `Created At: ${workflow.workflow.created_at}`,
31227
- `Updated At: ${workflow.workflow.updated_at}`,
31228
- `Node Count: ${workflow.workflow.node_count}`,
31229
- `Editable Inputs: ${workflow.workflow.editable_input_count}`,
31230
- `Source: ${workflow.workflow.source}`
31231
- ]),
31232
- "",
31233
- "## Editable Inputs",
31234
- formatMarkdownBulletList(
31235
- workflow.editable_inputs.map(
31236
- (input) => `${input.node_id}.${input.input_name} (${input.node_class}, ${input.value_type}) = ${String(
31237
- input.current_value
31238
- )}${input.options.length > 0 ? ` | options: ${input.options.join(", ")}` : ""}`
31239
- )
31240
- ),
31241
- "",
31242
- "## JSON",
31243
- formatJsonCodeBlock(workflow)
31244
- ].join("\n");
31245
- }
31246
- function summarizeSavedWorkflowText(workflow) {
31247
- return [
31248
- "# ComfyUI Workflow Saved",
31249
- "",
31250
- "## Summary",
31251
- formatMarkdownBulletList([
31252
- `Workflow ID: ${workflow.workflow.id}`,
31253
- `Name: ${workflow.workflow.name}`,
31254
- `Tags: ${workflow.workflow.tags.join(", ") || "none"}`,
31255
- `Editable Inputs: ${workflow.workflow.editable_input_count}`
31256
- ]),
31257
- "",
31258
- "## Next Step",
31259
- `Call comfyui_run_workflow with workflow_id="${workflow.workflow.id}" and optional overrides to reuse it.`,
31260
- "",
31261
- "## JSON",
31262
- formatJsonCodeBlock(workflow)
31263
- ].join("\n");
31264
- }
31265
- async function waitForWorkflowResult(params) {
31266
- const startTime = Date.now();
31267
- const timeoutMs = params.timeout_seconds * 1e3;
31268
- let latestEntry;
31269
- while (Date.now() - startTime <= timeoutMs) {
31270
- latestEntry = await fetchHistoryEntry(params.prompt_id);
31271
- if (latestEntry !== void 0) {
31272
- const images = extractHistoryImages(latestEntry);
31273
- const completed = isHistoryCompleted(latestEntry);
31274
- if (completed && (!params.require_images || images.length > 0)) {
31275
- return {
31276
- prompt_id: params.prompt_id,
31277
- status: getHistoryStatus(latestEntry),
31278
- completed: true,
31279
- timed_out: false,
31280
- elapsed_seconds: Number(
31281
- ((Date.now() - startTime) / 1e3).toFixed(3)
31282
- ),
31283
- image_count: images.length,
31284
- images,
31285
- history_entry: ensureRecord(latestEntry, "history entry")
31286
- };
31287
- }
31288
- }
31289
- await sleep(params.poll_interval_ms);
31290
- }
31291
- const timeoutImages = latestEntry !== void 0 ? extractHistoryImages(latestEntry) : [];
31292
- return {
31293
- prompt_id: params.prompt_id,
31294
- status: latestEntry !== void 0 ? getHistoryStatus(latestEntry) : "not_found",
31295
- completed: latestEntry !== void 0 ? isHistoryCompleted(latestEntry) : false,
31296
- timed_out: true,
31297
- elapsed_seconds: Number(((Date.now() - startTime) / 1e3).toFixed(3)),
31298
- image_count: timeoutImages.length,
31299
- images: timeoutImages,
31300
- history_entry: latestEntry !== void 0 ? ensureRecord(latestEntry, "history entry") : null
31301
- };
31302
- }
31303
- async function createStoredWorkflowRecord(params) {
31304
- const now = (/* @__PURE__ */ new Date()).toISOString();
31305
- const editableInputs = await deriveEditableWorkflowInputs(params.prompt);
31306
- const workflowId = params.workflow_id ? slugifyWorkflowId(params.workflow_id) : slugifyWorkflowId(params.name);
31307
- return {
31308
- workflow: {
31309
- id: workflowId,
31310
- name: params.name,
31311
- description: params.description,
31312
- tags: params.tags,
31313
- created_at: params.existing?.workflow.created_at ?? now,
31314
- updated_at: now,
31315
- node_count: Object.keys(params.prompt).length,
31316
- editable_input_count: editableInputs.length,
31317
- source: params.source
31318
- },
31319
- prompt: params.prompt,
31320
- editable_inputs: editableInputs
31321
- };
31322
- }
31323
- async function submitWorkflow(args) {
31324
- const result = IS_MOCK ? MOCK_FIXTURES.prompt_response : await fetchComfyJson(
31325
- "/prompt",
31326
- {
31327
- method: "POST",
31328
- body: JSON.stringify({
31329
- prompt: args.prompt,
31330
- client_id: args.client_id
31331
- })
31332
- }
31333
- );
31334
- return {
31335
- prompt_id: result.prompt_id,
31336
- number: result.number,
31337
- node_errors: result.node_errors
31338
- };
31339
- }
31340
- function summarizeWorkflowResponseText(result) {
31341
- const hasErrors = Object.keys(result.node_errors).length > 0;
31342
- return [
31343
- "# ComfyUI Workflow Submission",
31344
- "",
31345
- "## Summary",
31346
- formatMarkdownBulletList([
31347
- `Status: ${hasErrors ? "Submitted with node errors" : "Successfully queued"}`,
31348
- `Prompt ID: ${result.prompt_id}`,
31349
- `Queue Number: ${result.number}`,
31350
- `Node Errors: ${Object.keys(result.node_errors).length}`
31351
- ]),
31352
- "",
31353
- "## Next Step",
31354
- hasErrors ? "Fix the reported node_errors before retrying this workflow." : `Next step: call comfyui_get_history with prompt_id="${result.prompt_id}" to monitor outputs.`,
31355
- "",
31356
- "## JSON",
31357
- formatJsonCodeBlock({
31358
- prompt_id: result.prompt_id,
31359
- number: result.number,
31360
- node_errors: result.node_errors
31361
- })
31362
- ].join("\n");
30787
+ return lines.join("\n");
31363
30788
  }
31364
- function createAgentFacingResult(text, structuredContent) {
31365
- const normalizedText = text.trim();
31366
- if (normalizedText.length === 0) {
31367
- throw new Error(
31368
- "Tool response text must be non-empty. Do not rely on structuredContent alone because many MCP clients expose content.text to the model first."
31369
- );
30789
+ function formatWorkflowNodeErrors(nodeErrors) {
30790
+ const nodeLines = summarizeNodeErrors(nodeErrors);
30791
+ if (nodeLines.length === 0) {
30792
+ return "Workflow submission failed validation.";
31370
30793
  }
31371
- return {
31372
- content: [
31373
- {
31374
- type: "text",
31375
- text: normalizedText
31376
- }
31377
- ],
31378
- structuredContent
31379
- };
30794
+ return ["Workflow submission failed validation.", ...nodeLines].join("\n");
31380
30795
  }
31381
30796
  async function fetchComfyJson(apiPath, options) {
31382
30797
  const url2 = new URL(apiPath, COMFYUI_SERVER_URL).toString();
@@ -31385,666 +30800,551 @@ async function fetchComfyJson(apiPath, options) {
31385
30800
  response = await fetch(url2, {
31386
30801
  ...options,
31387
30802
  headers: {
31388
- "Content-Type": "application/json",
31389
- ...options?.headers
30803
+ Accept: "application/json",
30804
+ ...options?.headers || {}
31390
30805
  }
31391
30806
  });
31392
- } catch {
31393
- throw new ComfyApiError(
31394
- "Failed to connect to ComfyUI. Verify the server is running and COMFYUI_SERVER_URL is correct.",
31395
- 503
30807
+ } catch (error48) {
30808
+ throw new Error(
30809
+ `Failed to connect to ComfyUI server at ${COMFYUI_SERVER_URL}: ${error48 instanceof Error ? error48.message : String(error48)}`
31396
30810
  );
31397
30811
  }
31398
- const contentType = response.headers.get("content-type") || "";
30812
+ if (response.status === 404) {
30813
+ throw new ComfyApiError(`Not found: ${apiPath}`, 404, null);
30814
+ }
30815
+ const text = await response.text();
31399
30816
  let body;
31400
- if (contentType.includes("application/json")) {
31401
- body = await response.json();
31402
- } else {
31403
- body = await response.text();
30817
+ try {
30818
+ body = text ? JSON.parse(text) : null;
30819
+ } catch {
30820
+ throw new ComfyApiError(
30821
+ `Invalid JSON response from ComfyUI: ${text.slice(0, 100)}`,
30822
+ response.status,
30823
+ text
30824
+ );
31404
30825
  }
31405
30826
  if (!response.ok) {
31406
30827
  throw new ComfyApiError(
31407
- getErrorMessageFromDetails(body) || "The ComfyUI service returned an error.",
30828
+ "The ComfyUI service returned an error.",
31408
30829
  response.status,
31409
30830
  body
31410
30831
  );
31411
30832
  }
31412
30833
  return body;
31413
30834
  }
31414
- async function fetchHistoryEntry(promptId) {
31415
- if (IS_MOCK) {
31416
- return MOCK_FIXTURES.history[promptId];
31417
- }
31418
- try {
31419
- return await fetchComfyJson(
31420
- `/history/${encodeURIComponent(promptId)}`
31421
- );
31422
- } catch (error48) {
31423
- if (error48 instanceof ComfyApiError && error48.statusCode === 404) {
31424
- return void 0;
31425
- }
31426
- throw error48;
31427
- }
30835
+ async function fetchImageBase64(filename, type, subfolder) {
30836
+ const params = new URLSearchParams({ filename, type });
30837
+ if (subfolder) params.set("subfolder", subfolder);
30838
+ const url2 = new URL(
30839
+ `/view?${params.toString()}`,
30840
+ COMFYUI_SERVER_URL
30841
+ ).toString();
30842
+ const response = await fetch(url2);
30843
+ if (!response.ok)
30844
+ throw new Error(`Failed to fetch image: ${response.statusText}`);
30845
+ const arrayBuffer = await response.arrayBuffer();
30846
+ const mimeType = response.headers.get("content-type") || "image/png";
30847
+ return {
30848
+ data: Buffer.from(arrayBuffer).toString("base64"),
30849
+ mimeType
30850
+ };
31428
30851
  }
31429
30852
  function handleComfyError(error48) {
31430
30853
  if (error48 instanceof ZodError) {
31431
30854
  return createValidationError(
31432
- error48.issues[0]?.path.join(".") || "arguments",
30855
+ "arguments",
31433
30856
  error48.issues[0]?.message || "Invalid input."
31434
30857
  );
31435
30858
  }
31436
30859
  if (error48 instanceof ComfyApiError) {
31437
- const nodeErrors = getNodeErrors(error48.details);
31438
- if (nodeErrors) {
31439
- const details = JSON.stringify(nodeErrors, null, 2);
31440
- return createValidationError(
31441
- "prompt",
31442
- `${error48.message}
31443
- node_errors:
31444
- ${details}`
31445
- );
31446
- }
31447
- return createApiError(error48.message, error48.statusCode);
30860
+ const detailSummary = summarizeComfyErrorDetails(error48.details);
30861
+ return {
30862
+ content: [
30863
+ {
30864
+ type: "text",
30865
+ text: detailSummary.length > 0 ? `${error48.message}
30866
+ ${detailSummary}` : error48.message
30867
+ }
30868
+ ],
30869
+ structuredContent: error48.details || {},
30870
+ isError: true
30871
+ };
31448
30872
  }
31449
30873
  return createInternalError(error48);
31450
30874
  }
31451
- async function handleGetSystemInfo() {
31452
- try {
31453
- const rawStats = IS_MOCK ? MOCK_FIXTURES.system_stats : await fetchComfyJson("/system_stats");
31454
- const structuredContent = normalizeSystemInfo(rawStats);
31455
- return createAgentFacingResult(
31456
- summarizeSystemInfoText(structuredContent),
31457
- structuredContent
30875
+ async function validateWorkflowPath(targetPath) {
30876
+ const resolvedPath = path.resolve(targetPath);
30877
+ const allowedRoots = [
30878
+ path.join(
30879
+ homedir(),
30880
+ "comfy",
30881
+ "ComfyUI",
30882
+ "user",
30883
+ "default",
30884
+ "workflows"
30885
+ ),
30886
+ path.join(homedir(), ".fre4x-comfyui", "workflows"),
30887
+ path.join(process.cwd(), "workflows")
30888
+ ];
30889
+ const isAllowed = allowedRoots.some(
30890
+ (root) => resolvedPath.startsWith(path.resolve(root))
30891
+ );
30892
+ if (!isAllowed) {
30893
+ throw new Error(
30894
+ `Access denied: ${targetPath} is outside allowed workflow directories.`
31458
30895
  );
31459
- } catch (error48) {
31460
- return handleComfyError(error48);
30896
+ }
30897
+ try {
30898
+ await fs.access(resolvedPath);
30899
+ return resolvedPath;
30900
+ } catch {
30901
+ throw new Error(`Workflow file not found: ${targetPath}`);
30902
+ }
30903
+ }
30904
+ function applyOverrides(prompt, overrides) {
30905
+ for (const [key, value] of Object.entries(overrides)) {
30906
+ const parts = key.split(".");
30907
+ let current = prompt;
30908
+ for (let i = 0; i < parts.length - 1; i++) {
30909
+ const part = parts[i];
30910
+ if (typeof current[part] !== "object" || current[part] === null) {
30911
+ current[part] = {};
30912
+ }
30913
+ current = current[part];
30914
+ }
30915
+ current[parts[parts.length - 1]] = value;
31461
30916
  }
31462
30917
  }
31463
- async function handleListObjectInfo(args) {
30918
+ var InspectNodeSchema = paginationSchema.extend({
30919
+ node_class: z3.string().optional().describe(
30920
+ "Specific node class to inspect. If omitted, lists all available nodes."
30921
+ ),
30922
+ query: z3.string().optional().describe(
30923
+ "Search term to filter the list of available nodes by name or category."
30924
+ )
30925
+ });
30926
+ var DiscoverWorkflowsSchema = paginationSchema.extend({
30927
+ directory_path: z3.string().optional().describe("Custom directory path to scan for workflows.")
30928
+ });
30929
+ var WorkflowRunSchema = z3.object({
30930
+ workflow_file_path: z3.string().optional().describe(
30931
+ "Path or filename of workflow JSON. Scans default dirs if simple name provided."
30932
+ ),
30933
+ workflow_id: z3.string().optional().describe("Workflow identifier returned by discover_workflows."),
30934
+ overrides: z3.record(z3.string(), z3.unknown()).optional().describe(
30935
+ 'Key-value pairs to override using dot-notation. e.g. {"6.inputs.text": "dragon"}'
30936
+ ),
30937
+ await: z3.boolean().default(true).describe("Wait for completion and return native images."),
30938
+ timeout: z3.number().int().min(1).default(120).describe("Wait timeout in seconds.")
30939
+ });
30940
+ var WaitForWorkflowSchema = z3.object({
30941
+ prompt_id: z3.string().describe("The prompt_id of the submitted workflow."),
30942
+ timeout: z3.number().int().min(1).default(120).describe("Wait timeout in seconds.")
30943
+ });
30944
+ async function handleInspectNode(args) {
31464
30945
  try {
31465
- const rawInfo = IS_MOCK ? args.node_class ? MOCK_FIXTURES.object_info[args.node_class] : MOCK_FIXTURES.object_info : await fetchComfyJson(
31466
- args.node_class ? `/object_info/${encodeURIComponent(args.node_class)}` : "/object_info"
30946
+ const rawInfo = IS_MOCK ? MOCK_FIXTURES.object_info : await fetchComfyJson(
30947
+ "/object_info"
31467
30948
  );
31468
30949
  if (args.node_class) {
31469
- if (rawInfo === void 0) {
30950
+ const node = rawInfo[args.node_class];
30951
+ if (!node)
31470
30952
  return createNotFoundError(`Node class ${args.node_class}`);
31471
- }
31472
- const nodeDefinition = unwrapNamedNode(args.node_class, rawInfo);
31473
- return createAgentFacingResult(
31474
- summarizeNodeDefinitionText(args.node_class, nodeDefinition),
31475
- {
31476
- nodes: { [args.node_class]: nodeDefinition }
31477
- }
31478
- );
30953
+ return {
30954
+ content: [
30955
+ { type: "text", text: JSON.stringify(node, null, 2) }
30956
+ ],
30957
+ structuredContent: node
30958
+ };
31479
30959
  }
31480
- const nodes = ensureRecord(rawInfo, "object_info");
31481
- const entries = Object.entries(nodes);
31482
- const paginated = applyPagination(entries, args);
31483
- const structuredContent = {
31484
- nodes: Object.fromEntries(paginated.items),
31485
- pagination: {
31486
- total_count: paginated.total,
31487
- limit: paginated.limit,
31488
- offset: paginated.offset,
31489
- has_more: paginated.hasMore
30960
+ let classes = Object.keys(rawInfo);
30961
+ if (args.query) {
30962
+ const query = args.query.toLowerCase();
30963
+ classes = classes.filter((c) => {
30964
+ const node = rawInfo[c];
30965
+ const searchStr = `${c} ${node?.display_name || ""} ${node?.category || ""}`.toLowerCase();
30966
+ return searchStr.includes(query);
30967
+ });
30968
+ }
30969
+ const paginated = applyPagination(classes, args);
30970
+ const resultNodes = {};
30971
+ for (const c of paginated.items) {
30972
+ resultNodes[c] = rawInfo[c];
30973
+ }
30974
+ const text = `Available nodes (${paginated.total} total):
30975
+ ` + paginated.items.join(", ") + (paginated.hasMore ? `
30976
+
30977
+ ${formatPaginationFooter(
30978
+ paginated.offset,
30979
+ paginated.limit,
30980
+ paginated.total
30981
+ )}` : "");
30982
+ return {
30983
+ content: [{ type: "text", text }],
30984
+ structuredContent: {
30985
+ nodes: resultNodes,
30986
+ pagination: {
30987
+ total: paginated.total,
30988
+ offset: paginated.offset,
30989
+ limit: paginated.limit,
30990
+ has_more: paginated.hasMore
30991
+ }
31490
30992
  }
31491
30993
  };
31492
- return createAgentFacingResult(
31493
- [
31494
- "# ComfyUI Node Definitions",
31495
- "",
31496
- "## Pagination",
31497
- formatMarkdownBulletList([
31498
- `Returned: ${paginated.items.length}`,
31499
- `Total: ${paginated.total}`,
31500
- `Limit: ${paginated.limit}`,
31501
- `Offset: ${paginated.offset}`,
31502
- `Has More: ${paginated.hasMore ? "yes" : "no"}`
31503
- ]),
31504
- "",
31505
- "## Node Classes In This Page",
31506
- formatMarkdownBulletList(
31507
- paginated.items.map(([nodeClass]) => nodeClass)
31508
- ),
31509
- "",
31510
- "## Next Step",
31511
- paginated.hasMore ? `Use offset=${paginated.offset + paginated.limit} to retrieve the next page.` : "This page contains the final set of node classes for the current query.",
31512
- "If you need a usable workflow field list, call comfyui_list_object_info again with node_class set to the exact node name.",
31513
- "",
31514
- "## JSON",
31515
- formatJsonCodeBlock(structuredContent)
31516
- ].join("\n"),
31517
- structuredContent
31518
- );
31519
30994
  } catch (error48) {
31520
30995
  return handleComfyError(error48);
31521
30996
  }
31522
30997
  }
31523
- async function handleExecuteWorkflow(args) {
31524
- try {
31525
- const result = await submitWorkflow(args);
31526
- const structuredContent = {
31527
- prompt_id: result.prompt_id,
31528
- number: result.number,
31529
- node_errors: result.node_errors
31530
- };
31531
- if (args.await && Object.keys(result.node_errors).length === 0) {
31532
- const waitResult = await waitForWorkflowResult({
31533
- prompt_id: result.prompt_id,
31534
- timeout_seconds: args.timeout,
31535
- poll_interval_ms: 1e3,
31536
- require_images: false
31537
- });
31538
- const awaitedContent = {
31539
- ...structuredContent,
31540
- status: waitResult.status,
31541
- completed: waitResult.completed,
31542
- timed_out: waitResult.timed_out,
31543
- elapsed_seconds: waitResult.elapsed_seconds,
31544
- image_count: waitResult.image_count,
31545
- images: waitResult.images,
31546
- history_entry: waitResult.history_entry
31547
- };
31548
- return createAgentFacingResult(
31549
- [
31550
- summarizeWorkflowResponseText(awaitedContent),
31551
- "",
31552
- "## Await Result",
31553
- summarizeWaitForWorkflowText(waitResult)
31554
- ].join("\n"),
31555
- awaitedContent
31556
- );
30998
+ function getWorkflowId(absolutePath) {
30999
+ return crypto.createHash("sha256").update(absolutePath).digest("hex").substring(0, 8);
31000
+ }
31001
+ async function handleDiscoverWorkflows(args) {
31002
+ const pathsToScan = [
31003
+ path.join(
31004
+ homedir(),
31005
+ "comfy",
31006
+ "ComfyUI",
31007
+ "user",
31008
+ "default",
31009
+ "workflows"
31010
+ ),
31011
+ path.join(homedir(), ".fre4x-comfyui", "workflows"),
31012
+ path.join(process.cwd(), "workflows")
31013
+ ];
31014
+ if (args.directory_path) pathsToScan.push(args.directory_path);
31015
+ const foundWorkflows = [];
31016
+ for (const dir of pathsToScan) {
31017
+ try {
31018
+ const entries = await fs.readdir(dir, { withFileTypes: true });
31019
+ for (const entry of entries) {
31020
+ if (entry.isFile() && entry.name.endsWith(".json")) {
31021
+ const fullPath = path.join(dir, entry.name);
31022
+ const defaultId = getWorkflowId(fullPath);
31023
+ const defaultName = entry.name.replace(/\.json$/, "");
31024
+ try {
31025
+ const content = await fs.readFile(fullPath, "utf8");
31026
+ const json2 = JSON.parse(content);
31027
+ const id = defaultId;
31028
+ let name = defaultName;
31029
+ let isWebUI = false;
31030
+ if (typeof json2 === "object" && json2 !== null) {
31031
+ if (Array.isArray(json2.nodes) && Array.isArray(json2.links)) {
31032
+ isWebUI = true;
31033
+ }
31034
+ if (typeof json2.name === "string") name = json2.name;
31035
+ else if (json2.workflow && typeof json2.workflow.name === "string")
31036
+ name = json2.workflow.name;
31037
+ }
31038
+ foundWorkflows.push({
31039
+ id,
31040
+ name,
31041
+ path: fullPath,
31042
+ isWebUI
31043
+ });
31044
+ } catch (e) {
31045
+ foundWorkflows.push({
31046
+ id: defaultId,
31047
+ name: defaultName,
31048
+ path: fullPath,
31049
+ error: String(e)
31050
+ });
31051
+ }
31052
+ }
31053
+ }
31054
+ } catch (_e) {
31557
31055
  }
31558
- return createAgentFacingResult(
31559
- summarizeWorkflowResponseText(result),
31560
- structuredContent
31561
- );
31562
- } catch (error48) {
31563
- return handleComfyError(error48);
31564
31056
  }
31565
- }
31566
- async function handleListWorkflows(args) {
31567
- try {
31568
- const workflows = (await listStoredWorkflowFiles()).map(
31569
- (workflow) => workflow.workflow
31570
- );
31571
- const paginated = applyPagination(workflows, args);
31572
- const structuredContent = {
31573
- workflows: paginated.items,
31574
- pagination: {
31575
- total_count: paginated.total,
31576
- limit: paginated.limit,
31577
- offset: paginated.offset,
31578
- has_more: paginated.hasMore
31057
+ if (foundWorkflows.length === 0) {
31058
+ return {
31059
+ content: [
31060
+ {
31061
+ type: "text",
31062
+ text: `No workflows found in searched directories.`
31063
+ }
31064
+ ],
31065
+ structuredContent: {
31066
+ workflows: [],
31067
+ searched_paths: pathsToScan
31579
31068
  }
31580
31069
  };
31581
- return createAgentFacingResult(
31582
- summarizeWorkflowListText(
31583
- "# ComfyUI Stored Workflows",
31584
- structuredContent.workflows,
31585
- structuredContent.pagination
31586
- ),
31587
- structuredContent
31588
- );
31589
- } catch (error48) {
31590
- return handleComfyError(error48);
31591
31070
  }
31592
- }
31593
- async function handleSearchWorkflows(args) {
31594
- try {
31595
- const normalizedQuery = args.query.trim().toLowerCase();
31596
- const matching = (await listStoredWorkflowFiles()).map((workflow) => workflow.workflow).filter(
31597
- (workflow) => [
31598
- workflow.id,
31599
- workflow.name,
31600
- workflow.description ?? "",
31601
- workflow.tags.join(" ")
31602
- ].join(" ").toLowerCase().includes(normalizedQuery)
31603
- );
31604
- const paginated = applyPagination(matching, args);
31605
- const structuredContent = {
31071
+ const paginated = applyPagination(foundWorkflows, args);
31072
+ const text = `Found ${paginated.total} workflow files:
31073
+ ` + formatListItems(paginated.items, (w) => {
31074
+ const formatTag = w.isWebUI ? "\u{1F504} Web UI (Auto-converted on run)" : "\u2705 API Format";
31075
+ let line = `[ID: ${w.id}] [${formatTag}] Name: "${w.name}"`;
31076
+ if (w.error) line += ` (Error: ${w.error})`;
31077
+ return line;
31078
+ }) + (paginated.hasMore ? `
31079
+
31080
+ ${formatPaginationFooter(
31081
+ paginated.offset,
31082
+ paginated.limit,
31083
+ paginated.total
31084
+ )}` : "");
31085
+ return {
31086
+ content: [{ type: "text", text }],
31087
+ structuredContent: {
31606
31088
  workflows: paginated.items,
31607
31089
  pagination: {
31608
- total_count: paginated.total,
31609
- limit: paginated.limit,
31090
+ total: paginated.total,
31610
31091
  offset: paginated.offset,
31092
+ limit: paginated.limit,
31611
31093
  has_more: paginated.hasMore
31612
31094
  }
31613
- };
31614
- return createAgentFacingResult(
31615
- summarizeWorkflowListText(
31616
- `# ComfyUI Workflow Search
31617
-
31618
- Query: ${args.query}`,
31619
- structuredContent.workflows,
31620
- structuredContent.pagination
31621
- ),
31622
- structuredContent
31623
- );
31624
- } catch (error48) {
31625
- return handleComfyError(error48);
31626
- }
31627
- }
31628
- async function handleGetWorkflow(args) {
31629
- try {
31630
- const workflow = await readStoredWorkflowFile(args.workflow_id);
31631
- if (!workflow) {
31632
- return createNotFoundError(`Workflow ${args.workflow_id}`);
31633
31095
  }
31634
- return createAgentFacingResult(
31635
- summarizeStoredWorkflowText(workflow),
31636
- workflow
31637
- );
31638
- } catch (error48) {
31639
- return handleComfyError(error48);
31640
- }
31096
+ };
31641
31097
  }
31642
- async function handleSaveWorkflow(args) {
31643
- try {
31644
- const workflowId = args.workflow_id ? slugifyWorkflowId(args.workflow_id) : slugifyWorkflowId(args.name);
31645
- const existing = await readStoredWorkflowFile(workflowId);
31646
- if (existing && !args.overwrite) {
31647
- return createValidationError(
31648
- "workflow_id",
31649
- `Workflow ${workflowId} already exists. Pass overwrite=true to replace it.`
31650
- );
31098
+ async function waitForWorkflowResult(prompt_id, timeout_seconds) {
31099
+ const start = Date.now();
31100
+ const end = start + timeout_seconds * 1e3;
31101
+ while (Date.now() < end) {
31102
+ const history = IS_MOCK ? MOCK_FIXTURES.history : await fetchComfyJson(
31103
+ `/history/${prompt_id}`
31104
+ ).catch(() => ({}));
31105
+ if (history?.[prompt_id]) {
31106
+ return history[prompt_id];
31651
31107
  }
31652
- const workflow = await createStoredWorkflowRecord({
31653
- workflow_id: workflowId,
31654
- name: args.name,
31655
- description: args.description,
31656
- tags: args.tags,
31657
- prompt: args.prompt,
31658
- source: IS_MOCK ? "mock" : "local",
31659
- existing
31660
- });
31661
- await writeStoredWorkflowFile(workflow);
31662
- return createAgentFacingResult(
31663
- summarizeSavedWorkflowText(workflow),
31664
- workflow
31665
- );
31666
- } catch (error48) {
31667
- return handleComfyError(error48);
31108
+ await new Promise((r) => setTimeout(r, 1e3));
31668
31109
  }
31110
+ throw new Error(`Timeout waiting for workflow ${prompt_id}`);
31669
31111
  }
31670
- async function handleRunWorkflow(args) {
31112
+ async function handleWaitForWorkflow(args) {
31671
31113
  try {
31672
- const workflow = await readStoredWorkflowFile(args.workflow_id);
31673
- if (!workflow) {
31674
- return createNotFoundError(`Workflow ${args.workflow_id}`);
31675
- }
31676
- const prompt = applyWorkflowOverrides(
31677
- workflow.prompt,
31678
- workflow.editable_inputs,
31679
- args.overrides
31114
+ const historyEntry = await waitForWorkflowResult(
31115
+ args.prompt_id,
31116
+ args.timeout
31680
31117
  );
31681
- const submission = await submitWorkflow({
31682
- prompt,
31683
- client_id: args.client_id
31684
- });
31685
- const structuredContent = {
31686
- prompt_id: submission.prompt_id,
31687
- number: submission.number,
31688
- node_errors: submission.node_errors,
31689
- workflow_id: workflow.workflow.id,
31690
- workflow_name: workflow.workflow.name,
31691
- applied_overrides: args.overrides
31692
- };
31693
- if (args.await && Object.keys(submission.node_errors).length === 0) {
31694
- const waitResult = await waitForWorkflowResult({
31695
- prompt_id: submission.prompt_id,
31696
- timeout_seconds: args.timeout,
31697
- poll_interval_ms: 1e3,
31698
- require_images: args.require_images
31699
- });
31700
- Object.assign(structuredContent, {
31701
- status: waitResult.status,
31702
- completed: waitResult.completed,
31703
- timed_out: waitResult.timed_out,
31704
- elapsed_seconds: waitResult.elapsed_seconds,
31705
- image_count: waitResult.image_count,
31706
- images: waitResult.images,
31707
- history_entry: waitResult.history_entry
31708
- });
31709
- return createAgentFacingResult(
31710
- [
31711
- "# ComfyUI Stored Workflow Run",
31712
- "",
31713
- "## Summary",
31714
- formatMarkdownBulletList([
31715
- `Workflow ID: ${workflow.workflow.id}`,
31716
- `Workflow Name: ${workflow.workflow.name}`,
31717
- `Prompt ID: ${submission.prompt_id}`,
31718
- `Applied Overrides: ${args.overrides.length}`
31719
- ]),
31720
- "",
31721
- summarizeWorkflowResponseText(structuredContent),
31722
- "",
31723
- "## Await Result",
31724
- summarizeWaitForWorkflowText(waitResult),
31725
- "",
31726
- "## JSON",
31727
- formatJsonCodeBlock(structuredContent)
31728
- ].join("\n"),
31729
- structuredContent
31730
- );
31118
+ const outputsRecord = historyEntry.outputs || {};
31119
+ const content = [];
31120
+ let summaryText = `Workflow ${args.prompt_id} completed.
31121
+ `;
31122
+ for (const [, output] of Object.entries(outputsRecord)) {
31123
+ if (output && Array.isArray(output.images)) {
31124
+ for (const image of output.images) {
31125
+ if (image.filename && image.type) {
31126
+ if (!IS_MOCK) {
31127
+ try {
31128
+ const base643 = await fetchImageBase64(
31129
+ image.filename,
31130
+ image.type,
31131
+ image.subfolder
31132
+ );
31133
+ content.push({
31134
+ type: "image",
31135
+ data: base643.data,
31136
+ mimeType: base643.mimeType
31137
+ });
31138
+ summaryText += `Fetched image: ${image.filename}
31139
+ `;
31140
+ } catch (e) {
31141
+ summaryText += `Failed to fetch image ${image.filename}: ${String(e)}
31142
+ `;
31143
+ }
31144
+ } else {
31145
+ summaryText += `Mock image: ${image.filename}
31146
+ `;
31147
+ }
31148
+ }
31149
+ }
31150
+ }
31731
31151
  }
31732
- return createAgentFacingResult(
31733
- [
31734
- "# ComfyUI Stored Workflow Run",
31735
- "",
31736
- "## Summary",
31737
- formatMarkdownBulletList([
31738
- `Workflow ID: ${workflow.workflow.id}`,
31739
- `Workflow Name: ${workflow.workflow.name}`,
31740
- `Prompt ID: ${submission.prompt_id}`,
31741
- `Applied Overrides: ${args.overrides.length}`
31742
- ]),
31743
- "",
31744
- summarizeWorkflowResponseText(structuredContent),
31745
- "",
31746
- "## JSON",
31747
- formatJsonCodeBlock(structuredContent)
31748
- ].join("\n"),
31749
- structuredContent
31750
- );
31152
+ content.unshift({ type: "text", text: summaryText });
31153
+ return {
31154
+ content,
31155
+ structuredContent: historyEntry
31156
+ };
31751
31157
  } catch (error48) {
31752
31158
  return handleComfyError(error48);
31753
31159
  }
31754
31160
  }
31755
- async function handleGetHistory(args) {
31161
+ async function handleWorkflowRun(args) {
31756
31162
  try {
31757
- const rawHistory = IS_MOCK ? args.prompt_id ? MOCK_FIXTURES.history[args.prompt_id] : MOCK_FIXTURES.history : await fetchComfyJson(
31758
- args.prompt_id ? `/history/${encodeURIComponent(args.prompt_id)}` : "/history"
31759
- );
31760
- if (args.prompt_id) {
31761
- if (rawHistory === void 0) {
31762
- return createNotFoundError(`Prompt history ${args.prompt_id}`);
31763
- }
31764
- return createAgentFacingResult(
31765
- summarizeHistoryEntryText(args.prompt_id, rawHistory),
31766
- {
31767
- history: { [args.prompt_id]: rawHistory }
31163
+ let content = "";
31164
+ let targetPath = "";
31165
+ if (args.workflow_file_path) {
31166
+ targetPath = args.workflow_file_path;
31167
+ try {
31168
+ if (!targetPath.includes(path.sep)) {
31169
+ const paths = [
31170
+ path.join(
31171
+ homedir(),
31172
+ "comfy",
31173
+ "ComfyUI",
31174
+ "user",
31175
+ "default",
31176
+ "workflows",
31177
+ targetPath
31178
+ ),
31179
+ path.join(
31180
+ homedir(),
31181
+ ".fre4x-comfyui",
31182
+ "workflows",
31183
+ targetPath
31184
+ ),
31185
+ path.join(process.cwd(), "workflows", targetPath)
31186
+ ];
31187
+ let found = false;
31188
+ for (const p of paths) {
31189
+ try {
31190
+ content = await fs.readFile(p, "utf8");
31191
+ targetPath = p;
31192
+ found = true;
31193
+ break;
31194
+ } catch (_e) {
31195
+ }
31196
+ }
31197
+ if (!found) {
31198
+ throw new Error(
31199
+ `Workflow file not found: ${targetPath}`
31200
+ );
31201
+ }
31202
+ } else {
31203
+ targetPath = await validateWorkflowPath(targetPath);
31204
+ content = await fs.readFile(targetPath, "utf8");
31205
+ }
31206
+ } catch (err) {
31207
+ return handleComfyError(err);
31208
+ }
31209
+ } else if (args.workflow_id) {
31210
+ const pathsToScan = [
31211
+ path.join(
31212
+ homedir(),
31213
+ "comfy",
31214
+ "ComfyUI",
31215
+ "user",
31216
+ "default",
31217
+ "workflows"
31218
+ ),
31219
+ path.join(homedir(), ".fre4x-comfyui", "workflows"),
31220
+ path.join(process.cwd(), "workflows")
31221
+ ];
31222
+ let found = false;
31223
+ for (const dir of pathsToScan) {
31224
+ try {
31225
+ const entries = await fs.readdir(dir, {
31226
+ withFileTypes: true
31227
+ });
31228
+ for (const entry of entries) {
31229
+ if (entry.isFile() && entry.name.endsWith(".json")) {
31230
+ const fullPath = path.join(dir, entry.name);
31231
+ const defaultId = getWorkflowId(fullPath);
31232
+ if (args.workflow_id === defaultId || args.workflow_id === entry.name.replace(/\.json$/, "")) {
31233
+ content = await fs.readFile(fullPath, "utf8");
31234
+ targetPath = fullPath;
31235
+ found = true;
31236
+ break;
31237
+ }
31238
+ }
31239
+ }
31240
+ if (found) break;
31241
+ } catch (_e) {
31768
31242
  }
31243
+ }
31244
+ if (!found) {
31245
+ throw new Error(`Workflow ID not found: ${args.workflow_id}`);
31246
+ }
31247
+ } else {
31248
+ throw new Error(
31249
+ "Must provide either workflow_file_path or workflow_id."
31769
31250
  );
31770
31251
  }
31771
- const history = ensureRecord(rawHistory, "history");
31772
- const entries = Object.entries(history);
31773
- const paginated = applyPagination(entries, args);
31774
- const structuredContent = {
31775
- history: Object.fromEntries(paginated.items),
31776
- pagination: {
31777
- total_count: paginated.total,
31778
- limit: paginated.limit,
31779
- offset: paginated.offset,
31780
- has_more: paginated.hasMore
31252
+ let promptObj = {};
31253
+ try {
31254
+ promptObj = JSON.parse(content);
31255
+ } catch (e) {
31256
+ throw new Error(
31257
+ `SyntaxError: Invalid JSON in ${targetPath}: ${e instanceof Error ? e.message : String(e)}`
31258
+ );
31259
+ }
31260
+ if (isWebUIFormat(promptObj)) {
31261
+ let objectInfo;
31262
+ if (IS_MOCK) {
31263
+ objectInfo = MOCK_FIXTURES.object_info;
31264
+ } else {
31265
+ objectInfo = await fetchComfyJson(
31266
+ "/object_info"
31267
+ );
31781
31268
  }
31269
+ promptObj = convertWebUIToAPI(
31270
+ promptObj,
31271
+ objectInfo
31272
+ );
31273
+ }
31274
+ if (promptObj.prompt && typeof promptObj.prompt === "object") {
31275
+ promptObj = promptObj.prompt;
31276
+ }
31277
+ if (args.overrides) {
31278
+ applyOverrides(promptObj, args.overrides);
31279
+ }
31280
+ const clientId = `mcp-${Date.now()}`;
31281
+ const result = IS_MOCK ? MOCK_FIXTURES.prompt_response : await fetchComfyJson("/prompt", {
31282
+ method: "POST",
31283
+ body: JSON.stringify({
31284
+ prompt: promptObj,
31285
+ client_id: clientId
31286
+ })
31287
+ });
31288
+ if (result.node_errors && Object.keys(result.node_errors).length > 0) {
31289
+ return {
31290
+ content: [
31291
+ {
31292
+ type: "text",
31293
+ text: formatWorkflowNodeErrors(result.node_errors)
31294
+ }
31295
+ ],
31296
+ structuredContent: result,
31297
+ isError: true
31298
+ };
31299
+ }
31300
+ if (args.await) {
31301
+ return await handleWaitForWorkflow({
31302
+ prompt_id: result.prompt_id,
31303
+ timeout: args.timeout
31304
+ });
31305
+ }
31306
+ return {
31307
+ content: [
31308
+ {
31309
+ type: "text",
31310
+ text: `Workflow submitted. prompt_id: ${result.prompt_id}, queue: ${result.number}`
31311
+ }
31312
+ ],
31313
+ structuredContent: result
31782
31314
  };
31783
- return createAgentFacingResult(
31784
- [
31785
- "# ComfyUI History",
31786
- "",
31787
- "## Pagination",
31788
- formatMarkdownBulletList([
31789
- `Returned: ${paginated.items.length}`,
31790
- `Total: ${paginated.total}`,
31791
- `Limit: ${paginated.limit}`,
31792
- `Offset: ${paginated.offset}`,
31793
- `Has More: ${paginated.hasMore ? "yes" : "no"}`
31794
- ]),
31795
- "",
31796
- "## Prompt IDs In This Page",
31797
- formatMarkdownBulletList(
31798
- paginated.items.map(([promptId]) => promptId)
31799
- ),
31800
- "",
31801
- "## Next Step",
31802
- paginated.hasMore ? `Use offset=${paginated.offset + paginated.limit} to retrieve the next page.` : "This page contains the final set of history entries for the current query.",
31803
- "",
31804
- "## JSON",
31805
- formatJsonCodeBlock(structuredContent)
31806
- ].join("\n"),
31807
- structuredContent
31808
- );
31809
- } catch (error48) {
31810
- return handleComfyError(error48);
31811
- }
31812
- }
31813
- async function handleWaitForWorkflow(args) {
31814
- try {
31815
- const structuredContent = await waitForWorkflowResult(args);
31816
- return createAgentFacingResult(
31817
- summarizeWaitForWorkflowText(structuredContent),
31818
- structuredContent
31819
- );
31820
- } catch (error48) {
31821
- return handleComfyError(error48);
31822
- }
31823
- }
31824
- async function handleGetQueue() {
31825
- try {
31826
- const queue = IS_MOCK ? MOCK_FIXTURES.queue : await fetchComfyJson("/queue");
31827
- const queueRecord = ensureRecord(queue, "queue");
31828
- const structuredContent = {
31829
- queue_running: Array.isArray(queueRecord.queue_running) ? queueRecord.queue_running : [],
31830
- queue_pending: Array.isArray(queueRecord.queue_pending) ? queueRecord.queue_pending : []
31831
- };
31832
- return createAgentFacingResult(
31833
- [
31834
- "# ComfyUI Queue",
31835
- "",
31836
- "## Summary",
31837
- formatMarkdownBulletList([
31838
- `Running Jobs: ${structuredContent.queue_running.length}`,
31839
- `Pending Jobs: ${structuredContent.queue_pending.length}`
31840
- ]),
31841
- "",
31842
- "## JSON",
31843
- formatJsonCodeBlock(structuredContent)
31844
- ].join("\n"),
31845
- structuredContent
31846
- );
31847
31315
  } catch (error48) {
31848
31316
  return handleComfyError(error48);
31849
31317
  }
31850
31318
  }
31851
- async function handleGetViewUrl(args) {
31852
- const url2 = buildViewUrl(args.filename, args.type, args.subfolder);
31853
- return createAgentFacingResult(
31854
- [
31855
- "# ComfyUI View URL",
31856
- "",
31857
- "## Summary",
31858
- formatMarkdownBulletList([
31859
- `Filename: ${args.filename}`,
31860
- `Subfolder: ${args.subfolder ?? "(root)"}`,
31861
- `Type: ${args.type}`,
31862
- `URL: ${url2}`
31863
- ]),
31864
- "",
31865
- "## JSON",
31866
- formatJsonCodeBlock({ url: url2 })
31867
- ].join("\n"),
31868
- { url: url2 }
31869
- );
31870
- }
31871
31319
  function createServer() {
31872
31320
  const server = new McpServer({
31873
31321
  name: "@fre4x/comfyui",
31874
31322
  version: PACKAGE_VERSION
31875
31323
  });
31876
- server.registerTool(
31877
- "comfyui_get_system_info",
31878
- {
31879
- title: "Get ComfyUI System Info",
31880
- description: "Get ComfyUI server status, hardware info, and VRAM usage.",
31881
- inputSchema: NoArgsSchema,
31882
- outputSchema: SystemInfoSchema,
31883
- annotations: {
31884
- readOnlyHint: true,
31885
- idempotentHint: true,
31886
- openWorldHint: true
31887
- }
31888
- },
31889
- handleGetSystemInfo
31890
- );
31891
- server.registerTool(
31892
- "comfyui_list_object_info",
31893
- {
31894
- title: "List ComfyUI Node Definitions",
31895
- description: "Get definitions of available node classes, with optional node lookup and pagination.",
31896
- inputSchema: ListObjectInfoInputSchema,
31897
- outputSchema: ObjectInfoSchema,
31898
- annotations: {
31899
- readOnlyHint: true,
31900
- idempotentHint: true,
31901
- openWorldHint: true
31902
- }
31903
- },
31904
- handleListObjectInfo
31905
- );
31906
- server.registerTool(
31907
- "comfyui_execute_workflow",
31908
- {
31909
- title: "Execute ComfyUI Workflow",
31910
- description: "Submit a ComfyUI workflow in API JSON format and return its prompt_id.",
31911
- inputSchema: ExecuteWorkflowInputSchema,
31912
- outputSchema: WorkflowResponseSchema,
31913
- annotations: {
31914
- openWorldHint: true
31915
- }
31916
- },
31917
- handleExecuteWorkflow
31324
+ server.tool(
31325
+ "comfyui_inspect_node",
31326
+ "Inspect a ComfyUI node or list all nodes.",
31327
+ InspectNodeSchema.shape,
31328
+ handleInspectNode
31918
31329
  );
31919
- server.registerTool(
31920
- "comfyui_list_workflows",
31921
- {
31922
- title: "List Stored Workflows",
31923
- description: "List stored ComfyUI workflows with pagination.",
31924
- inputSchema: ListWorkflowsInputSchema,
31925
- outputSchema: WorkflowListSchema,
31926
- annotations: {
31927
- readOnlyHint: true,
31928
- idempotentHint: true,
31929
- openWorldHint: true
31930
- }
31931
- },
31932
- handleListWorkflows
31330
+ server.tool(
31331
+ "comfyui_discover_workflows",
31332
+ "Scan standard directories for .json workflow files.",
31333
+ DiscoverWorkflowsSchema.shape,
31334
+ handleDiscoverWorkflows
31933
31335
  );
31934
- server.registerTool(
31935
- "comfyui_search_workflows",
31936
- {
31937
- title: "Search Stored Workflows",
31938
- description: "Search stored ComfyUI workflows by name, tags, or description.",
31939
- inputSchema: SearchWorkflowsInputSchema,
31940
- outputSchema: WorkflowListSchema,
31941
- annotations: {
31942
- readOnlyHint: true,
31943
- idempotentHint: true,
31944
- openWorldHint: true
31945
- }
31946
- },
31947
- handleSearchWorkflows
31948
- );
31949
- server.registerTool(
31950
- "comfyui_get_workflow",
31951
- {
31952
- title: "Get Stored Workflow",
31953
- description: "Get a stored ComfyUI workflow and its editable inputs.",
31954
- inputSchema: GetWorkflowInputSchema,
31955
- outputSchema: StoredWorkflowSchema,
31956
- annotations: {
31957
- readOnlyHint: true,
31958
- idempotentHint: true,
31959
- openWorldHint: true
31960
- }
31961
- },
31962
- handleGetWorkflow
31963
- );
31964
- server.registerTool(
31965
- "comfyui_save_workflow",
31966
- {
31967
- title: "Save Workflow",
31968
- description: "Save a ComfyUI workflow for later reuse.",
31969
- inputSchema: SaveWorkflowInputSchema,
31970
- outputSchema: StoredWorkflowSchema,
31971
- annotations: {
31972
- openWorldHint: true
31973
- }
31974
- },
31975
- handleSaveWorkflow
31336
+ server.tool(
31337
+ "comfyui_workflow_run",
31338
+ "Run a workflow from a file path or ID, applying overrides, and optionally returning images.",
31339
+ WorkflowRunSchema.shape,
31340
+ handleWorkflowRun
31976
31341
  );
31977
- server.registerTool(
31978
- "comfyui_run_workflow",
31979
- {
31980
- title: "Run Stored Workflow",
31981
- description: "Run a stored workflow with optional overrides.",
31982
- inputSchema: RunWorkflowInputSchema,
31983
- outputSchema: RunStoredWorkflowSchema,
31984
- annotations: {
31985
- openWorldHint: true
31986
- }
31987
- },
31988
- handleRunWorkflow
31989
- );
31990
- server.registerTool(
31991
- "comfyui_get_history",
31992
- {
31993
- title: "Get ComfyUI History",
31994
- description: "Get execution history or a specific workflow result, with pagination for list mode.",
31995
- inputSchema: GetHistoryInputSchema,
31996
- outputSchema: HistorySchema,
31997
- annotations: {
31998
- readOnlyHint: true,
31999
- idempotentHint: true,
32000
- openWorldHint: true
32001
- }
32002
- },
32003
- handleGetHistory
32004
- );
32005
- server.registerTool(
31342
+ server.tool(
32006
31343
  "comfyui_wait_for_workflow",
32007
- {
32008
- title: "Wait for ComfyUI Workflow",
32009
- description: "Wait for a workflow result with timeout-based polling.",
32010
- inputSchema: WaitForWorkflowInputSchema,
32011
- outputSchema: WaitForWorkflowSchema,
32012
- annotations: {
32013
- openWorldHint: true
32014
- }
32015
- },
31344
+ "Wait for a submitted workflow to finish and fetch its images.",
31345
+ WaitForWorkflowSchema.shape,
32016
31346
  handleWaitForWorkflow
32017
31347
  );
32018
- server.registerTool(
32019
- "comfyui_get_queue",
32020
- {
32021
- title: "Get ComfyUI Queue",
32022
- description: "Get the current pending and running jobs in the ComfyUI queue.",
32023
- inputSchema: NoArgsSchema,
32024
- outputSchema: QueueSchema,
32025
- annotations: {
32026
- readOnlyHint: true,
32027
- idempotentHint: true,
32028
- openWorldHint: true
32029
- }
32030
- },
32031
- handleGetQueue
32032
- );
32033
- server.registerTool(
32034
- "comfyui_get_view_url",
32035
- {
32036
- title: "Get ComfyUI View URL",
32037
- description: "Build the direct URL for a generated file in ComfyUI.",
32038
- inputSchema: GetViewUrlInputSchema,
32039
- outputSchema: ViewUrlSchema,
32040
- annotations: {
32041
- readOnlyHint: true,
32042
- idempotentHint: true,
32043
- openWorldHint: true
32044
- }
32045
- },
32046
- handleGetViewUrl
32047
- );
32048
31348
  return server;
32049
31349
  }
32050
31350
  function isMainModule(url2) {