@fre4x/comfyui 1.1.0-beta.4 → 1.1.0

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 +801 -785
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3223,8 +3223,8 @@ var require_utils = __commonJS({
3223
3223
  }
3224
3224
  return ind;
3225
3225
  }
3226
- function removeDotSegments(path2) {
3227
- let input = path2;
3226
+ function removeDotSegments(path4) {
3227
+ let input = path4;
3228
3228
  const output = [];
3229
3229
  let nextSlash = -1;
3230
3230
  let len = 0;
@@ -3423,8 +3423,8 @@ var require_schemes = __commonJS({
3423
3423
  wsComponent.secure = void 0;
3424
3424
  }
3425
3425
  if (wsComponent.resourceName) {
3426
- const [path2, query] = wsComponent.resourceName.split("?");
3427
- wsComponent.path = path2 && path2 !== "/" ? path2 : void 0;
3426
+ const [path4, query] = wsComponent.resourceName.split("?");
3427
+ wsComponent.path = path4 && path4 !== "/" ? path4 : void 0;
3428
3428
  wsComponent.query = query;
3429
3429
  wsComponent.resourceName = void 0;
3430
3430
  }
@@ -6786,12 +6786,12 @@ var require_dist = __commonJS({
6786
6786
  throw new Error(`Unknown format "${name}"`);
6787
6787
  return f;
6788
6788
  };
6789
- function addFormats(ajv, list, fs2, exportName) {
6789
+ function addFormats(ajv, list, fs4, exportName) {
6790
6790
  var _a2;
6791
6791
  var _b;
6792
6792
  (_a2 = (_b = ajv.opts.code).formats) !== null && _a2 !== void 0 ? _a2 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
6793
6793
  for (const f of list)
6794
- ajv.addFormat(f, fs2[f]);
6794
+ ajv.addFormat(f, fs4[f]);
6795
6795
  }
6796
6796
  module.exports = exports = formatsPlugin;
6797
6797
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -6800,11 +6800,9 @@ var require_dist = __commonJS({
6800
6800
  });
6801
6801
 
6802
6802
  // src/index.ts
6803
- import * as fs from "node:fs/promises";
6804
- import * as crypto from "node:crypto";
6803
+ import * as fs3 from "node:fs/promises";
6805
6804
  import { realpathSync } from "node:fs";
6806
- import { homedir } from "node:os";
6807
- import path from "node:path";
6805
+ import path3 from "node:path";
6808
6806
  import { fileURLToPath } from "node:url";
6809
6807
 
6810
6808
  // ../packages/shared/dist/errors.js
@@ -7873,10 +7871,10 @@ function mergeDefs(...defs) {
7873
7871
  function cloneDef(schema) {
7874
7872
  return mergeDefs(schema._zod.def);
7875
7873
  }
7876
- function getElementAtPath(obj, path2) {
7877
- if (!path2)
7874
+ function getElementAtPath(obj, path4) {
7875
+ if (!path4)
7878
7876
  return obj;
7879
- return path2.reduce((acc, key) => acc?.[key], obj);
7877
+ return path4.reduce((acc, key) => acc?.[key], obj);
7880
7878
  }
7881
7879
  function promiseAllObject(promisesObj) {
7882
7880
  const keys = Object.keys(promisesObj);
@@ -8259,11 +8257,11 @@ function aborted(x, startIndex = 0) {
8259
8257
  }
8260
8258
  return false;
8261
8259
  }
8262
- function prefixIssues(path2, issues) {
8260
+ function prefixIssues(path4, issues) {
8263
8261
  return issues.map((iss) => {
8264
8262
  var _a2;
8265
8263
  (_a2 = iss).path ?? (_a2.path = []);
8266
- iss.path.unshift(path2);
8264
+ iss.path.unshift(path4);
8267
8265
  return iss;
8268
8266
  });
8269
8267
  }
@@ -8446,7 +8444,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
8446
8444
  }
8447
8445
  function treeifyError(error48, mapper = (issue2) => issue2.message) {
8448
8446
  const result = { errors: [] };
8449
- const processError = (error49, path2 = []) => {
8447
+ const processError = (error49, path4 = []) => {
8450
8448
  var _a2, _b;
8451
8449
  for (const issue2 of error49.issues) {
8452
8450
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -8456,7 +8454,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
8456
8454
  } else if (issue2.code === "invalid_element") {
8457
8455
  processError({ issues: issue2.issues }, issue2.path);
8458
8456
  } else {
8459
- const fullpath = [...path2, ...issue2.path];
8457
+ const fullpath = [...path4, ...issue2.path];
8460
8458
  if (fullpath.length === 0) {
8461
8459
  result.errors.push(mapper(issue2));
8462
8460
  continue;
@@ -8488,8 +8486,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
8488
8486
  }
8489
8487
  function toDotPath(_path) {
8490
8488
  const segs = [];
8491
- const path2 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
8492
- for (const seg of path2) {
8489
+ const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
8490
+ for (const seg of path4) {
8493
8491
  if (typeof seg === "number")
8494
8492
  segs.push(`[${seg}]`);
8495
8493
  else if (typeof seg === "symbol")
@@ -20466,13 +20464,13 @@ function resolveRef(ref, ctx) {
20466
20464
  if (!ref.startsWith("#")) {
20467
20465
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
20468
20466
  }
20469
- const path2 = ref.slice(1).split("/").filter(Boolean);
20470
- if (path2.length === 0) {
20467
+ const path4 = ref.slice(1).split("/").filter(Boolean);
20468
+ if (path4.length === 0) {
20471
20469
  return ctx.rootSchema;
20472
20470
  }
20473
20471
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
20474
- if (path2[0] === defsKey) {
20475
- const key = path2[1];
20472
+ if (path4[0] === defsKey) {
20473
+ const key = path4[1];
20476
20474
  if (!key || !ctx.defs[key]) {
20477
20475
  throw new Error(`Reference not found: ${ref}`);
20478
20476
  }
@@ -21264,8 +21262,8 @@ function getErrorMap2() {
21264
21262
 
21265
21263
  // ../node_modules/zod/v3/helpers/parseUtil.js
21266
21264
  var makeIssue = (params) => {
21267
- const { data, path: path2, errorMaps, issueData } = params;
21268
- const fullPath = [...path2, ...issueData.path || []];
21265
+ const { data, path: path4, errorMaps, issueData } = params;
21266
+ const fullPath = [...path4, ...issueData.path || []];
21269
21267
  const fullIssue = {
21270
21268
  ...issueData,
21271
21269
  path: fullPath
@@ -21380,11 +21378,11 @@ var errorUtil;
21380
21378
 
21381
21379
  // ../node_modules/zod/v3/types.js
21382
21380
  var ParseInputLazyPath = class {
21383
- constructor(parent, value, path2, key) {
21381
+ constructor(parent, value, path4, key) {
21384
21382
  this._cachedPath = [];
21385
21383
  this.parent = parent;
21386
21384
  this.data = value;
21387
- this._path = path2;
21385
+ this._path = path4;
21388
21386
  this._key = key;
21389
21387
  }
21390
21388
  get path() {
@@ -30427,6 +30425,37 @@ var StdioServerTransport = class {
30427
30425
  }
30428
30426
  };
30429
30427
 
30428
+ // src/errors.ts
30429
+ var ComfyApiError = class extends Error {
30430
+ constructor(message, statusCode, details) {
30431
+ super(message);
30432
+ this.statusCode = statusCode;
30433
+ this.details = details;
30434
+ this.name = "ComfyApiError";
30435
+ }
30436
+ };
30437
+ var WorkflowInputError = class extends Error {
30438
+ constructor(message, field, kind = "validation") {
30439
+ super(message);
30440
+ this.field = field;
30441
+ this.kind = kind;
30442
+ this.name = "WorkflowInputError";
30443
+ }
30444
+ };
30445
+ var WorkflowTimeoutError = class extends Error {
30446
+ constructor(promptId, timeoutSeconds, queueSnapshot) {
30447
+ super(`Timeout waiting for workflow ${promptId}`);
30448
+ this.promptId = promptId;
30449
+ this.timeoutSeconds = timeoutSeconds;
30450
+ this.queueSnapshot = queueSnapshot;
30451
+ this.name = "WorkflowTimeoutError";
30452
+ }
30453
+ };
30454
+
30455
+ // src/api.ts
30456
+ import * as fs from "node:fs/promises";
30457
+ import path from "node:path";
30458
+
30430
30459
  // src/mock.ts
30431
30460
  var IS_MOCK = process.env.MOCK === "true";
30432
30461
  var MOCK_FIXTURES = {
@@ -30577,233 +30606,181 @@ var MOCK_FIXTURES = {
30577
30606
  }
30578
30607
  };
30579
30608
 
30580
- // src/converter.ts
30581
- var WIDGET_TYPES = /* @__PURE__ */ new Set(["INT", "FLOAT", "STRING", "BOOLEAN"]);
30582
- var UI_CONTROL_VALUES = /* @__PURE__ */ new Set([
30583
- "fixed",
30584
- "increment",
30585
- "decrement",
30586
- "randomize"
30587
- ]);
30588
- function isRecord(value) {
30589
- return typeof value === "object" && value !== null;
30609
+ // src/api.ts
30610
+ var COMFYUI_SERVER_URL = process.env.COMFYUI_SERVER_URL || "http://localhost:8188";
30611
+ async function fetchComfyJson(apiPath, options) {
30612
+ const url2 = new URL(apiPath, COMFYUI_SERVER_URL).toString();
30613
+ let response;
30614
+ try {
30615
+ response = await fetch(url2, {
30616
+ ...options,
30617
+ headers: {
30618
+ Accept: "application/json",
30619
+ ...options?.headers || {}
30620
+ }
30621
+ });
30622
+ } catch (error48) {
30623
+ throw new Error(
30624
+ `Failed to connect to ComfyUI server at ${COMFYUI_SERVER_URL}: ${error48 instanceof Error ? error48.message : String(error48)}`
30625
+ );
30626
+ }
30627
+ if (response.status === 404) {
30628
+ throw new ComfyApiError(`Not found: ${apiPath}`, 404, null);
30629
+ }
30630
+ const text = await response.text();
30631
+ let body;
30632
+ try {
30633
+ body = text ? JSON.parse(text) : null;
30634
+ } catch {
30635
+ throw new ComfyApiError(
30636
+ `Invalid JSON response from ComfyUI: ${text.slice(0, 100)}`,
30637
+ response.status,
30638
+ text
30639
+ );
30640
+ }
30641
+ if (!response.ok) {
30642
+ throw new ComfyApiError(
30643
+ "The ComfyUI service returned an error.",
30644
+ response.status,
30645
+ body
30646
+ );
30647
+ }
30648
+ return body;
30590
30649
  }
30591
- function isWebUIFormat(json2) {
30592
- return isRecord(json2) && Array.isArray(json2.nodes) && Array.isArray(json2.links);
30650
+ async function fetchImageAsset(filename, type, subfolder) {
30651
+ const params = new URLSearchParams({ filename, type });
30652
+ if (subfolder) params.set("subfolder", subfolder);
30653
+ const url2 = new URL(
30654
+ `/view?${params.toString()}`,
30655
+ COMFYUI_SERVER_URL
30656
+ ).toString();
30657
+ const response = await fetch(url2);
30658
+ if (!response.ok)
30659
+ throw new Error(`Failed to fetch image: ${response.statusText}`);
30660
+ const arrayBuffer = await response.arrayBuffer();
30661
+ const mimeType = response.headers.get("content-type") || "image/png";
30662
+ return {
30663
+ buffer: Buffer.from(arrayBuffer),
30664
+ mimeType
30665
+ };
30593
30666
  }
30594
- function isLinkData(link) {
30595
- 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";
30667
+ function createMockImageAsset() {
30668
+ return {
30669
+ buffer: Buffer.from("mock-image-data"),
30670
+ mimeType: "image/png"
30671
+ };
30596
30672
  }
30597
- function getOrderedInputEntries(group, orderedNames) {
30598
- if (!group) {
30599
- return [];
30600
- }
30601
- const seen = /* @__PURE__ */ new Set();
30602
- const orderedEntries = [];
30603
- for (const inputName of orderedNames ?? []) {
30604
- const inputDefinition = group[inputName];
30605
- if (!inputDefinition) {
30606
- continue;
30607
- }
30608
- orderedEntries.push([inputName, inputDefinition]);
30609
- seen.add(inputName);
30610
- }
30611
- for (const [inputName, inputDefinition] of Object.entries(group)) {
30612
- if (seen.has(inputName)) {
30613
- continue;
30614
- }
30615
- orderedEntries.push([inputName, inputDefinition]);
30673
+ function getSessionWorkspaceDir() {
30674
+ return process.env.MCP_WORKSPACE_DIR;
30675
+ }
30676
+ function getSafePathSegments(subfolder) {
30677
+ if (!subfolder) return [];
30678
+ return subfolder.split(/[/\\]/).filter((s) => s.length > 0 && s !== ".." && s !== ".");
30679
+ }
30680
+ function sanitizePathSegment(value, fallback) {
30681
+ const normalized = value.trim().replace(/[\\/]+/g, "_").replace(/\.\.+/g, "_").replace(/_+/g, "_").replace(/[^A-Za-z0-9._-]/g, "_");
30682
+ return normalized.length > 0 ? normalized : fallback;
30683
+ }
30684
+ async function saveToWorkspace(promptId, image, asset) {
30685
+ const workspaceDir = getSessionWorkspaceDir();
30686
+ if (!workspaceDir) {
30687
+ return void 0;
30616
30688
  }
30617
- return orderedEntries;
30689
+ const safePromptId = sanitizePathSegment(promptId, "unknown-prompt");
30690
+ const safeFileName = path.basename(image.filename);
30691
+ const safeSubfolderSegments = getSafePathSegments(image.subfolder);
30692
+ const relativePath = path.join(
30693
+ "comfyui",
30694
+ safePromptId,
30695
+ ...safeSubfolderSegments,
30696
+ safeFileName
30697
+ );
30698
+ const workspacePath = path.join(workspaceDir, relativePath);
30699
+ await fs.mkdir(path.dirname(workspacePath), { recursive: true });
30700
+ await fs.writeFile(workspacePath, asset.buffer);
30701
+ return {
30702
+ workspace_path: workspacePath,
30703
+ relative_path: relativePath,
30704
+ filename: safeFileName,
30705
+ mime_type: asset.mimeType,
30706
+ subfolder: image.subfolder
30707
+ };
30618
30708
  }
30619
- function getWidgetTypeName(inputTypeData) {
30620
- return Array.isArray(inputTypeData) ? "COMBO" : String(inputTypeData).toUpperCase();
30709
+ async function getObjectInfo() {
30710
+ return IS_MOCK ? MOCK_FIXTURES.object_info : await fetchComfyJson("/object_info");
30621
30711
  }
30622
- function isWidgetInput(inputTypeData) {
30623
- const typeName = getWidgetTypeName(inputTypeData);
30624
- return typeName === "COMBO" || WIDGET_TYPES.has(typeName);
30712
+ function summarizeErrorMessage(error48) {
30713
+ return error48 instanceof Error ? error48.message : String(error48);
30625
30714
  }
30626
- function isSkippableUiArtifact(value, expectedTypeName) {
30627
- if (expectedTypeName !== "STRING" && typeof value === "string" && UI_CONTROL_VALUES.has(value)) {
30628
- return true;
30715
+ async function tryGetObjectInfo() {
30716
+ try {
30717
+ return {
30718
+ objectInfo: await getObjectInfo()
30719
+ };
30720
+ } catch (error48) {
30721
+ return {
30722
+ warning: `Node schema details unavailable from ComfyUI: ${summarizeErrorMessage(
30723
+ error48
30724
+ )}`
30725
+ };
30629
30726
  }
30630
- return typeof value === "object" && value !== null && !Array.isArray(value);
30631
30727
  }
30632
- function normalizeWidgetValue(value, typeName, extraInfo) {
30633
- const defaultValue = extraInfo?.default;
30634
- const shouldUseDefaultForNumeric = (typeName === "INT" || typeName === "FLOAT") && (value === "" || value === void 0 || value === null);
30635
- if (shouldUseDefaultForNumeric && defaultValue !== void 0) {
30636
- return defaultValue;
30728
+ async function getQueueSnapshot() {
30729
+ try {
30730
+ return IS_MOCK ? MOCK_FIXTURES.queue : await fetchComfyJson("/queue");
30731
+ } catch {
30732
+ return void 0;
30637
30733
  }
30638
- return value;
30639
30734
  }
30640
- function convertWebUIToAPIWithMetadata(webUIJson, objectInfo) {
30641
- const prompt = {};
30642
- const widgetPathMap = {};
30643
- const links = webUIJson.links || [];
30644
- const nodes = webUIJson.nodes || [];
30645
- const serverNodeIds = /* @__PURE__ */ new Set();
30646
- for (const node of nodes) {
30647
- if (!node.id || !node.type || !objectInfo[node.type]) {
30648
- continue;
30649
- }
30650
- serverNodeIds.add(String(node.id));
30735
+ function formatQueueSnapshotText(queueSnapshot) {
30736
+ if (!queueSnapshot) {
30737
+ return "";
30651
30738
  }
30652
- const linkMap = /* @__PURE__ */ new Map();
30653
- for (const link of links) {
30654
- if (isLinkData(link) && serverNodeIds.has(String(link[3]))) {
30655
- linkMap.set(link[0], link);
30656
- }
30739
+ return `Queue snapshot: ${queueSnapshot.queue_running.length} running, ${queueSnapshot.queue_pending.length} pending.`;
30740
+ }
30741
+ function getPromptQueueVisibility(promptId, queueSnapshot) {
30742
+ if (!queueSnapshot) {
30743
+ return null;
30657
30744
  }
30658
- for (const node of nodes) {
30659
- if (!node.id || !node.type) continue;
30660
- const objInfo = objectInfo[node.type];
30661
- if (!objInfo) continue;
30662
- const nodeIdStr = String(node.id);
30663
- const promptNode = {
30664
- class_type: node.type,
30665
- inputs: {}
30666
- };
30667
- const connectedInputNames = /* @__PURE__ */ new Set();
30668
- if (Array.isArray(node.inputs)) {
30669
- for (const input of node.inputs) {
30670
- if (input.name && input.link !== null && input.link !== void 0) {
30671
- const linkData = linkMap.get(input.link);
30672
- if (Array.isArray(linkData) && linkData.length >= 6 && String(linkData[3]) === nodeIdStr) {
30673
- const originNodeId = String(linkData[1]);
30674
- const originSlot = linkData[2];
30675
- promptNode.inputs[input.name] = [
30676
- originNodeId,
30677
- originSlot
30678
- ];
30679
- connectedInputNames.add(input.name);
30680
- }
30681
- }
30745
+ return JSON.stringify(queueSnapshot).includes(promptId);
30746
+ }
30747
+ async function waitForWorkflowResult(prompt_id, timeout_seconds) {
30748
+ const start = Date.now();
30749
+ const end = start + timeout_seconds * 1e3;
30750
+ while (Date.now() < end) {
30751
+ let history = MOCK_FIXTURES.history;
30752
+ if (!IS_MOCK) {
30753
+ try {
30754
+ history = await fetchComfyJson(
30755
+ `/history/${prompt_id}`
30756
+ );
30757
+ } catch {
30758
+ history = {};
30682
30759
  }
30683
30760
  }
30684
- if (objInfo) {
30685
- const widgets = Array.isArray(node.widgets_values) ? node.widgets_values : [];
30686
- let widgetIndex = 0;
30687
- const processInputGroup = (group, orderedNames) => {
30688
- for (const [inputName, inputDataRaw] of getOrderedInputEntries(
30689
- group,
30690
- orderedNames
30691
- )) {
30692
- const [inputTypeData, inputExtraInfo] = inputDataRaw;
30693
- if (!isWidgetInput(inputTypeData)) {
30694
- continue;
30695
- }
30696
- const typeName = getWidgetTypeName(inputTypeData);
30697
- while (widgetIndex < widgets.length && isSkippableUiArtifact(widgets[widgetIndex], typeName)) {
30698
- widgetIndex++;
30699
- }
30700
- const isConnectedWidget = connectedInputNames.has(inputName);
30701
- if (widgetIndex >= widgets.length) {
30702
- if (!isConnectedWidget && inputExtraInfo?.default !== void 0) {
30703
- promptNode.inputs[inputName] = inputExtraInfo.default;
30704
- }
30705
- continue;
30706
- }
30707
- const widgetSourceIndex = widgetIndex;
30708
- const value = normalizeWidgetValue(
30709
- widgets[widgetSourceIndex],
30710
- typeName,
30711
- inputExtraInfo
30712
- );
30713
- widgetIndex++;
30714
- if (!isConnectedWidget) {
30715
- promptNode.inputs[inputName] = value;
30716
- widgetPathMap[`${nodeIdStr}.widgets[${widgetSourceIndex}]`] = `${nodeIdStr}.inputs.${inputName}`;
30717
- }
30718
- }
30719
- };
30720
- processInputGroup(
30721
- objInfo.input?.required,
30722
- objInfo.input_order?.required
30723
- );
30724
- processInputGroup(
30725
- objInfo.input?.optional,
30726
- objInfo.input_order?.optional
30727
- );
30761
+ if (history?.[prompt_id]) {
30762
+ return history[prompt_id];
30728
30763
  }
30729
- prompt[nodeIdStr] = promptNode;
30764
+ await new Promise((r) => setTimeout(r, 1e3));
30730
30765
  }
30731
- return {
30732
- prompt,
30733
- widgetPathMap
30734
- };
30766
+ const queueSnapshot = await getQueueSnapshot();
30767
+ throw new WorkflowTimeoutError(prompt_id, timeout_seconds, queueSnapshot);
30735
30768
  }
30736
30769
 
30737
- // src/index.ts
30738
- var zNamespace = zod_exports;
30739
- var z3 = zNamespace.z ?? zNamespace.default ?? zNamespace;
30740
- var COMFYUI_SERVER_URL = process.env.COMFYUI_SERVER_URL || "http://localhost:8188";
30741
- var PACKAGE_VERSION = getPackageVersion(import.meta.url);
30742
- var ComfyApiError = class extends Error {
30743
- constructor(message, statusCode, details) {
30744
- super(message);
30745
- this.statusCode = statusCode;
30746
- this.details = details;
30747
- this.name = "ComfyApiError";
30748
- }
30749
- };
30750
- var WorkflowInputError = class extends Error {
30751
- constructor(message, field, kind = "validation") {
30752
- super(message);
30753
- this.field = field;
30754
- this.kind = kind;
30755
- this.name = "WorkflowInputError";
30756
- }
30757
- };
30758
- var INPUT_SEGMENT = "inputs";
30759
- var OverrideRecordSchema = z3.record(z3.string(), z3.unknown());
30760
- var OverridesSchema = z3.union([OverrideRecordSchema, z3.string()]).describe(
30761
- 'Dot-notation overrides as an object or JSON string. e.g. {"6.seed": 7}'
30762
- );
30763
- var WorkflowTimeoutError = class extends Error {
30764
- constructor(promptId, timeoutSeconds, queueSnapshot) {
30765
- super(`Timeout waiting for workflow ${promptId}`);
30766
- this.promptId = promptId;
30767
- this.timeoutSeconds = timeoutSeconds;
30768
- this.queueSnapshot = queueSnapshot;
30769
- this.name = "WorkflowTimeoutError";
30770
- }
30771
- };
30772
- function isRecord2(value) {
30770
+ // src/workflow/common.ts
30771
+ function isRecord(value) {
30773
30772
  return typeof value === "object" && value !== null;
30774
30773
  }
30775
- function isComfyPromptNode(value) {
30776
- return isRecord2(value) && typeof value.class_type === "string" && isRecord2(value.inputs);
30777
- }
30778
- function isUiOnlyPromptNode(nodeId, node) {
30779
- return nodeId === "meta" || ["Note", "MarkdownNote", "PrimitiveNode"].includes(node.class_type);
30780
- }
30781
- function extractRunnablePromptNodes(prompt) {
30782
- const runnablePrompt = {};
30783
- for (const [nodeId, rawNode] of Object.entries(prompt)) {
30784
- if (!isComfyPromptNode(rawNode) || isUiOnlyPromptNode(nodeId, rawNode)) {
30785
- continue;
30786
- }
30787
- runnablePrompt[nodeId] = rawNode;
30788
- }
30789
- if (Object.keys(runnablePrompt).length === 0) {
30790
- throw new WorkflowInputError(
30791
- "Workflow prompt does not contain any runnable ComfyUI nodes.",
30792
- "workflow"
30793
- );
30794
- }
30795
- return runnablePrompt;
30796
- }
30797
30774
  function toStructuredContent(value) {
30798
- return isRecord2(value) ? value : {};
30775
+ return isRecord(value) ? value : {};
30799
30776
  }
30800
30777
  function summarizeNodeErrors(nodeErrors) {
30801
- if (!isRecord2(nodeErrors)) {
30778
+ if (!isRecord(nodeErrors)) {
30802
30779
  return [];
30803
30780
  }
30804
30781
  const lines = [];
30805
30782
  for (const [nodeId, rawNodeError] of Object.entries(nodeErrors)) {
30806
- if (!isRecord2(rawNodeError)) {
30783
+ if (!isRecord(rawNodeError)) {
30807
30784
  continue;
30808
30785
  }
30809
30786
  const classType = typeof rawNodeError.class_type === "string" ? ` (${rawNodeError.class_type})` : "";
@@ -30811,7 +30788,7 @@ function summarizeNodeErrors(nodeErrors) {
30811
30788
  const missingInputs = [];
30812
30789
  const otherMessages = [];
30813
30790
  for (const rawError of rawErrors) {
30814
- if (!isRecord2(rawError)) {
30791
+ if (!isRecord(rawError)) {
30815
30792
  continue;
30816
30793
  }
30817
30794
  const errorType = rawError.type;
@@ -30841,11 +30818,11 @@ function summarizeNodeErrors(nodeErrors) {
30841
30818
  return lines;
30842
30819
  }
30843
30820
  function summarizeComfyErrorDetails(details) {
30844
- if (!isRecord2(details)) {
30821
+ if (!isRecord(details)) {
30845
30822
  return "";
30846
30823
  }
30847
30824
  const lines = [];
30848
- const errorDetails = isRecord2(details.error) ? details.error : void 0;
30825
+ const errorDetails = isRecord(details.error) ? details.error : void 0;
30849
30826
  if (typeof errorDetails?.message === "string") {
30850
30827
  lines.push(errorDetails.message);
30851
30828
  } else if (typeof details.message === "string") {
@@ -30864,159 +30841,260 @@ function formatWorkflowNodeErrors(nodeErrors) {
30864
30841
  }
30865
30842
  return ["Workflow submission failed validation.", ...nodeLines].join("\n");
30866
30843
  }
30867
- async function fetchComfyJson(apiPath, options) {
30868
- const url2 = new URL(apiPath, COMFYUI_SERVER_URL).toString();
30869
- let response;
30870
- try {
30871
- response = await fetch(url2, {
30872
- ...options,
30873
- headers: {
30874
- Accept: "application/json",
30875
- ...options?.headers || {}
30876
- }
30877
- });
30878
- } catch (error48) {
30879
- throw new Error(
30880
- `Failed to connect to ComfyUI server at ${COMFYUI_SERVER_URL}: ${error48 instanceof Error ? error48.message : String(error48)}`
30881
- );
30882
- }
30883
- if (response.status === 404) {
30884
- throw new ComfyApiError(`Not found: ${apiPath}`, 404, null);
30844
+ function formatValuePreview(value) {
30845
+ if (typeof value === "string") {
30846
+ return value.length > 100 ? `${value.slice(0, 97)}...` : value;
30885
30847
  }
30886
- const text = await response.text();
30887
- let body;
30888
30848
  try {
30889
- body = text ? JSON.parse(text) : null;
30849
+ const serialized = JSON.stringify(value);
30850
+ if (!serialized) {
30851
+ return String(value);
30852
+ }
30853
+ return serialized.length > 100 ? `${serialized.slice(0, 97)}...` : serialized;
30890
30854
  } catch {
30891
- throw new ComfyApiError(
30892
- `Invalid JSON response from ComfyUI: ${text.slice(0, 100)}`,
30893
- response.status,
30894
- text
30895
- );
30855
+ return String(value);
30896
30856
  }
30897
- if (!response.ok) {
30898
- throw new ComfyApiError(
30899
- "The ComfyUI service returned an error.",
30900
- response.status,
30901
- body
30902
- );
30857
+ }
30858
+ function getInputDefinition(objectInfo, prompt, nodeId, inputName) {
30859
+ if (!objectInfo) {
30860
+ return void 0;
30903
30861
  }
30904
- return body;
30862
+ const node = prompt[nodeId];
30863
+ if (!node) {
30864
+ return void 0;
30865
+ }
30866
+ const nodeDefinition = objectInfo[node.class_type];
30867
+ if (!nodeDefinition) {
30868
+ return void 0;
30869
+ }
30870
+ return nodeDefinition.input?.required?.[inputName] ?? nodeDefinition.input?.optional?.[inputName];
30905
30871
  }
30906
- async function fetchImageAsset(filename, type, subfolder) {
30907
- const params = new URLSearchParams({ filename, type });
30908
- if (subfolder) params.set("subfolder", subfolder);
30909
- const url2 = new URL(
30910
- `/view?${params.toString()}`,
30911
- COMFYUI_SERVER_URL
30912
- ).toString();
30913
- const response = await fetch(url2);
30914
- if (!response.ok)
30915
- throw new Error(`Failed to fetch image: ${response.statusText}`);
30916
- const arrayBuffer = await response.arrayBuffer();
30917
- const mimeType = response.headers.get("content-type") || "image/png";
30918
- return {
30919
- buffer: Buffer.from(arrayBuffer),
30920
- mimeType
30921
- };
30872
+ function getExpectedTypeName(inputDefinition) {
30873
+ if (!inputDefinition) {
30874
+ return void 0;
30875
+ }
30876
+ const [typeData] = inputDefinition;
30877
+ return Array.isArray(typeData) ? "COMBO" : String(typeData).toUpperCase();
30922
30878
  }
30923
- function createMockImageAsset() {
30879
+ function getInputOptionsSummary(inputDefinition, previewLimit = 8) {
30880
+ if (!inputDefinition) {
30881
+ return void 0;
30882
+ }
30883
+ const [typeData] = inputDefinition;
30884
+ if (!Array.isArray(typeData)) {
30885
+ return void 0;
30886
+ }
30924
30887
  return {
30925
- buffer: Buffer.from("mock-image-data"),
30926
- mimeType: "image/png"
30888
+ options_count: typeData.length,
30889
+ options_preview: typeData.slice(0, previewLimit),
30890
+ options_truncated: typeData.length > previewLimit
30927
30891
  };
30928
30892
  }
30929
- function getSessionWorkspaceDir() {
30930
- return process.env.MCP_WORKSPACE_DIR;
30893
+ function isPlainObject3(value) {
30894
+ return isRecord(value) && !Array.isArray(value);
30931
30895
  }
30932
- function getSafePathSegments(subfolder) {
30933
- if (!subfolder) return [];
30934
- return subfolder.split(/[/\\]/).filter((s) => s.length > 0 && s !== ".." && s !== ".");
30896
+ function getValueTypeName(value) {
30897
+ if (Array.isArray(value)) {
30898
+ return "array";
30899
+ }
30900
+ if (value === null) {
30901
+ return "null";
30902
+ }
30903
+ return typeof value;
30935
30904
  }
30936
- async function saveToWorkspace(promptId, image, asset) {
30937
- const workspaceDir = getSessionWorkspaceDir();
30938
- if (!workspaceDir) {
30939
- return void 0;
30905
+
30906
+ // src/workflow/load.ts
30907
+ import * as fs2 from "node:fs/promises";
30908
+ import * as crypto from "node:crypto";
30909
+ import { homedir } from "node:os";
30910
+ import path2 from "node:path";
30911
+
30912
+ // src/converter.ts
30913
+ var WIDGET_TYPES = /* @__PURE__ */ new Set(["INT", "FLOAT", "STRING", "BOOLEAN"]);
30914
+ var UI_CONTROL_VALUES = /* @__PURE__ */ new Set([
30915
+ "fixed",
30916
+ "increment",
30917
+ "decrement",
30918
+ "randomize"
30919
+ ]);
30920
+ function isRecord2(value) {
30921
+ return typeof value === "object" && value !== null;
30922
+ }
30923
+ function isWebUIFormat(json2) {
30924
+ return isRecord2(json2) && Array.isArray(json2.nodes) && Array.isArray(json2.links);
30925
+ }
30926
+ function isLinkData(link) {
30927
+ 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";
30928
+ }
30929
+ function getOrderedInputEntries(group, orderedNames) {
30930
+ if (!group) {
30931
+ return [];
30940
30932
  }
30941
- const safeFileName = path.basename(image.filename);
30942
- const safeSubfolderSegments = getSafePathSegments(image.subfolder);
30943
- const relativePath = path.join(
30944
- "comfyui",
30945
- promptId,
30946
- ...safeSubfolderSegments,
30947
- safeFileName
30948
- );
30949
- const workspacePath = path.join(workspaceDir, relativePath);
30950
- await fs.mkdir(path.dirname(workspacePath), { recursive: true });
30951
- await fs.writeFile(workspacePath, asset.buffer);
30952
- return {
30953
- workspace_path: workspacePath,
30954
- relative_path: relativePath,
30955
- filename: safeFileName,
30956
- mime_type: asset.mimeType,
30957
- subfolder: image.subfolder
30958
- };
30933
+ const seen = /* @__PURE__ */ new Set();
30934
+ const orderedEntries = [];
30935
+ for (const inputName of orderedNames ?? []) {
30936
+ const inputDefinition = group[inputName];
30937
+ if (!inputDefinition) {
30938
+ continue;
30939
+ }
30940
+ orderedEntries.push([inputName, inputDefinition]);
30941
+ seen.add(inputName);
30942
+ }
30943
+ for (const [inputName, inputDefinition] of Object.entries(group)) {
30944
+ if (seen.has(inputName)) {
30945
+ continue;
30946
+ }
30947
+ orderedEntries.push([inputName, inputDefinition]);
30948
+ }
30949
+ return orderedEntries;
30959
30950
  }
30960
- function handleComfyError(error48) {
30961
- if (error48 instanceof ZodError) {
30962
- return createValidationError(
30963
- "arguments",
30964
- error48.issues[0]?.message || "Invalid input."
30965
- );
30951
+ function getWidgetTypeName(inputTypeData) {
30952
+ return Array.isArray(inputTypeData) ? "COMBO" : String(inputTypeData).toUpperCase();
30953
+ }
30954
+ function isWidgetInput(inputTypeData) {
30955
+ const typeName = getWidgetTypeName(inputTypeData);
30956
+ return typeName === "COMBO" || WIDGET_TYPES.has(typeName);
30957
+ }
30958
+ function isSkippableUiArtifact(value, expectedTypeName) {
30959
+ if (expectedTypeName !== "STRING" && typeof value === "string" && UI_CONTROL_VALUES.has(value)) {
30960
+ return true;
30966
30961
  }
30967
- if (error48 instanceof WorkflowInputError) {
30968
- return error48.kind === "not_found" ? createNotFoundError(error48.message) : createValidationError(error48.field, error48.message);
30962
+ return typeof value === "object" && value !== null && !Array.isArray(value);
30963
+ }
30964
+ function normalizeWidgetValue(value, typeName, extraInfo) {
30965
+ const defaultValue = extraInfo?.default;
30966
+ const shouldUseDefaultForNumeric = (typeName === "INT" || typeName === "FLOAT") && (value === "" || value === void 0 || value === null);
30967
+ if (shouldUseDefaultForNumeric && defaultValue !== void 0) {
30968
+ return defaultValue;
30969
+ }
30970
+ return value;
30971
+ }
30972
+ function convertWebUIToAPIWithMetadata(webUIJson, objectInfo) {
30973
+ const prompt = {};
30974
+ const widgetPathMap = {};
30975
+ const links = webUIJson.links || [];
30976
+ const nodes = webUIJson.nodes || [];
30977
+ const serverNodeIds = /* @__PURE__ */ new Set();
30978
+ for (const node of nodes) {
30979
+ if (!node.id || !node.type || !objectInfo[node.type]) {
30980
+ continue;
30981
+ }
30982
+ serverNodeIds.add(String(node.id));
30983
+ }
30984
+ const linkMap = /* @__PURE__ */ new Map();
30985
+ for (const link of links) {
30986
+ if (isLinkData(link) && serverNodeIds.has(String(link[3]))) {
30987
+ linkMap.set(link[0], link);
30988
+ }
30989
+ }
30990
+ for (const node of nodes) {
30991
+ if (!node.id || !node.type) continue;
30992
+ const objInfo = objectInfo[node.type];
30993
+ if (!objInfo) continue;
30994
+ const nodeIdStr = String(node.id);
30995
+ const promptNode = {
30996
+ class_type: node.type,
30997
+ inputs: {}
30998
+ };
30999
+ const connectedInputNames = /* @__PURE__ */ new Set();
31000
+ if (Array.isArray(node.inputs)) {
31001
+ for (const input of node.inputs) {
31002
+ if (input.name && input.link !== null && input.link !== void 0) {
31003
+ const linkData = linkMap.get(input.link);
31004
+ if (Array.isArray(linkData) && linkData.length >= 6 && String(linkData[3]) === nodeIdStr) {
31005
+ const originNodeId = String(linkData[1]);
31006
+ const originSlot = linkData[2];
31007
+ promptNode.inputs[input.name] = [
31008
+ originNodeId,
31009
+ originSlot
31010
+ ];
31011
+ connectedInputNames.add(input.name);
31012
+ }
31013
+ }
31014
+ }
31015
+ }
31016
+ if (objInfo) {
31017
+ const widgets = Array.isArray(node.widgets_values) ? node.widgets_values : [];
31018
+ let widgetIndex = 0;
31019
+ const processInputGroup = (group, orderedNames) => {
31020
+ for (const [inputName, inputDataRaw] of getOrderedInputEntries(
31021
+ group,
31022
+ orderedNames
31023
+ )) {
31024
+ const [inputTypeData, inputExtraInfo] = inputDataRaw;
31025
+ if (!isWidgetInput(inputTypeData)) {
31026
+ continue;
31027
+ }
31028
+ const typeName = getWidgetTypeName(inputTypeData);
31029
+ while (widgetIndex < widgets.length && isSkippableUiArtifact(widgets[widgetIndex], typeName)) {
31030
+ widgetIndex++;
31031
+ }
31032
+ const isConnectedWidget = connectedInputNames.has(inputName);
31033
+ if (widgetIndex >= widgets.length) {
31034
+ if (!isConnectedWidget && inputExtraInfo?.default !== void 0) {
31035
+ promptNode.inputs[inputName] = inputExtraInfo.default;
31036
+ }
31037
+ continue;
31038
+ }
31039
+ const widgetSourceIndex = widgetIndex;
31040
+ const value = normalizeWidgetValue(
31041
+ widgets[widgetSourceIndex],
31042
+ typeName,
31043
+ inputExtraInfo
31044
+ );
31045
+ widgetIndex++;
31046
+ if (!isConnectedWidget) {
31047
+ promptNode.inputs[inputName] = value;
31048
+ widgetPathMap[`${nodeIdStr}.widgets[${widgetSourceIndex}]`] = `${nodeIdStr}.inputs.${inputName}`;
31049
+ }
31050
+ }
31051
+ };
31052
+ processInputGroup(
31053
+ objInfo.input?.required,
31054
+ objInfo.input_order?.required
31055
+ );
31056
+ processInputGroup(
31057
+ objInfo.input?.optional,
31058
+ objInfo.input_order?.optional
31059
+ );
31060
+ }
31061
+ prompt[nodeIdStr] = promptNode;
30969
31062
  }
30970
- if (error48 instanceof ComfyApiError) {
30971
- const detailSummary = summarizeComfyErrorDetails(error48.details);
30972
- return {
30973
- content: [
30974
- {
30975
- type: "text",
30976
- text: detailSummary.length > 0 ? `${error48.message}
30977
- ${detailSummary}` : error48.message
30978
- }
30979
- ],
30980
- structuredContent: toStructuredContent(error48.details),
30981
- isError: true
30982
- };
31063
+ return {
31064
+ prompt,
31065
+ widgetPathMap
31066
+ };
31067
+ }
31068
+
31069
+ // src/workflow/load.ts
31070
+ function isComfyPromptNode(value) {
31071
+ return isRecord(value) && typeof value.class_type === "string" && isRecord(value.inputs);
31072
+ }
31073
+ function isUiOnlyPromptNode(nodeId, node) {
31074
+ return nodeId === "meta" || ["Note", "MarkdownNote", "PrimitiveNode"].includes(node.class_type);
31075
+ }
31076
+ function extractRunnablePromptNodes(prompt) {
31077
+ const runnablePrompt = {};
31078
+ for (const [nodeId, rawNode] of Object.entries(prompt)) {
31079
+ if (!isComfyPromptNode(rawNode) || isUiOnlyPromptNode(nodeId, rawNode)) {
31080
+ continue;
31081
+ }
31082
+ runnablePrompt[nodeId] = rawNode;
30983
31083
  }
30984
- if (error48 instanceof WorkflowTimeoutError) {
30985
- const queueSummary = formatQueueSnapshotText(error48.queueSnapshot);
30986
- const promptVisibility = getPromptQueueVisibility(
30987
- error48.promptId,
30988
- error48.queueSnapshot
31084
+ if (Object.keys(runnablePrompt).length === 0) {
31085
+ throw new WorkflowInputError(
31086
+ "Workflow prompt does not contain any runnable ComfyUI nodes.",
31087
+ "workflow"
30989
31088
  );
30990
- const promptVisibilityText = promptVisibility === true ? "The prompt is still visible in the ComfyUI queue snapshot." : promptVisibility === false ? "The prompt is not visible in the ComfyUI queue snapshot; inspect ComfyUI history or server logs if it never completes." : "ComfyUI queue state was unavailable at timeout.";
30991
- return {
30992
- content: [
30993
- {
30994
- type: "text",
30995
- text: [
30996
- `${error48.message}.`,
30997
- queueSummary,
30998
- promptVisibilityText,
30999
- `You can re-attach by calling 'comfyui_wait_for_workflow' again with prompt_id '${error48.promptId}'.`
31000
- ].filter((line) => line && line.length > 0).join("\n")
31001
- }
31002
- ],
31003
- structuredContent: {
31004
- prompt_id: error48.promptId,
31005
- timeout_seconds: error48.timeoutSeconds,
31006
- queue: error48.queueSnapshot ?? null,
31007
- prompt_visible_in_queue: promptVisibility
31008
- },
31009
- isError: true
31010
- };
31011
31089
  }
31012
- return createInternalError(error48);
31090
+ return runnablePrompt;
31013
31091
  }
31014
31092
  function getWorkflowDirectories(extraPath) {
31015
31093
  const directories = [
31016
31094
  process.cwd(),
31017
- path.join(process.cwd(), "workflows"),
31018
- path.join(homedir(), ".fre4x-comfyui", "workflows"),
31019
- path.join(
31095
+ path2.join(process.cwd(), "workflows"),
31096
+ path2.join(homedir(), ".fre4x-comfyui", "workflows"),
31097
+ path2.join(
31020
31098
  homedir(),
31021
31099
  "comfy",
31022
31100
  "ComfyUI",
@@ -31029,7 +31107,7 @@ function getWorkflowDirectories(extraPath) {
31029
31107
  directories.push(extraPath);
31030
31108
  }
31031
31109
  return [
31032
- ...new Set(directories.map((directory) => path.resolve(directory)))
31110
+ ...new Set(directories.map((directory) => path2.resolve(directory)))
31033
31111
  ];
31034
31112
  }
31035
31113
  function getWorkflowNameCandidates(name) {
@@ -31050,48 +31128,32 @@ function normalizeWorkflowFileName(fileName) {
31050
31128
  "output_file_name"
31051
31129
  );
31052
31130
  }
31053
- return trimmed.endsWith(".json") ? trimmed : `${trimmed}.json`;
31054
- }
31055
- function isConnectionValue(value) {
31056
- return Array.isArray(value) && value.length >= 2 && (typeof value[0] === "string" || typeof value[0] === "number") && typeof value[1] === "number";
31057
- }
31058
- async function getObjectInfo() {
31059
- return IS_MOCK ? MOCK_FIXTURES.object_info : await fetchComfyJson("/object_info");
31060
- }
31061
- function summarizeErrorMessage(error48) {
31062
- return error48 instanceof Error ? error48.message : String(error48);
31063
- }
31064
- async function tryGetObjectInfo() {
31065
- try {
31066
- return {
31067
- objectInfo: await getObjectInfo()
31068
- };
31069
- } catch (error48) {
31070
- return {
31071
- warning: `Node schema details unavailable from ComfyUI: ${summarizeErrorMessage(
31072
- error48
31073
- )}`
31074
- };
31131
+ if (path2.isAbsolute(trimmed)) {
31132
+ throw new WorkflowInputError(
31133
+ "Absolute paths are not allowed.",
31134
+ "output_file_name"
31135
+ );
31075
31136
  }
31137
+ return trimmed.endsWith(".json") ? trimmed : `${trimmed}.json`;
31076
31138
  }
31077
31139
  async function resolveWorkflowReference(args) {
31078
31140
  if (args.workflow_file_path) {
31079
31141
  const targetPath = args.workflow_file_path;
31080
31142
  try {
31081
- const resolvedPath = path.resolve(targetPath);
31082
- const content = await fs.readFile(resolvedPath, "utf8");
31143
+ const resolvedPath = path2.resolve(targetPath);
31144
+ const content = await fs2.readFile(resolvedPath, "utf8");
31083
31145
  return {
31084
31146
  targetPath: resolvedPath,
31085
31147
  content
31086
31148
  };
31087
31149
  } catch {
31088
- if (path.basename(targetPath) === targetPath) {
31150
+ if (path2.basename(targetPath) === targetPath) {
31089
31151
  const candidates = getWorkflowNameCandidates(targetPath);
31090
31152
  for (const dir of getWorkflowDirectories()) {
31091
31153
  for (const candidate of candidates) {
31092
- const candidatePath = path.join(dir, candidate);
31154
+ const candidatePath = path2.join(dir, candidate);
31093
31155
  try {
31094
- const content = await fs.readFile(
31156
+ const content = await fs2.readFile(
31095
31157
  candidatePath,
31096
31158
  "utf8"
31097
31159
  );
@@ -31122,12 +31184,12 @@ async function resolveWorkflowReference(args) {
31122
31184
  const objectInfoAvailability = await tryGetObjectInfo();
31123
31185
  for (const dir of getWorkflowDirectories()) {
31124
31186
  try {
31125
- const entries = await fs.readdir(dir, { withFileTypes: true });
31187
+ const entries = await fs2.readdir(dir, { withFileTypes: true });
31126
31188
  for (const entry of entries) {
31127
31189
  if (!entry.isFile() || !entry.name.endsWith(".json")) {
31128
31190
  continue;
31129
31191
  }
31130
- const fullPath = path.join(dir, entry.name);
31192
+ const fullPath = path2.join(dir, entry.name);
31131
31193
  const inspected = await inspectWorkflowFile(
31132
31194
  fullPath,
31133
31195
  objectInfoAvailability.objectInfo
@@ -31157,7 +31219,7 @@ async function resolveWorkflowReference(args) {
31157
31219
  );
31158
31220
  }
31159
31221
  async function normalizeWorkflowPrompt(workflow, objectInfo) {
31160
- if (!isRecord2(workflow)) {
31222
+ if (!isRecord(workflow)) {
31161
31223
  throw new WorkflowInputError(
31162
31224
  "Workflow must be a JSON object.",
31163
31225
  "workflow"
@@ -31181,8 +31243,8 @@ async function normalizeWorkflowPrompt(workflow, objectInfo) {
31181
31243
  normalizedWorkflow = converted.prompt;
31182
31244
  widgetPathMap = converted.widgetPathMap;
31183
31245
  }
31184
- const promptCandidate = isRecord2(normalizedWorkflow.prompt) && normalizedWorkflow.prompt ? normalizedWorkflow.prompt : normalizedWorkflow;
31185
- if (!isRecord2(promptCandidate)) {
31246
+ const promptCandidate = isRecord(normalizedWorkflow.prompt) && normalizedWorkflow.prompt ? normalizedWorkflow.prompt : normalizedWorkflow;
31247
+ if (!isRecord(promptCandidate)) {
31186
31248
  throw new WorkflowInputError(
31187
31249
  "Workflow prompt must be a JSON object.",
31188
31250
  "workflow"
@@ -31214,8 +31276,8 @@ async function loadWorkflow(args, objectInfo) {
31214
31276
  };
31215
31277
  }
31216
31278
  async function inspectWorkflowFile(fullPath, objectInfo) {
31217
- const content = await fs.readFile(fullPath, "utf8");
31218
- const defaultName = path.basename(fullPath, ".json");
31279
+ const content = await fs2.readFile(fullPath, "utf8");
31280
+ const defaultName = path2.basename(fullPath, ".json");
31219
31281
  const defaultId = getWorkflowId(fullPath);
31220
31282
  let parsed;
31221
31283
  try {
@@ -31224,10 +31286,10 @@ async function inspectWorkflowFile(fullPath, objectInfo) {
31224
31286
  return void 0;
31225
31287
  }
31226
31288
  let name = defaultName;
31227
- if (isRecord2(parsed)) {
31289
+ if (isRecord(parsed)) {
31228
31290
  if (typeof parsed.name === "string" && parsed.name.trim().length > 0) {
31229
31291
  name = parsed.name;
31230
- } else if (isRecord2(parsed.workflow) && typeof parsed.workflow.name === "string" && parsed.workflow.name.trim().length > 0) {
31292
+ } else if (isRecord(parsed.workflow) && typeof parsed.workflow.name === "string" && parsed.workflow.name.trim().length > 0) {
31231
31293
  name = parsed.workflow.name;
31232
31294
  }
31233
31295
  }
@@ -31282,6 +31344,23 @@ async function inspectWorkflowFile(fullPath, objectInfo) {
31282
31344
  return void 0;
31283
31345
  }
31284
31346
  }
31347
+ function deriveSavedWorkflowFileName(sourcePath, requestedName) {
31348
+ if (requestedName) {
31349
+ return normalizeWorkflowFileName(requestedName);
31350
+ }
31351
+ if (sourcePath) {
31352
+ return normalizeWorkflowFileName(path2.basename(sourcePath));
31353
+ }
31354
+ return `workflow-${Date.now()}.json`;
31355
+ }
31356
+ function getWorkflowId(absolutePath) {
31357
+ return crypto.createHash("sha256").update(absolutePath).digest("hex").substring(0, 8);
31358
+ }
31359
+
31360
+ // src/workflow/editable.ts
31361
+ function isConnectionValue(value) {
31362
+ return Array.isArray(value) && value.length >= 2 && (typeof value[0] === "string" || typeof value[0] === "number") && typeof value[1] === "number";
31363
+ }
31285
31364
  function inferEditableRole(nodeId, classType, inputName, consumerMap) {
31286
31365
  if (classType === "CLIPTextEncode" && inputName === "text") {
31287
31366
  const consumers = consumerMap.get(nodeId) ?? [];
@@ -31302,167 +31381,46 @@ function inferEditableRole(nodeId, classType, inputName, consumerMap) {
31302
31381
  for (const consumer of textConsumers) {
31303
31382
  const clipConsumers = consumerMap.get(consumer.nodeId) ?? [];
31304
31383
  if (clipConsumers.some(
31305
- (clipConsumer) => clipConsumer.inputName === "positive"
31306
- )) {
31307
- return "positive_prompt";
31308
- }
31309
- if (clipConsumers.some(
31310
- (clipConsumer) => clipConsumer.inputName === "negative"
31311
- )) {
31312
- return "negative_prompt";
31313
- }
31314
- }
31315
- return "prompt_text";
31316
- }
31317
- }
31318
- switch (inputName) {
31319
- case "seed":
31320
- return "seed";
31321
- case "steps":
31322
- return "steps";
31323
- case "cfg":
31324
- return "cfg";
31325
- case "sampler_name":
31326
- return "sampler";
31327
- case "scheduler":
31328
- return "scheduler";
31329
- case "denoise":
31330
- return "denoise";
31331
- case "width":
31332
- return "width";
31333
- case "height":
31334
- return "height";
31335
- case "batch_size":
31336
- return "batch_size";
31337
- case "ckpt_name":
31338
- return "checkpoint";
31339
- case "filename_prefix":
31340
- return "filename_prefix";
31341
- case "text":
31342
- return "text";
31343
- default:
31344
- return void 0;
31345
- }
31346
- }
31347
- function formatValuePreview(value) {
31348
- if (typeof value === "string") {
31349
- return value.length > 100 ? `${value.slice(0, 97)}...` : value;
31350
- }
31351
- try {
31352
- const serialized = JSON.stringify(value);
31353
- if (!serialized) {
31354
- return String(value);
31355
- }
31356
- return serialized.length > 100 ? `${serialized.slice(0, 97)}...` : serialized;
31357
- } catch {
31358
- return String(value);
31359
- }
31360
- }
31361
- function getInputDefinition(objectInfo, prompt, nodeId, inputName) {
31362
- if (!objectInfo) {
31363
- return void 0;
31364
- }
31365
- const node = prompt[nodeId];
31366
- if (!node) {
31367
- return void 0;
31368
- }
31369
- const nodeDefinition = objectInfo[node.class_type];
31370
- if (!nodeDefinition) {
31371
- return void 0;
31372
- }
31373
- return nodeDefinition.input?.required?.[inputName] ?? nodeDefinition.input?.optional?.[inputName];
31374
- }
31375
- function getExpectedTypeName(inputDefinition) {
31376
- if (!inputDefinition) {
31377
- return void 0;
31378
- }
31379
- const [typeData] = inputDefinition;
31380
- return Array.isArray(typeData) ? "COMBO" : String(typeData).toUpperCase();
31381
- }
31382
- function getInputOptionsSummary(inputDefinition, previewLimit = 8) {
31383
- if (!inputDefinition) {
31384
- return void 0;
31385
- }
31386
- const [typeData] = inputDefinition;
31387
- if (!Array.isArray(typeData)) {
31388
- return void 0;
31389
- }
31390
- return {
31391
- options_count: typeData.length,
31392
- options_preview: typeData.slice(0, previewLimit),
31393
- options_truncated: typeData.length > previewLimit
31394
- };
31395
- }
31396
- function isPlainObject3(value) {
31397
- return isRecord2(value) && !Array.isArray(value);
31398
- }
31399
- function getValueTypeName(value) {
31400
- if (Array.isArray(value)) {
31401
- return "array";
31402
- }
31403
- if (value === null) {
31404
- return "null";
31405
- }
31406
- return typeof value;
31407
- }
31408
- function validateOverrideValueType(path2, value, inputDefinition) {
31409
- if (!inputDefinition) {
31410
- return;
31411
- }
31412
- const [typeData] = inputDefinition;
31413
- if (Array.isArray(typeData)) {
31414
- if (!typeData.some((option) => option === value)) {
31415
- throw new WorkflowInputError(
31416
- `Invalid override for '${path2}': expected one of ${typeData.map((option) => JSON.stringify(option)).join(", ")}, received ${JSON.stringify(value)}.`,
31417
- "overrides"
31418
- );
31419
- }
31420
- return;
31421
- }
31422
- const typeName = String(typeData).toUpperCase();
31423
- switch (typeName) {
31424
- case "INT":
31425
- if (typeof value !== "number" || !Number.isInteger(value)) {
31426
- throw new WorkflowInputError(
31427
- `Invalid override for '${path2}': expected INT, received ${getValueTypeName(
31428
- value
31429
- )}.`,
31430
- "overrides"
31431
- );
31432
- }
31433
- return;
31434
- case "FLOAT":
31435
- if (typeof value !== "number") {
31436
- throw new WorkflowInputError(
31437
- `Invalid override for '${path2}': expected FLOAT, received ${getValueTypeName(
31438
- value
31439
- )}.`,
31440
- "overrides"
31441
- );
31442
- }
31443
- return;
31444
- case "STRING":
31445
- if (typeof value !== "string") {
31446
- throw new WorkflowInputError(
31447
- `Invalid override for '${path2}': expected STRING, received ${getValueTypeName(
31448
- value
31449
- )}.`,
31450
- "overrides"
31451
- );
31452
- }
31453
- return;
31454
- case "BOOLEAN":
31455
- if (typeof value !== "boolean") {
31456
- throw new WorkflowInputError(
31457
- `Invalid override for '${path2}': expected BOOLEAN, received ${getValueTypeName(
31458
- value
31459
- )}.`,
31460
- "overrides"
31461
- );
31384
+ (clipConsumer) => clipConsumer.inputName === "positive"
31385
+ )) {
31386
+ return "positive_prompt";
31387
+ }
31388
+ if (clipConsumers.some(
31389
+ (clipConsumer) => clipConsumer.inputName === "negative"
31390
+ )) {
31391
+ return "negative_prompt";
31392
+ }
31462
31393
  }
31463
- return;
31394
+ return "prompt_text";
31395
+ }
31396
+ }
31397
+ switch (inputName) {
31398
+ case "seed":
31399
+ return "seed";
31400
+ case "steps":
31401
+ return "steps";
31402
+ case "cfg":
31403
+ return "cfg";
31404
+ case "sampler_name":
31405
+ return "sampler";
31406
+ case "scheduler":
31407
+ return "scheduler";
31408
+ case "denoise":
31409
+ return "denoise";
31410
+ case "width":
31411
+ return "width";
31412
+ case "height":
31413
+ return "height";
31414
+ case "batch_size":
31415
+ return "batch_size";
31416
+ case "ckpt_name":
31417
+ return "checkpoint";
31418
+ case "filename_prefix":
31419
+ return "filename_prefix";
31420
+ case "text":
31421
+ return "text";
31464
31422
  default:
31465
- return;
31423
+ return void 0;
31466
31424
  }
31467
31425
  }
31468
31426
  function getEditableAliases(canonicalPath, nodeId, inputName, role, widgetPathMap) {
@@ -31512,15 +31470,15 @@ function buildEditableInputIndex(editableInputs) {
31512
31470
  pathsByNode
31513
31471
  };
31514
31472
  }
31515
- function getPathSegmentHelp(prompt, path2) {
31516
- const parts = path2.split(".");
31473
+ function getPathSegmentHelp(prompt, path4) {
31474
+ const parts = path4.split(".");
31517
31475
  if (parts.length === 0 || parts[0].length === 0) {
31518
31476
  return void 0;
31519
31477
  }
31520
31478
  const nodeId = parts[0];
31521
31479
  return {
31522
31480
  nodeId,
31523
- nodeExists: isRecord2(prompt[nodeId])
31481
+ nodeExists: isRecord(prompt[nodeId])
31524
31482
  };
31525
31483
  }
31526
31484
  function describeAvailableNodePaths(editableIndex, nodeId) {
@@ -31702,6 +31660,221 @@ function buildOverrideExamples(editableInputs, limit = 5) {
31702
31660
  function formatOverrideExamplesText(examples) {
31703
31661
  return JSON.stringify(examples, null, 2);
31704
31662
  }
31663
+ function analyzeEditableInputs(prompt, objectInfo, widgetPathMap = {}) {
31664
+ const consumerMap = /* @__PURE__ */ new Map();
31665
+ for (const [nodeId, node] of Object.entries(prompt)) {
31666
+ for (const [inputName, value] of Object.entries(node.inputs)) {
31667
+ if (!isConnectionValue(value)) {
31668
+ continue;
31669
+ }
31670
+ const sourceNodeId = String(value[0]);
31671
+ const consumers = consumerMap.get(sourceNodeId) ?? [];
31672
+ consumers.push({
31673
+ inputName,
31674
+ classType: node.class_type,
31675
+ nodeId
31676
+ });
31677
+ consumerMap.set(sourceNodeId, consumers);
31678
+ }
31679
+ }
31680
+ const editableInputs = [];
31681
+ for (const [nodeId, node] of Object.entries(prompt)) {
31682
+ for (const [inputName, value] of Object.entries(node.inputs)) {
31683
+ if (isConnectionValue(value)) {
31684
+ continue;
31685
+ }
31686
+ editableInputs.push({
31687
+ path: `${nodeId}.inputs.${inputName}`,
31688
+ node_id: nodeId,
31689
+ class_type: node.class_type,
31690
+ input_name: inputName,
31691
+ value,
31692
+ value_preview: formatValuePreview(value),
31693
+ role: inferEditableRole(
31694
+ nodeId,
31695
+ node.class_type,
31696
+ inputName,
31697
+ consumerMap
31698
+ ),
31699
+ aliases: [],
31700
+ expected_type: getExpectedTypeName(
31701
+ getInputDefinition(objectInfo, prompt, nodeId, inputName)
31702
+ ),
31703
+ ...getInputOptionsSummary(
31704
+ getInputDefinition(objectInfo, prompt, nodeId, inputName)
31705
+ )
31706
+ });
31707
+ }
31708
+ }
31709
+ return editableInputs.map((entry) => ({
31710
+ ...entry,
31711
+ aliases: getEditableAliases(
31712
+ entry.path,
31713
+ entry.node_id,
31714
+ entry.input_name,
31715
+ entry.role,
31716
+ widgetPathMap
31717
+ )
31718
+ })).sort(
31719
+ (left, right) => left.path.localeCompare(right.path, void 0, { numeric: true })
31720
+ );
31721
+ }
31722
+ function formatEditableInputsText(editableInputs, options = {}) {
31723
+ if (editableInputs.length === 0) {
31724
+ return "No literal editable inputs were detected.";
31725
+ }
31726
+ return formatListItems(editableInputs, (entry) => {
31727
+ const role = entry.role ? ` [${entry.role}]` : "";
31728
+ const classType = entry.class_type ? ` (${entry.class_type})` : "";
31729
+ const expectedType = entry.expected_type ? ` <${entry.expected_type}>` : "";
31730
+ const aliasSuffix = entry.aliases.length > 0 ? ` | aliases: ${entry.aliases.join(", ")}` : "";
31731
+ const optionsSuffix = options.includeOptions ? formatEditableInputOptionsText(entry) : "";
31732
+ return `${entry.path}${role}${classType}${expectedType}: ${entry.value_preview}${aliasSuffix}${optionsSuffix}`;
31733
+ });
31734
+ }
31735
+ function formatInputDefinitionSummary(inputName, inputDefinition) {
31736
+ const [typeData, extraInfo] = inputDefinition;
31737
+ const typeLabel = Array.isArray(typeData) ? `COMBO (${typeData.length} options)` : String(typeData).toUpperCase();
31738
+ const details = [];
31739
+ if (Array.isArray(typeData)) {
31740
+ details.push(
31741
+ `options: ${typeData.slice(0, 5).map((option) => JSON.stringify(option)).join(", ")}${typeData.length > 5 ? ", ..." : ""}`
31742
+ );
31743
+ }
31744
+ if (isRecord(extraInfo)) {
31745
+ if (extraInfo.default !== void 0) {
31746
+ details.push(`default: ${formatValuePreview(extraInfo.default)}`);
31747
+ }
31748
+ if (typeof extraInfo.min === "number") {
31749
+ details.push(`min: ${extraInfo.min}`);
31750
+ }
31751
+ if (typeof extraInfo.max === "number") {
31752
+ details.push(`max: ${extraInfo.max}`);
31753
+ }
31754
+ }
31755
+ return `${inputName} <${typeLabel}>${details.length > 0 ? ` | ${details.join(" | ")}` : ""}`;
31756
+ }
31757
+ function formatNodeDefinitionText(nodeClass, node) {
31758
+ const requiredInputs = Object.entries(node.input.required ?? {});
31759
+ const optionalInputs = Object.entries(node.input.optional ?? {});
31760
+ const lines = [
31761
+ `Node: ${nodeClass}`,
31762
+ `Display name: ${node.display_name || node.name || nodeClass}`,
31763
+ `Category: ${node.category || "uncategorized"}`
31764
+ ];
31765
+ if (typeof node.description === "string" && node.description.trim().length > 0) {
31766
+ lines.push(`Description: ${node.description.trim()}`);
31767
+ }
31768
+ lines.push(
31769
+ `Required inputs (${requiredInputs.length}): ${requiredInputs.length > 0 ? requiredInputs.map(
31770
+ ([inputName, inputDefinition]) => formatInputDefinitionSummary(
31771
+ inputName,
31772
+ inputDefinition
31773
+ )
31774
+ ).join("\n") : "None."}`
31775
+ );
31776
+ lines.push(
31777
+ `Optional inputs (${optionalInputs.length}): ${optionalInputs.length > 0 ? optionalInputs.map(
31778
+ ([inputName, inputDefinition]) => formatInputDefinitionSummary(
31779
+ inputName,
31780
+ inputDefinition
31781
+ )
31782
+ ).join("\n") : "None."}`
31783
+ );
31784
+ lines.push(`Outputs: ${node.output.join(", ") || "None."}`);
31785
+ return lines.join("\n");
31786
+ }
31787
+ function formatNodeListText(classes, rawInfo, pagination) {
31788
+ const text = `Available nodes (${pagination.total} total):
31789
+ ` + formatListItems(classes, (nodeClass) => {
31790
+ const node = rawInfo[nodeClass];
31791
+ const displayName = node.display_name && node.display_name !== nodeClass ? ` \u2014 ${node.display_name}` : "";
31792
+ const category = node.category ? ` | category: ${node.category}` : "";
31793
+ const description = typeof node.description === "string" && node.description.trim().length > 0 ? ` | ${node.description.trim()}` : "";
31794
+ return `${nodeClass}${displayName}${category}${description}`;
31795
+ });
31796
+ if (!pagination.hasMore) {
31797
+ return text;
31798
+ }
31799
+ return `${text}
31800
+
31801
+ ${formatPaginationFooter(
31802
+ pagination.offset,
31803
+ pagination.limit,
31804
+ pagination.total
31805
+ )}`;
31806
+ }
31807
+
31808
+ // src/zod.ts
31809
+ var zNamespace = zod_exports;
31810
+ var z3 = zNamespace.z ?? zNamespace.default ?? zNamespace;
31811
+
31812
+ // src/workflow/overrides.ts
31813
+ var INPUT_SEGMENT = "inputs";
31814
+ var OverrideRecordSchema = z3.record(z3.string(), z3.unknown());
31815
+ var OverridesSchema = z3.union([OverrideRecordSchema, z3.string()]).describe(
31816
+ 'Dot-notation overrides as an object or JSON string. e.g. {"6.seed": 7}'
31817
+ );
31818
+ function validateOverrideValueType(path4, value, inputDefinition) {
31819
+ if (!inputDefinition) {
31820
+ return;
31821
+ }
31822
+ const [typeData] = inputDefinition;
31823
+ if (Array.isArray(typeData)) {
31824
+ if (!typeData.some((option) => option === value)) {
31825
+ throw new WorkflowInputError(
31826
+ `Invalid override for '${path4}': expected one of ${typeData.map((option) => JSON.stringify(option)).join(", ")}, received ${JSON.stringify(value)}.`,
31827
+ "overrides"
31828
+ );
31829
+ }
31830
+ return;
31831
+ }
31832
+ const typeName = String(typeData).toUpperCase();
31833
+ switch (typeName) {
31834
+ case "INT":
31835
+ if (typeof value !== "number" || !Number.isInteger(value)) {
31836
+ throw new WorkflowInputError(
31837
+ `Invalid override for '${path4}': expected INT, received ${getValueTypeName(
31838
+ value
31839
+ )}.`,
31840
+ "overrides"
31841
+ );
31842
+ }
31843
+ return;
31844
+ case "FLOAT":
31845
+ if (typeof value !== "number") {
31846
+ throw new WorkflowInputError(
31847
+ `Invalid override for '${path4}': expected FLOAT, received ${getValueTypeName(
31848
+ value
31849
+ )}.`,
31850
+ "overrides"
31851
+ );
31852
+ }
31853
+ return;
31854
+ case "STRING":
31855
+ if (typeof value !== "string") {
31856
+ throw new WorkflowInputError(
31857
+ `Invalid override for '${path4}': expected STRING, received ${getValueTypeName(
31858
+ value
31859
+ )}.`,
31860
+ "overrides"
31861
+ );
31862
+ }
31863
+ return;
31864
+ case "BOOLEAN":
31865
+ if (typeof value !== "boolean") {
31866
+ throw new WorkflowInputError(
31867
+ `Invalid override for '${path4}': expected BOOLEAN, received ${getValueTypeName(
31868
+ value
31869
+ )}.`,
31870
+ "overrides"
31871
+ );
31872
+ }
31873
+ return;
31874
+ default:
31875
+ return;
31876
+ }
31877
+ }
31705
31878
  function resolveOverridePath(prompt, rawPath, editableIndex) {
31706
31879
  if (!editableIndex) {
31707
31880
  return rawPath;
@@ -31720,25 +31893,9 @@ function resolveOverridePath(prompt, rawPath, editableIndex) {
31720
31893
  }
31721
31894
  const resolved = editableIndex.aliasToPath.get(rawPath);
31722
31895
  if (resolved) {
31723
- return resolved;
31724
- }
31725
- const rawParts = rawPath.split(".");
31726
- if (rawParts.length >= 3 && rawParts[1] === INPUT_SEGMENT) {
31727
- const pathHelp2 = getPathSegmentHelp(prompt, rawPath);
31728
- if (pathHelp2?.nodeId && pathHelp2.nodeExists) {
31729
- const validPaths = describeAvailableNodePaths(
31730
- editableIndex,
31731
- pathHelp2.nodeId
31732
- );
31733
- if (validPaths.length > 0) {
31734
- throw new WorkflowInputError(
31735
- `Unsupported override path '${rawPath}'. Only detected editable inputs can be overridden. Valid editable paths for node ${pathHelp2.nodeId}:
31736
- ${validPaths}`,
31737
- "overrides"
31738
- );
31739
- }
31740
- }
31896
+ return resolved;
31741
31897
  }
31898
+ const rawParts = rawPath.split(".");
31742
31899
  const pathHelp = getPathSegmentHelp(prompt, rawPath);
31743
31900
  if (pathHelp?.nodeId && pathHelp.nodeExists) {
31744
31901
  const validPaths = describeAvailableNodePaths(
@@ -31746,11 +31903,10 @@ ${validPaths}`,
31746
31903
  pathHelp.nodeId
31747
31904
  );
31748
31905
  if (validPaths.length > 0) {
31749
- throw new WorkflowInputError(
31750
- `Unknown override path '${rawPath}'. Valid editable paths for node ${pathHelp.nodeId}:
31751
- ${validPaths}`,
31752
- "overrides"
31753
- );
31906
+ const message = rawParts.length >= 3 && rawParts[1] === INPUT_SEGMENT ? `Unsupported override path '${rawPath}'. Only detected editable inputs can be overridden. Valid editable paths for node ${pathHelp.nodeId}:
31907
+ ${validPaths}` : `Unknown override path '${rawPath}'. Valid editable paths for node ${pathHelp.nodeId}:
31908
+ ${validPaths}`;
31909
+ throw new WorkflowInputError(message, "overrides");
31754
31910
  }
31755
31911
  }
31756
31912
  return rawPath;
@@ -31779,78 +31935,6 @@ function parseOverrides(rawOverrides) {
31779
31935
  }
31780
31936
  return parsed;
31781
31937
  }
31782
- function analyzeEditableInputs(prompt, objectInfo, widgetPathMap = {}) {
31783
- const consumerMap = /* @__PURE__ */ new Map();
31784
- for (const [nodeId, node] of Object.entries(prompt)) {
31785
- for (const [inputName, value] of Object.entries(node.inputs)) {
31786
- if (!isConnectionValue(value)) {
31787
- continue;
31788
- }
31789
- const sourceNodeId = String(value[0]);
31790
- const consumers = consumerMap.get(sourceNodeId) ?? [];
31791
- consumers.push({
31792
- inputName,
31793
- classType: node.class_type,
31794
- nodeId
31795
- });
31796
- consumerMap.set(sourceNodeId, consumers);
31797
- }
31798
- }
31799
- const editableInputs = [];
31800
- for (const [nodeId, node] of Object.entries(prompt)) {
31801
- for (const [inputName, value] of Object.entries(node.inputs)) {
31802
- if (isConnectionValue(value)) {
31803
- continue;
31804
- }
31805
- editableInputs.push({
31806
- path: `${nodeId}.inputs.${inputName}`,
31807
- node_id: nodeId,
31808
- class_type: node.class_type,
31809
- input_name: inputName,
31810
- value,
31811
- value_preview: formatValuePreview(value),
31812
- role: inferEditableRole(
31813
- nodeId,
31814
- node.class_type,
31815
- inputName,
31816
- consumerMap
31817
- ),
31818
- aliases: [],
31819
- expected_type: getExpectedTypeName(
31820
- getInputDefinition(objectInfo, prompt, nodeId, inputName)
31821
- ),
31822
- ...getInputOptionsSummary(
31823
- getInputDefinition(objectInfo, prompt, nodeId, inputName)
31824
- )
31825
- });
31826
- }
31827
- }
31828
- return editableInputs.map((entry) => ({
31829
- ...entry,
31830
- aliases: getEditableAliases(
31831
- entry.path,
31832
- entry.node_id,
31833
- entry.input_name,
31834
- entry.role,
31835
- widgetPathMap
31836
- )
31837
- })).sort(
31838
- (left, right) => left.path.localeCompare(right.path, void 0, { numeric: true })
31839
- );
31840
- }
31841
- function formatEditableInputsText(editableInputs, options = {}) {
31842
- if (editableInputs.length === 0) {
31843
- return "No literal editable inputs were detected.";
31844
- }
31845
- return formatListItems(editableInputs, (entry) => {
31846
- const role = entry.role ? ` [${entry.role}]` : "";
31847
- const classType = entry.class_type ? ` (${entry.class_type})` : "";
31848
- const expectedType = entry.expected_type ? ` <${entry.expected_type}>` : "";
31849
- const aliasSuffix = entry.aliases.length > 0 ? ` | aliases: ${entry.aliases.join(", ")}` : "";
31850
- const optionsSuffix = options.includeOptions ? formatEditableInputOptionsText(entry) : "";
31851
- return `${entry.path}${role}${classType}${expectedType}: ${entry.value_preview}${aliasSuffix}${optionsSuffix}`;
31852
- });
31853
- }
31854
31938
  function validatePromptPreflight(prompt, objectInfo, widgetPathMap = {}) {
31855
31939
  if (!objectInfo) {
31856
31940
  return {
@@ -31904,106 +31988,6 @@ ${issues.join("\n")}`,
31904
31988
  combo_inputs: comboInputs
31905
31989
  };
31906
31990
  }
31907
- function formatInputDefinitionSummary(inputName, inputDefinition) {
31908
- const [typeData, extraInfo] = inputDefinition;
31909
- const typeLabel = Array.isArray(typeData) ? `COMBO (${typeData.length} options)` : String(typeData).toUpperCase();
31910
- const details = [];
31911
- if (Array.isArray(typeData)) {
31912
- details.push(
31913
- `options: ${typeData.slice(0, 5).map((option) => JSON.stringify(option)).join(", ")}${typeData.length > 5 ? ", ..." : ""}`
31914
- );
31915
- }
31916
- if (isRecord2(extraInfo)) {
31917
- if (extraInfo.default !== void 0) {
31918
- details.push(`default: ${formatValuePreview(extraInfo.default)}`);
31919
- }
31920
- if (typeof extraInfo.min === "number") {
31921
- details.push(`min: ${extraInfo.min}`);
31922
- }
31923
- if (typeof extraInfo.max === "number") {
31924
- details.push(`max: ${extraInfo.max}`);
31925
- }
31926
- }
31927
- return `${inputName} <${typeLabel}>${details.length > 0 ? ` | ${details.join(" | ")}` : ""}`;
31928
- }
31929
- function formatNodeDefinitionText(nodeClass, node) {
31930
- const requiredInputs = Object.entries(node.input.required ?? {});
31931
- const optionalInputs = Object.entries(node.input.optional ?? {});
31932
- const lines = [
31933
- `Node: ${nodeClass}`,
31934
- `Display name: ${node.display_name || node.name || nodeClass}`,
31935
- `Category: ${node.category || "uncategorized"}`
31936
- ];
31937
- if (typeof node.description === "string" && node.description.trim().length > 0) {
31938
- lines.push(`Description: ${node.description.trim()}`);
31939
- }
31940
- lines.push(
31941
- `Required inputs (${requiredInputs.length}): ${requiredInputs.length > 0 ? requiredInputs.map(
31942
- ([inputName, inputDefinition]) => formatInputDefinitionSummary(
31943
- inputName,
31944
- inputDefinition
31945
- )
31946
- ).join("\n") : "None."}`
31947
- );
31948
- lines.push(
31949
- `Optional inputs (${optionalInputs.length}): ${optionalInputs.length > 0 ? optionalInputs.map(
31950
- ([inputName, inputDefinition]) => formatInputDefinitionSummary(
31951
- inputName,
31952
- inputDefinition
31953
- )
31954
- ).join("\n") : "None."}`
31955
- );
31956
- lines.push(`Outputs: ${node.output.join(", ") || "None."}`);
31957
- return lines.join("\n");
31958
- }
31959
- function formatNodeListText(classes, rawInfo, pagination) {
31960
- const text = `Available nodes (${pagination.total} total):
31961
- ` + formatListItems(classes, (nodeClass) => {
31962
- const node = rawInfo[nodeClass];
31963
- const displayName = node.display_name && node.display_name !== nodeClass ? ` \u2014 ${node.display_name}` : "";
31964
- const category = node.category ? ` | category: ${node.category}` : "";
31965
- const description = typeof node.description === "string" && node.description.trim().length > 0 ? ` | ${node.description.trim()}` : "";
31966
- return `${nodeClass}${displayName}${category}${description}`;
31967
- });
31968
- if (!pagination.hasMore) {
31969
- return text;
31970
- }
31971
- return `${text}
31972
-
31973
- ${formatPaginationFooter(
31974
- pagination.offset,
31975
- pagination.limit,
31976
- pagination.total
31977
- )}`;
31978
- }
31979
- async function getQueueSnapshot() {
31980
- try {
31981
- return IS_MOCK ? MOCK_FIXTURES.queue : await fetchComfyJson("/queue");
31982
- } catch {
31983
- return void 0;
31984
- }
31985
- }
31986
- function formatQueueSnapshotText(queueSnapshot) {
31987
- if (!queueSnapshot) {
31988
- return "";
31989
- }
31990
- return `Queue snapshot: ${queueSnapshot.queue_running.length} running, ${queueSnapshot.queue_pending.length} pending.`;
31991
- }
31992
- function getPromptQueueVisibility(promptId, queueSnapshot) {
31993
- if (!queueSnapshot) {
31994
- return null;
31995
- }
31996
- return JSON.stringify(queueSnapshot).includes(promptId);
31997
- }
31998
- function deriveSavedWorkflowFileName(sourcePath, requestedName) {
31999
- if (requestedName) {
32000
- return normalizeWorkflowFileName(requestedName);
32001
- }
32002
- if (sourcePath) {
32003
- return normalizeWorkflowFileName(path.basename(sourcePath));
32004
- }
32005
- return `workflow-${Date.now()}.json`;
32006
- }
32007
31991
  function applyOverrides(prompt, overrides, options = {}) {
32008
31992
  for (const [rawKey, value] of Object.entries(overrides)) {
32009
31993
  const key = resolveOverridePath(prompt, rawKey, options.editableIndex);
@@ -32012,7 +31996,7 @@ function applyOverrides(prompt, overrides, options = {}) {
32012
31996
  for (let i = 0; i < parts.length - 1; i++) {
32013
31997
  const part = parts[i];
32014
31998
  const next = current[part];
32015
- if (!isRecord2(next)) {
31999
+ if (!isRecord(next) || Array.isArray(next)) {
32016
32000
  const pathHelp = getPathSegmentHelp(prompt, key);
32017
32001
  const validPaths = pathHelp?.nodeId && pathHelp.nodeExists ? describeAvailableNodePaths(
32018
32002
  options.editableIndex,
@@ -32040,6 +32024,63 @@ ${validPaths}` : "";
32040
32024
  current[parts[parts.length - 1]] = value;
32041
32025
  }
32042
32026
  }
32027
+
32028
+ // src/index.ts
32029
+ var PACKAGE_VERSION = getPackageVersion(import.meta.url);
32030
+ function handleComfyError(error48) {
32031
+ if (error48 instanceof zod_exports.ZodError) {
32032
+ return createValidationError(
32033
+ "arguments",
32034
+ error48.issues[0]?.message || "Invalid input."
32035
+ );
32036
+ }
32037
+ if (error48 instanceof WorkflowInputError) {
32038
+ return error48.kind === "not_found" ? createNotFoundError(error48.message) : createValidationError(error48.field, error48.message);
32039
+ }
32040
+ if (error48 instanceof ComfyApiError) {
32041
+ const detailSummary = summarizeComfyErrorDetails(error48.details);
32042
+ return {
32043
+ content: [
32044
+ {
32045
+ type: "text",
32046
+ text: detailSummary.length > 0 ? `${error48.message}
32047
+ ${detailSummary}` : error48.message
32048
+ }
32049
+ ],
32050
+ structuredContent: toStructuredContent(error48.details),
32051
+ isError: true
32052
+ };
32053
+ }
32054
+ if (error48 instanceof WorkflowTimeoutError) {
32055
+ const queueSummary = formatQueueSnapshotText(error48.queueSnapshot);
32056
+ const promptVisibility = getPromptQueueVisibility(
32057
+ error48.promptId,
32058
+ error48.queueSnapshot
32059
+ );
32060
+ const promptVisibilityText = promptVisibility === true ? "The prompt is still visible in the ComfyUI queue snapshot." : promptVisibility === false ? "The prompt is not visible in the ComfyUI queue snapshot; inspect ComfyUI history or server logs if it never completes." : "ComfyUI queue state was unavailable at timeout.";
32061
+ return {
32062
+ content: [
32063
+ {
32064
+ type: "text",
32065
+ text: [
32066
+ `${error48.message}.`,
32067
+ queueSummary,
32068
+ promptVisibilityText,
32069
+ `You can re-attach by calling 'comfyui_wait_for_workflow' again with prompt_id '${error48.promptId}'.`
32070
+ ].filter((line) => line && line.length > 0).join("\n")
32071
+ }
32072
+ ],
32073
+ structuredContent: {
32074
+ prompt_id: error48.promptId,
32075
+ timeout_seconds: error48.timeoutSeconds,
32076
+ queue: error48.queueSnapshot ?? null,
32077
+ prompt_visible_in_queue: promptVisibility
32078
+ },
32079
+ isError: true
32080
+ };
32081
+ }
32082
+ return createInternalError(error48);
32083
+ }
32043
32084
  var InspectNodeSchema = paginationSchema.extend({
32044
32085
  node_class: z3.string().optional().describe(
32045
32086
  "Specific node class to inspect. If omitted, lists all available nodes."
@@ -32139,9 +32180,6 @@ async function handleInspectNode(args) {
32139
32180
  return handleComfyError(error48);
32140
32181
  }
32141
32182
  }
32142
- function getWorkflowId(absolutePath) {
32143
- return crypto.createHash("sha256").update(absolutePath).digest("hex").substring(0, 8);
32144
- }
32145
32183
  async function handleDiscoverWorkflows(args) {
32146
32184
  const pathsToScan = getWorkflowDirectories(args.directory_path);
32147
32185
  const objectInfoAvailability = await tryGetObjectInfo();
@@ -32149,10 +32187,10 @@ async function handleDiscoverWorkflows(args) {
32149
32187
  let skippedNonWorkflowFiles = 0;
32150
32188
  for (const dir of pathsToScan) {
32151
32189
  try {
32152
- const entries = await fs.readdir(dir, { withFileTypes: true });
32190
+ const entries = await fs3.readdir(dir, { withFileTypes: true });
32153
32191
  for (const entry of entries) {
32154
32192
  if (entry.isFile() && entry.name.endsWith(".json")) {
32155
- const fullPath = path.join(dir, entry.name);
32193
+ const fullPath = path3.join(dir, entry.name);
32156
32194
  try {
32157
32195
  const inspected = await inspectWorkflowFile(
32158
32196
  fullPath,
@@ -32237,28 +32275,6 @@ Hint: If your workflows are stored elsewhere, provide a custom path using the 'd
32237
32275
  }
32238
32276
  };
32239
32277
  }
32240
- async function waitForWorkflowResult(prompt_id, timeout_seconds) {
32241
- const start = Date.now();
32242
- const end = start + timeout_seconds * 1e3;
32243
- while (Date.now() < end) {
32244
- let history = MOCK_FIXTURES.history;
32245
- if (!IS_MOCK) {
32246
- try {
32247
- history = await fetchComfyJson(
32248
- `/history/${prompt_id}`
32249
- );
32250
- } catch {
32251
- history = {};
32252
- }
32253
- }
32254
- if (history?.[prompt_id]) {
32255
- return history[prompt_id];
32256
- }
32257
- await new Promise((r) => setTimeout(r, 1e3));
32258
- }
32259
- const queueSnapshot = await getQueueSnapshot();
32260
- throw new WorkflowTimeoutError(prompt_id, timeout_seconds, queueSnapshot);
32261
- }
32262
32278
  async function handleWaitForWorkflow(args) {
32263
32279
  try {
32264
32280
  const historyEntry = await waitForWorkflowResult(
@@ -32438,11 +32454,11 @@ async function handleSaveWorkflow(args) {
32438
32454
  "Directory traversal sequences (..) are not allowed."
32439
32455
  );
32440
32456
  }
32441
- const outputPath = path.resolve(process.cwd(), outputFileName);
32442
- await fs.mkdir(path.dirname(outputPath), { recursive: true });
32457
+ const outputPath = path3.resolve(process.cwd(), outputFileName);
32458
+ await fs3.mkdir(path3.dirname(outputPath), { recursive: true });
32443
32459
  let existedBeforeSave = false;
32444
32460
  try {
32445
- await fs.access(outputPath);
32461
+ await fs3.access(outputPath);
32446
32462
  existedBeforeSave = true;
32447
32463
  } catch {
32448
32464
  existedBeforeSave = false;
@@ -32453,7 +32469,7 @@ async function handleSaveWorkflow(args) {
32453
32469
  `File already exists: ${outputFileName}. Set overwrite=true to replace it.`
32454
32470
  );
32455
32471
  }
32456
- await fs.writeFile(
32472
+ await fs3.writeFile(
32457
32473
  outputPath,
32458
32474
  `${JSON.stringify(prompt, null, 2)}
32459
32475
  `,