@fre4x/comfyui 1.0.65 → 1.1.0-beta.1
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.
- package/README.md +24 -5
- package/dist/index.js +476 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,6 +60,9 @@ If you want a single call that submits and waits, pass `await: true` with a
|
|
|
60
60
|
`timeout` value in seconds to `comfyui_workflow_run`. For lower-level control,
|
|
61
61
|
use `comfyui_wait_for_workflow` with `prompt_id` and `timeout`.
|
|
62
62
|
|
|
63
|
+
`comfyui_workflow_run` accepts either a stored workflow reference
|
|
64
|
+
(`workflow_id` / `workflow_file_path`) or an inline `workflow` JSON object.
|
|
65
|
+
|
|
63
66
|
## Stored Workflow Reuse
|
|
64
67
|
|
|
65
68
|
The stored-workflow tools let you avoid re-sending large API JSON blobs and give
|
|
@@ -71,20 +74,32 @@ agents a stable local edit loop.
|
|
|
71
74
|
4. Reuse it with `comfyui_workflow_run`
|
|
72
75
|
|
|
73
76
|
`comfyui_workflow_run` and `comfyui_save_workflow` both accept dot-notation
|
|
74
|
-
overrides in
|
|
77
|
+
overrides in canonical `node.inputs.field` form, plus shorter aliases like
|
|
78
|
+
`node.seed`, `node.text`, `node.positive_prompt`, and, for Web UI workflows,
|
|
79
|
+
`node.widgets[index]`.
|
|
75
80
|
|
|
76
81
|
```json
|
|
77
82
|
{
|
|
78
83
|
"workflow_id": "pony-portrait-v1",
|
|
79
84
|
"overrides": {
|
|
80
|
-
"2.
|
|
81
|
-
"6.
|
|
85
|
+
"2.seed": 67890,
|
|
86
|
+
"6.positive_prompt": "cinematic dragon portrait"
|
|
82
87
|
},
|
|
83
88
|
"await": true,
|
|
84
89
|
"timeout": 90
|
|
85
90
|
}
|
|
86
91
|
```
|
|
87
92
|
|
|
93
|
+
If your client serializes nested arguments first, `overrides` may also be sent
|
|
94
|
+
as a JSON string:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"workflow_id": "pony-portrait-v1",
|
|
99
|
+
"overrides": "{\"2.seed\":67890,\"6.positive_prompt\":\"cinematic dragon portrait\"}"
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
88
103
|
To inspect editable inputs and semantic hints for agent editing:
|
|
89
104
|
|
|
90
105
|
```json
|
|
@@ -94,14 +109,18 @@ To inspect editable inputs and semantic hints for agent editing:
|
|
|
94
109
|
}
|
|
95
110
|
```
|
|
96
111
|
|
|
112
|
+
`comfyui_get_workflow` also returns a copy-ready `override_examples` object in
|
|
113
|
+
both `content.text` and `structuredContent` so agents can paste a valid payload
|
|
114
|
+
without rebuilding the shape by hand.
|
|
115
|
+
|
|
97
116
|
To save a new or edited workflow locally:
|
|
98
117
|
|
|
99
118
|
```json
|
|
100
119
|
{
|
|
101
120
|
"workflow_file_path": "pony-portrait-v1.json",
|
|
102
121
|
"overrides": {
|
|
103
|
-
"6.
|
|
104
|
-
"7.
|
|
122
|
+
"6.text": "studio lighting, ultra detailed",
|
|
123
|
+
"7.filename_prefix": "pony-portrait-agent"
|
|
105
124
|
},
|
|
106
125
|
"output_file_name": "pony-portrait-agent.json",
|
|
107
126
|
"overwrite": true
|
package/dist/index.js
CHANGED
|
@@ -30637,8 +30637,9 @@ function normalizeWidgetValue(value, typeName, extraInfo) {
|
|
|
30637
30637
|
}
|
|
30638
30638
|
return value;
|
|
30639
30639
|
}
|
|
30640
|
-
function
|
|
30640
|
+
function convertWebUIToAPIWithMetadata(webUIJson, objectInfo) {
|
|
30641
30641
|
const prompt = {};
|
|
30642
|
+
const widgetPathMap = {};
|
|
30642
30643
|
const links = webUIJson.links || [];
|
|
30643
30644
|
const nodes = webUIJson.nodes || [];
|
|
30644
30645
|
const serverNodeIds = /* @__PURE__ */ new Set();
|
|
@@ -30703,14 +30704,16 @@ function convertWebUIToAPI(webUIJson, objectInfo) {
|
|
|
30703
30704
|
}
|
|
30704
30705
|
continue;
|
|
30705
30706
|
}
|
|
30707
|
+
const widgetSourceIndex = widgetIndex;
|
|
30706
30708
|
const value = normalizeWidgetValue(
|
|
30707
|
-
widgets[
|
|
30709
|
+
widgets[widgetSourceIndex],
|
|
30708
30710
|
typeName,
|
|
30709
30711
|
inputExtraInfo
|
|
30710
30712
|
);
|
|
30711
30713
|
widgetIndex++;
|
|
30712
30714
|
if (!isConnectedWidget) {
|
|
30713
30715
|
promptNode.inputs[inputName] = value;
|
|
30716
|
+
widgetPathMap[`${nodeIdStr}.widgets[${widgetSourceIndex}]`] = `${nodeIdStr}.inputs.${inputName}`;
|
|
30714
30717
|
}
|
|
30715
30718
|
}
|
|
30716
30719
|
};
|
|
@@ -30725,7 +30728,10 @@ function convertWebUIToAPI(webUIJson, objectInfo) {
|
|
|
30725
30728
|
}
|
|
30726
30729
|
prompt[nodeIdStr] = promptNode;
|
|
30727
30730
|
}
|
|
30728
|
-
return
|
|
30731
|
+
return {
|
|
30732
|
+
prompt,
|
|
30733
|
+
widgetPathMap
|
|
30734
|
+
};
|
|
30729
30735
|
}
|
|
30730
30736
|
|
|
30731
30737
|
// src/index.ts
|
|
@@ -30749,6 +30755,11 @@ var WorkflowInputError = class extends Error {
|
|
|
30749
30755
|
this.name = "WorkflowInputError";
|
|
30750
30756
|
}
|
|
30751
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
|
+
);
|
|
30752
30763
|
function isRecord2(value) {
|
|
30753
30764
|
return typeof value === "object" && value !== null;
|
|
30754
30765
|
}
|
|
@@ -31022,6 +31033,22 @@ function isConnectionValue(value) {
|
|
|
31022
31033
|
async function getObjectInfo() {
|
|
31023
31034
|
return IS_MOCK ? MOCK_FIXTURES.object_info : await fetchComfyJson("/object_info");
|
|
31024
31035
|
}
|
|
31036
|
+
function summarizeErrorMessage(error48) {
|
|
31037
|
+
return error48 instanceof Error ? error48.message : String(error48);
|
|
31038
|
+
}
|
|
31039
|
+
async function tryGetObjectInfo() {
|
|
31040
|
+
try {
|
|
31041
|
+
return {
|
|
31042
|
+
objectInfo: await getObjectInfo()
|
|
31043
|
+
};
|
|
31044
|
+
} catch (error48) {
|
|
31045
|
+
return {
|
|
31046
|
+
warning: `Node schema details unavailable from ComfyUI: ${summarizeErrorMessage(
|
|
31047
|
+
error48
|
|
31048
|
+
)}`
|
|
31049
|
+
};
|
|
31050
|
+
}
|
|
31051
|
+
}
|
|
31025
31052
|
async function resolveWorkflowReference(args) {
|
|
31026
31053
|
if (args.workflow_file_path) {
|
|
31027
31054
|
const targetPath = args.workflow_file_path;
|
|
@@ -31097,7 +31124,7 @@ async function resolveWorkflowReference(args) {
|
|
|
31097
31124
|
"workflow_id"
|
|
31098
31125
|
);
|
|
31099
31126
|
}
|
|
31100
|
-
async function normalizeWorkflowPrompt(workflow) {
|
|
31127
|
+
async function normalizeWorkflowPrompt(workflow, objectInfo) {
|
|
31101
31128
|
if (!isRecord2(workflow)) {
|
|
31102
31129
|
throw new WorkflowInputError(
|
|
31103
31130
|
"Workflow must be a JSON object.",
|
|
@@ -31105,13 +31132,22 @@ async function normalizeWorkflowPrompt(workflow) {
|
|
|
31105
31132
|
);
|
|
31106
31133
|
}
|
|
31107
31134
|
let sourceFormat = "api";
|
|
31135
|
+
let widgetPathMap = {};
|
|
31108
31136
|
let normalizedWorkflow = workflow;
|
|
31109
31137
|
if (isWebUIFormat(normalizedWorkflow)) {
|
|
31138
|
+
if (!objectInfo) {
|
|
31139
|
+
throw new WorkflowInputError(
|
|
31140
|
+
"Web UI workflow normalization requires ComfyUI node definitions. Start the ComfyUI server or save the workflow in API format first.",
|
|
31141
|
+
"workflow"
|
|
31142
|
+
);
|
|
31143
|
+
}
|
|
31110
31144
|
sourceFormat = "webui";
|
|
31111
|
-
|
|
31145
|
+
const converted = convertWebUIToAPIWithMetadata(
|
|
31112
31146
|
normalizedWorkflow,
|
|
31113
|
-
|
|
31147
|
+
objectInfo
|
|
31114
31148
|
);
|
|
31149
|
+
normalizedWorkflow = converted.prompt;
|
|
31150
|
+
widgetPathMap = converted.widgetPathMap;
|
|
31115
31151
|
}
|
|
31116
31152
|
const promptCandidate = isRecord2(normalizedWorkflow.prompt) && normalizedWorkflow.prompt ? normalizedWorkflow.prompt : normalizedWorkflow;
|
|
31117
31153
|
if (!isRecord2(promptCandidate)) {
|
|
@@ -31122,10 +31158,11 @@ async function normalizeWorkflowPrompt(workflow) {
|
|
|
31122
31158
|
}
|
|
31123
31159
|
return {
|
|
31124
31160
|
prompt: extractRunnablePromptNodes(promptCandidate),
|
|
31125
|
-
sourceFormat
|
|
31161
|
+
sourceFormat,
|
|
31162
|
+
widgetPathMap
|
|
31126
31163
|
};
|
|
31127
31164
|
}
|
|
31128
|
-
async function loadWorkflow(args) {
|
|
31165
|
+
async function loadWorkflow(args, objectInfo) {
|
|
31129
31166
|
const { targetPath, content } = await resolveWorkflowReference(args);
|
|
31130
31167
|
let parsed;
|
|
31131
31168
|
try {
|
|
@@ -31136,11 +31173,12 @@ async function loadWorkflow(args) {
|
|
|
31136
31173
|
args.workflow_file_path ? "workflow_file_path" : "workflow_id"
|
|
31137
31174
|
);
|
|
31138
31175
|
}
|
|
31139
|
-
const { prompt, sourceFormat } = await normalizeWorkflowPrompt(parsed);
|
|
31176
|
+
const { prompt, sourceFormat, widgetPathMap } = await normalizeWorkflowPrompt(parsed, objectInfo);
|
|
31140
31177
|
return {
|
|
31141
31178
|
targetPath,
|
|
31142
31179
|
prompt,
|
|
31143
|
-
sourceFormat
|
|
31180
|
+
sourceFormat,
|
|
31181
|
+
widgetPathMap
|
|
31144
31182
|
};
|
|
31145
31183
|
}
|
|
31146
31184
|
function inferEditableRole(nodeId, classType, inputName, consumerMap) {
|
|
@@ -31197,7 +31235,278 @@ function formatValuePreview(value) {
|
|
|
31197
31235
|
return String(value);
|
|
31198
31236
|
}
|
|
31199
31237
|
}
|
|
31200
|
-
function
|
|
31238
|
+
function getInputDefinition(objectInfo, prompt, nodeId, inputName) {
|
|
31239
|
+
if (!objectInfo) {
|
|
31240
|
+
return void 0;
|
|
31241
|
+
}
|
|
31242
|
+
const node = prompt[nodeId];
|
|
31243
|
+
if (!node) {
|
|
31244
|
+
return void 0;
|
|
31245
|
+
}
|
|
31246
|
+
const nodeDefinition = objectInfo[node.class_type];
|
|
31247
|
+
if (!nodeDefinition) {
|
|
31248
|
+
return void 0;
|
|
31249
|
+
}
|
|
31250
|
+
return nodeDefinition.input?.required?.[inputName] ?? nodeDefinition.input?.optional?.[inputName];
|
|
31251
|
+
}
|
|
31252
|
+
function getExpectedTypeName(inputDefinition) {
|
|
31253
|
+
if (!inputDefinition) {
|
|
31254
|
+
return void 0;
|
|
31255
|
+
}
|
|
31256
|
+
const [typeData] = inputDefinition;
|
|
31257
|
+
return Array.isArray(typeData) ? "COMBO" : String(typeData).toUpperCase();
|
|
31258
|
+
}
|
|
31259
|
+
function isPlainObject3(value) {
|
|
31260
|
+
return isRecord2(value) && !Array.isArray(value);
|
|
31261
|
+
}
|
|
31262
|
+
function getValueTypeName(value) {
|
|
31263
|
+
if (Array.isArray(value)) {
|
|
31264
|
+
return "array";
|
|
31265
|
+
}
|
|
31266
|
+
if (value === null) {
|
|
31267
|
+
return "null";
|
|
31268
|
+
}
|
|
31269
|
+
return typeof value;
|
|
31270
|
+
}
|
|
31271
|
+
function validateOverrideValueType(path2, value, inputDefinition) {
|
|
31272
|
+
if (!inputDefinition) {
|
|
31273
|
+
return;
|
|
31274
|
+
}
|
|
31275
|
+
const [typeData] = inputDefinition;
|
|
31276
|
+
if (Array.isArray(typeData)) {
|
|
31277
|
+
if (!typeData.some((option) => option === value)) {
|
|
31278
|
+
throw new WorkflowInputError(
|
|
31279
|
+
`Invalid override for '${path2}': expected one of ${typeData.map((option) => JSON.stringify(option)).join(", ")}, received ${JSON.stringify(value)}.`,
|
|
31280
|
+
"overrides"
|
|
31281
|
+
);
|
|
31282
|
+
}
|
|
31283
|
+
return;
|
|
31284
|
+
}
|
|
31285
|
+
const typeName = String(typeData).toUpperCase();
|
|
31286
|
+
switch (typeName) {
|
|
31287
|
+
case "INT":
|
|
31288
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
31289
|
+
throw new WorkflowInputError(
|
|
31290
|
+
`Invalid override for '${path2}': expected INT, received ${getValueTypeName(
|
|
31291
|
+
value
|
|
31292
|
+
)}.`,
|
|
31293
|
+
"overrides"
|
|
31294
|
+
);
|
|
31295
|
+
}
|
|
31296
|
+
return;
|
|
31297
|
+
case "FLOAT":
|
|
31298
|
+
if (typeof value !== "number") {
|
|
31299
|
+
throw new WorkflowInputError(
|
|
31300
|
+
`Invalid override for '${path2}': expected FLOAT, received ${getValueTypeName(
|
|
31301
|
+
value
|
|
31302
|
+
)}.`,
|
|
31303
|
+
"overrides"
|
|
31304
|
+
);
|
|
31305
|
+
}
|
|
31306
|
+
return;
|
|
31307
|
+
case "STRING":
|
|
31308
|
+
if (typeof value !== "string") {
|
|
31309
|
+
throw new WorkflowInputError(
|
|
31310
|
+
`Invalid override for '${path2}': expected STRING, received ${getValueTypeName(
|
|
31311
|
+
value
|
|
31312
|
+
)}.`,
|
|
31313
|
+
"overrides"
|
|
31314
|
+
);
|
|
31315
|
+
}
|
|
31316
|
+
return;
|
|
31317
|
+
case "BOOLEAN":
|
|
31318
|
+
if (typeof value !== "boolean") {
|
|
31319
|
+
throw new WorkflowInputError(
|
|
31320
|
+
`Invalid override for '${path2}': expected BOOLEAN, received ${getValueTypeName(
|
|
31321
|
+
value
|
|
31322
|
+
)}.`,
|
|
31323
|
+
"overrides"
|
|
31324
|
+
);
|
|
31325
|
+
}
|
|
31326
|
+
return;
|
|
31327
|
+
default:
|
|
31328
|
+
return;
|
|
31329
|
+
}
|
|
31330
|
+
}
|
|
31331
|
+
function getEditableAliases(canonicalPath, nodeId, inputName, role, widgetPathMap) {
|
|
31332
|
+
const aliases = /* @__PURE__ */ new Set([`${nodeId}.${inputName}`]);
|
|
31333
|
+
if (role) {
|
|
31334
|
+
aliases.add(`${nodeId}.${role}`);
|
|
31335
|
+
}
|
|
31336
|
+
for (const [widgetPath, resolvedPath] of Object.entries(widgetPathMap)) {
|
|
31337
|
+
if (resolvedPath === canonicalPath) {
|
|
31338
|
+
aliases.add(widgetPath);
|
|
31339
|
+
}
|
|
31340
|
+
}
|
|
31341
|
+
aliases.delete(canonicalPath);
|
|
31342
|
+
return [...aliases];
|
|
31343
|
+
}
|
|
31344
|
+
function buildEditableInputIndex(editableInputs) {
|
|
31345
|
+
const entriesByPath = /* @__PURE__ */ new Map();
|
|
31346
|
+
const aliasToPath = /* @__PURE__ */ new Map();
|
|
31347
|
+
const ambiguousAliases = /* @__PURE__ */ new Map();
|
|
31348
|
+
const pathsByNode = /* @__PURE__ */ new Map();
|
|
31349
|
+
for (const entry of editableInputs) {
|
|
31350
|
+
entriesByPath.set(entry.path, entry);
|
|
31351
|
+
const nodeEntries = pathsByNode.get(entry.node_id) ?? [];
|
|
31352
|
+
nodeEntries.push(entry);
|
|
31353
|
+
pathsByNode.set(entry.node_id, nodeEntries);
|
|
31354
|
+
for (const alias of [entry.path, ...entry.aliases]) {
|
|
31355
|
+
const existing = aliasToPath.get(alias);
|
|
31356
|
+
if (!existing) {
|
|
31357
|
+
aliasToPath.set(alias, entry.path);
|
|
31358
|
+
continue;
|
|
31359
|
+
}
|
|
31360
|
+
if (existing === entry.path) {
|
|
31361
|
+
continue;
|
|
31362
|
+
}
|
|
31363
|
+
const ambiguous = new Set(
|
|
31364
|
+
ambiguousAliases.get(alias) ?? [existing]
|
|
31365
|
+
);
|
|
31366
|
+
ambiguous.add(entry.path);
|
|
31367
|
+
ambiguousAliases.set(alias, [...ambiguous].sort());
|
|
31368
|
+
aliasToPath.delete(alias);
|
|
31369
|
+
}
|
|
31370
|
+
}
|
|
31371
|
+
return {
|
|
31372
|
+
entriesByPath,
|
|
31373
|
+
aliasToPath,
|
|
31374
|
+
ambiguousAliases,
|
|
31375
|
+
pathsByNode
|
|
31376
|
+
};
|
|
31377
|
+
}
|
|
31378
|
+
function getPathSegmentHelp(prompt, path2) {
|
|
31379
|
+
const parts = path2.split(".");
|
|
31380
|
+
if (parts.length === 0 || parts[0].length === 0) {
|
|
31381
|
+
return void 0;
|
|
31382
|
+
}
|
|
31383
|
+
const nodeId = parts[0];
|
|
31384
|
+
return {
|
|
31385
|
+
nodeId,
|
|
31386
|
+
nodeExists: isRecord2(prompt[nodeId])
|
|
31387
|
+
};
|
|
31388
|
+
}
|
|
31389
|
+
function describeAvailableNodePaths(editableIndex, nodeId) {
|
|
31390
|
+
if (!editableIndex) {
|
|
31391
|
+
return "";
|
|
31392
|
+
}
|
|
31393
|
+
const entries = editableIndex.pathsByNode.get(nodeId) ?? [];
|
|
31394
|
+
if (entries.length === 0) {
|
|
31395
|
+
return "";
|
|
31396
|
+
}
|
|
31397
|
+
return entries.map((entry) => {
|
|
31398
|
+
const aliasSuffix = entry.aliases.length > 0 ? ` (aliases: ${entry.aliases.join(", ")})` : "";
|
|
31399
|
+
return `${entry.path}${aliasSuffix}`;
|
|
31400
|
+
}).join("\n");
|
|
31401
|
+
}
|
|
31402
|
+
function getPreferredOverridePath(entry) {
|
|
31403
|
+
const roleAlias = entry.role ? `${entry.node_id}.${entry.role}` : void 0;
|
|
31404
|
+
if (roleAlias && entry.aliases.includes(roleAlias)) {
|
|
31405
|
+
return roleAlias;
|
|
31406
|
+
}
|
|
31407
|
+
const inputAlias = `${entry.node_id}.${entry.input_name}`;
|
|
31408
|
+
if (entry.aliases.includes(inputAlias)) {
|
|
31409
|
+
return inputAlias;
|
|
31410
|
+
}
|
|
31411
|
+
const nonWidgetAlias = entry.aliases.find(
|
|
31412
|
+
(alias) => !alias.includes(".widgets[")
|
|
31413
|
+
);
|
|
31414
|
+
return nonWidgetAlias ?? entry.path;
|
|
31415
|
+
}
|
|
31416
|
+
function buildOverrideExamples(editableInputs, limit = 5) {
|
|
31417
|
+
const examples = {};
|
|
31418
|
+
const preferredPaths = [];
|
|
31419
|
+
for (const entry of editableInputs.slice(0, limit)) {
|
|
31420
|
+
const preferredPath = getPreferredOverridePath(entry);
|
|
31421
|
+
examples[preferredPath] = entry.value;
|
|
31422
|
+
preferredPaths.push(preferredPath);
|
|
31423
|
+
}
|
|
31424
|
+
return {
|
|
31425
|
+
examples,
|
|
31426
|
+
preferred_paths: preferredPaths
|
|
31427
|
+
};
|
|
31428
|
+
}
|
|
31429
|
+
function formatOverrideExamplesText(examples) {
|
|
31430
|
+
return JSON.stringify(examples, null, 2);
|
|
31431
|
+
}
|
|
31432
|
+
function resolveOverridePath(prompt, rawPath, editableIndex) {
|
|
31433
|
+
if (!editableIndex) {
|
|
31434
|
+
return rawPath;
|
|
31435
|
+
}
|
|
31436
|
+
if (editableIndex.entriesByPath.has(rawPath)) {
|
|
31437
|
+
return rawPath;
|
|
31438
|
+
}
|
|
31439
|
+
const ambiguous = editableIndex.ambiguousAliases.get(rawPath);
|
|
31440
|
+
if (ambiguous) {
|
|
31441
|
+
throw new WorkflowInputError(
|
|
31442
|
+
`Ambiguous override path '${rawPath}'. It could refer to: ${ambiguous.join(
|
|
31443
|
+
", "
|
|
31444
|
+
)}. Use a full '<node>.inputs.<field>' path instead.`,
|
|
31445
|
+
"overrides"
|
|
31446
|
+
);
|
|
31447
|
+
}
|
|
31448
|
+
const resolved = editableIndex.aliasToPath.get(rawPath);
|
|
31449
|
+
if (resolved) {
|
|
31450
|
+
return resolved;
|
|
31451
|
+
}
|
|
31452
|
+
const rawParts = rawPath.split(".");
|
|
31453
|
+
if (rawParts.length >= 3 && rawParts[1] === INPUT_SEGMENT) {
|
|
31454
|
+
const pathHelp2 = getPathSegmentHelp(prompt, rawPath);
|
|
31455
|
+
if (pathHelp2?.nodeId && pathHelp2.nodeExists) {
|
|
31456
|
+
const validPaths = describeAvailableNodePaths(
|
|
31457
|
+
editableIndex,
|
|
31458
|
+
pathHelp2.nodeId
|
|
31459
|
+
);
|
|
31460
|
+
if (validPaths.length > 0) {
|
|
31461
|
+
throw new WorkflowInputError(
|
|
31462
|
+
`Unsupported override path '${rawPath}'. Only detected editable inputs can be overridden. Valid editable paths for node ${pathHelp2.nodeId}:
|
|
31463
|
+
${validPaths}`,
|
|
31464
|
+
"overrides"
|
|
31465
|
+
);
|
|
31466
|
+
}
|
|
31467
|
+
}
|
|
31468
|
+
}
|
|
31469
|
+
const pathHelp = getPathSegmentHelp(prompt, rawPath);
|
|
31470
|
+
if (pathHelp?.nodeId && pathHelp.nodeExists) {
|
|
31471
|
+
const validPaths = describeAvailableNodePaths(
|
|
31472
|
+
editableIndex,
|
|
31473
|
+
pathHelp.nodeId
|
|
31474
|
+
);
|
|
31475
|
+
if (validPaths.length > 0) {
|
|
31476
|
+
throw new WorkflowInputError(
|
|
31477
|
+
`Unknown override path '${rawPath}'. Valid editable paths for node ${pathHelp.nodeId}:
|
|
31478
|
+
${validPaths}`,
|
|
31479
|
+
"overrides"
|
|
31480
|
+
);
|
|
31481
|
+
}
|
|
31482
|
+
}
|
|
31483
|
+
return rawPath;
|
|
31484
|
+
}
|
|
31485
|
+
function parseOverrides(rawOverrides) {
|
|
31486
|
+
if (rawOverrides === void 0) {
|
|
31487
|
+
return void 0;
|
|
31488
|
+
}
|
|
31489
|
+
if (typeof rawOverrides !== "string") {
|
|
31490
|
+
return rawOverrides;
|
|
31491
|
+
}
|
|
31492
|
+
let parsed;
|
|
31493
|
+
try {
|
|
31494
|
+
parsed = JSON.parse(rawOverrides);
|
|
31495
|
+
} catch (error48) {
|
|
31496
|
+
throw new WorkflowInputError(
|
|
31497
|
+
`Overrides must be a valid JSON object string: ${error48 instanceof Error ? error48.message : String(error48)}`,
|
|
31498
|
+
"overrides"
|
|
31499
|
+
);
|
|
31500
|
+
}
|
|
31501
|
+
if (!isPlainObject3(parsed)) {
|
|
31502
|
+
throw new WorkflowInputError(
|
|
31503
|
+
"Overrides JSON string must decode to an object.",
|
|
31504
|
+
"overrides"
|
|
31505
|
+
);
|
|
31506
|
+
}
|
|
31507
|
+
return parsed;
|
|
31508
|
+
}
|
|
31509
|
+
function analyzeEditableInputs(prompt, objectInfo, widgetPathMap = {}) {
|
|
31201
31510
|
const consumerMap = /* @__PURE__ */ new Map();
|
|
31202
31511
|
for (const [nodeId, node] of Object.entries(prompt)) {
|
|
31203
31512
|
for (const [inputName, value] of Object.entries(node.inputs)) {
|
|
@@ -31232,11 +31541,24 @@ function analyzeEditableInputs(prompt) {
|
|
|
31232
31541
|
node.class_type,
|
|
31233
31542
|
inputName,
|
|
31234
31543
|
consumerMap
|
|
31544
|
+
),
|
|
31545
|
+
aliases: [],
|
|
31546
|
+
expected_type: getExpectedTypeName(
|
|
31547
|
+
getInputDefinition(objectInfo, prompt, nodeId, inputName)
|
|
31235
31548
|
)
|
|
31236
31549
|
});
|
|
31237
31550
|
}
|
|
31238
31551
|
}
|
|
31239
|
-
return editableInputs.
|
|
31552
|
+
return editableInputs.map((entry) => ({
|
|
31553
|
+
...entry,
|
|
31554
|
+
aliases: getEditableAliases(
|
|
31555
|
+
entry.path,
|
|
31556
|
+
entry.node_id,
|
|
31557
|
+
entry.input_name,
|
|
31558
|
+
entry.role,
|
|
31559
|
+
widgetPathMap
|
|
31560
|
+
)
|
|
31561
|
+
})).sort(
|
|
31240
31562
|
(left, right) => left.path.localeCompare(right.path, void 0, { numeric: true })
|
|
31241
31563
|
);
|
|
31242
31564
|
}
|
|
@@ -31247,7 +31569,9 @@ function formatEditableInputsText(editableInputs) {
|
|
|
31247
31569
|
return formatListItems(editableInputs, (entry) => {
|
|
31248
31570
|
const role = entry.role ? ` [${entry.role}]` : "";
|
|
31249
31571
|
const classType = entry.class_type ? ` (${entry.class_type})` : "";
|
|
31250
|
-
|
|
31572
|
+
const expectedType = entry.expected_type ? ` <${entry.expected_type}>` : "";
|
|
31573
|
+
const aliasSuffix = entry.aliases.length > 0 ? ` | aliases: ${entry.aliases.join(", ")}` : "";
|
|
31574
|
+
return `${entry.path}${role}${classType}${expectedType}: ${entry.value_preview}${aliasSuffix}`;
|
|
31251
31575
|
});
|
|
31252
31576
|
}
|
|
31253
31577
|
function deriveSavedWorkflowFileName(sourcePath, requestedName) {
|
|
@@ -31259,21 +31583,39 @@ function deriveSavedWorkflowFileName(sourcePath, requestedName) {
|
|
|
31259
31583
|
}
|
|
31260
31584
|
return `workflow-${Date.now()}.json`;
|
|
31261
31585
|
}
|
|
31262
|
-
function applyOverrides(prompt, overrides) {
|
|
31263
|
-
for (const [
|
|
31586
|
+
function applyOverrides(prompt, overrides, options = {}) {
|
|
31587
|
+
for (const [rawKey, value] of Object.entries(overrides)) {
|
|
31588
|
+
const key = resolveOverridePath(prompt, rawKey, options.editableIndex);
|
|
31264
31589
|
const parts = key.split(".");
|
|
31265
31590
|
let current = prompt;
|
|
31266
31591
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
31267
31592
|
const part = parts[i];
|
|
31268
31593
|
const next = current[part];
|
|
31269
31594
|
if (!isRecord2(next)) {
|
|
31595
|
+
const pathHelp = getPathSegmentHelp(prompt, key);
|
|
31596
|
+
const validPaths = pathHelp?.nodeId && pathHelp.nodeExists ? describeAvailableNodePaths(
|
|
31597
|
+
options.editableIndex,
|
|
31598
|
+
pathHelp.nodeId
|
|
31599
|
+
) : "";
|
|
31600
|
+
const validPathSuffix = validPaths.length > 0 ? `
|
|
31601
|
+
Valid editable paths for node ${pathHelp?.nodeId}:
|
|
31602
|
+
${validPaths}` : "";
|
|
31270
31603
|
throw new WorkflowInputError(
|
|
31271
|
-
`Invalid override path: '${
|
|
31604
|
+
`Invalid override path: '${rawKey}'. The resolved path '${key}' failed at segment '${part}'.${validPathSuffix}`,
|
|
31272
31605
|
"overrides"
|
|
31273
31606
|
);
|
|
31274
31607
|
}
|
|
31275
31608
|
current = next;
|
|
31276
31609
|
}
|
|
31610
|
+
if (parts.length === 3 && parts[1] === INPUT_SEGMENT && typeof parts[0] === "string" && typeof parts[2] === "string") {
|
|
31611
|
+
const inputDefinition = getInputDefinition(
|
|
31612
|
+
options.objectInfo,
|
|
31613
|
+
prompt,
|
|
31614
|
+
parts[0],
|
|
31615
|
+
parts[2]
|
|
31616
|
+
);
|
|
31617
|
+
validateOverrideValueType(key, value, inputDefinition);
|
|
31618
|
+
}
|
|
31277
31619
|
current[parts[parts.length - 1]] = value;
|
|
31278
31620
|
}
|
|
31279
31621
|
}
|
|
@@ -31289,13 +31631,12 @@ var DiscoverWorkflowsSchema = paginationSchema.extend({
|
|
|
31289
31631
|
directory_path: z3.string().optional().describe("Custom directory path to scan for workflows.")
|
|
31290
31632
|
});
|
|
31291
31633
|
var WorkflowRunSchema = z3.object({
|
|
31634
|
+
workflow: z3.unknown().optional().describe("Workflow JSON object to execute immediately."),
|
|
31292
31635
|
workflow_file_path: z3.string().optional().describe(
|
|
31293
31636
|
"Path or filename of workflow JSON. Scans default dirs if simple name provided."
|
|
31294
31637
|
),
|
|
31295
31638
|
workflow_id: z3.string().optional().describe("Workflow identifier returned by discover_workflows."),
|
|
31296
|
-
overrides:
|
|
31297
|
-
'Key-value pairs to override using dot-notation. e.g. {"6.inputs.text": "dragon"}'
|
|
31298
|
-
),
|
|
31639
|
+
overrides: OverridesSchema.optional(),
|
|
31299
31640
|
await: z3.boolean().default(true).describe("Wait for completion and return native images."),
|
|
31300
31641
|
timeout: z3.number().int().min(1).default(120).describe("Wait timeout in seconds.")
|
|
31301
31642
|
});
|
|
@@ -31308,7 +31649,7 @@ var SaveWorkflowSchema = z3.object({
|
|
|
31308
31649
|
workflow: z3.unknown().optional().describe("Workflow JSON object to save locally."),
|
|
31309
31650
|
workflow_file_path: z3.string().optional().describe("Existing workflow path or filename to save."),
|
|
31310
31651
|
workflow_id: z3.string().optional().describe("Existing workflow identifier to save."),
|
|
31311
|
-
overrides:
|
|
31652
|
+
overrides: OverridesSchema.optional(),
|
|
31312
31653
|
output_file_name: z3.string().optional().describe("Filename to write locally. (default: anchored to ./)"),
|
|
31313
31654
|
overwrite: z3.boolean().default(false).describe("Replace an existing local file when true."),
|
|
31314
31655
|
include_prompt: z3.boolean().default(false).describe("Include saved API JSON in structuredContent.")
|
|
@@ -31552,21 +31893,43 @@ async function handleWaitForWorkflow(args) {
|
|
|
31552
31893
|
}
|
|
31553
31894
|
async function handleGetWorkflow(args) {
|
|
31554
31895
|
try {
|
|
31555
|
-
const
|
|
31556
|
-
const
|
|
31896
|
+
const objectInfoAvailability = await tryGetObjectInfo();
|
|
31897
|
+
const loadedWorkflow = await loadWorkflow(
|
|
31898
|
+
args,
|
|
31899
|
+
objectInfoAvailability.objectInfo
|
|
31900
|
+
);
|
|
31901
|
+
const editableInputs = analyzeEditableInputs(
|
|
31902
|
+
loadedWorkflow.prompt,
|
|
31903
|
+
objectInfoAvailability.objectInfo,
|
|
31904
|
+
loadedWorkflow.widgetPathMap
|
|
31905
|
+
);
|
|
31906
|
+
const overrideExamples = buildOverrideExamples(editableInputs);
|
|
31557
31907
|
const workflowId = getWorkflowId(loadedWorkflow.targetPath);
|
|
31558
|
-
const
|
|
31908
|
+
const textSections = [
|
|
31559
31909
|
`Workflow ${workflowId} loaded from ${loadedWorkflow.targetPath}.`,
|
|
31560
|
-
loadedWorkflow.sourceFormat === "webui" ? "Source format: Web UI (normalized to API format)." : "Source format: API format."
|
|
31910
|
+
loadedWorkflow.sourceFormat === "webui" ? "Source format: Web UI (normalized to API format)." : "Source format: API format."
|
|
31911
|
+
];
|
|
31912
|
+
if (objectInfoAvailability.warning) {
|
|
31913
|
+
textSections.push(objectInfoAvailability.warning);
|
|
31914
|
+
}
|
|
31915
|
+
textSections.push(
|
|
31561
31916
|
`Editable inputs (${editableInputs.length}):`,
|
|
31562
|
-
formatEditableInputsText(editableInputs)
|
|
31563
|
-
|
|
31917
|
+
formatEditableInputsText(editableInputs),
|
|
31918
|
+
"Copy-ready override example:",
|
|
31919
|
+
formatOverrideExamplesText(overrideExamples.examples)
|
|
31920
|
+
);
|
|
31921
|
+
const text = textSections.join("\n");
|
|
31564
31922
|
const structuredContent = {
|
|
31565
31923
|
workflow_id: workflowId,
|
|
31566
31924
|
path: loadedWorkflow.targetPath,
|
|
31567
31925
|
source_format: loadedWorkflow.sourceFormat,
|
|
31568
|
-
editable_inputs: editableInputs
|
|
31926
|
+
editable_inputs: editableInputs,
|
|
31927
|
+
override_examples: overrideExamples.examples,
|
|
31928
|
+
preferred_override_paths: overrideExamples.preferred_paths
|
|
31569
31929
|
};
|
|
31930
|
+
if (objectInfoAvailability.warning) {
|
|
31931
|
+
structuredContent.warnings = [objectInfoAvailability.warning];
|
|
31932
|
+
}
|
|
31570
31933
|
if (args.include_prompt) {
|
|
31571
31934
|
structuredContent.prompt = loadedWorkflow.prompt;
|
|
31572
31935
|
}
|
|
@@ -31581,10 +31944,17 @@ async function handleGetWorkflow(args) {
|
|
|
31581
31944
|
async function handleSaveWorkflow(args) {
|
|
31582
31945
|
try {
|
|
31583
31946
|
let prompt;
|
|
31947
|
+
let widgetPathMap = {};
|
|
31584
31948
|
let sourcePath;
|
|
31949
|
+
const parsedOverrides = parseOverrides(args.overrides);
|
|
31950
|
+
const objectInfoAvailability = await tryGetObjectInfo();
|
|
31585
31951
|
if (args.workflow !== void 0) {
|
|
31586
|
-
const normalized = await normalizeWorkflowPrompt(
|
|
31952
|
+
const normalized = await normalizeWorkflowPrompt(
|
|
31953
|
+
args.workflow,
|
|
31954
|
+
objectInfoAvailability.objectInfo
|
|
31955
|
+
);
|
|
31587
31956
|
prompt = normalized.prompt;
|
|
31957
|
+
widgetPathMap = normalized.widgetPathMap;
|
|
31588
31958
|
} else {
|
|
31589
31959
|
if (!args.workflow_file_path && !args.workflow_id) {
|
|
31590
31960
|
return createValidationError(
|
|
@@ -31592,12 +31962,25 @@ async function handleSaveWorkflow(args) {
|
|
|
31592
31962
|
"Provide workflow JSON or an existing workflow reference."
|
|
31593
31963
|
);
|
|
31594
31964
|
}
|
|
31595
|
-
const loadedWorkflow = await loadWorkflow(
|
|
31965
|
+
const loadedWorkflow = await loadWorkflow(
|
|
31966
|
+
args,
|
|
31967
|
+
objectInfoAvailability.objectInfo
|
|
31968
|
+
);
|
|
31596
31969
|
prompt = loadedWorkflow.prompt;
|
|
31597
31970
|
sourcePath = loadedWorkflow.targetPath;
|
|
31971
|
+
widgetPathMap = loadedWorkflow.widgetPathMap;
|
|
31598
31972
|
}
|
|
31599
|
-
|
|
31600
|
-
|
|
31973
|
+
const editableInputs = analyzeEditableInputs(
|
|
31974
|
+
prompt,
|
|
31975
|
+
objectInfoAvailability.objectInfo,
|
|
31976
|
+
widgetPathMap
|
|
31977
|
+
);
|
|
31978
|
+
const editableIndex = buildEditableInputIndex(editableInputs);
|
|
31979
|
+
if (parsedOverrides) {
|
|
31980
|
+
applyOverrides(prompt, parsedOverrides, {
|
|
31981
|
+
editableIndex,
|
|
31982
|
+
objectInfo: objectInfoAvailability.objectInfo
|
|
31983
|
+
});
|
|
31601
31984
|
}
|
|
31602
31985
|
const outputFileName = deriveSavedWorkflowFileName(
|
|
31603
31986
|
sourcePath,
|
|
@@ -31630,25 +32013,43 @@ async function handleSaveWorkflow(args) {
|
|
|
31630
32013
|
`,
|
|
31631
32014
|
"utf8"
|
|
31632
32015
|
);
|
|
31633
|
-
const
|
|
32016
|
+
const updatedEditableInputs = analyzeEditableInputs(
|
|
32017
|
+
prompt,
|
|
32018
|
+
objectInfoAvailability.objectInfo,
|
|
32019
|
+
widgetPathMap
|
|
32020
|
+
);
|
|
32021
|
+
const overrideExamples = buildOverrideExamples(updatedEditableInputs);
|
|
31634
32022
|
const workflowId = getWorkflowId(outputPath);
|
|
31635
32023
|
const structuredContent = {
|
|
31636
32024
|
workflow_id: workflowId,
|
|
31637
32025
|
path: outputPath,
|
|
31638
32026
|
source_path: sourcePath,
|
|
31639
32027
|
overwritten: existedBeforeSave,
|
|
31640
|
-
editable_inputs:
|
|
32028
|
+
editable_inputs: updatedEditableInputs,
|
|
32029
|
+
override_examples: overrideExamples.examples,
|
|
32030
|
+
preferred_override_paths: overrideExamples.preferred_paths
|
|
31641
32031
|
};
|
|
32032
|
+
if (objectInfoAvailability.warning) {
|
|
32033
|
+
structuredContent.warnings = [objectInfoAvailability.warning];
|
|
32034
|
+
}
|
|
31642
32035
|
if (args.include_prompt) {
|
|
31643
32036
|
structuredContent.prompt = prompt;
|
|
31644
32037
|
}
|
|
31645
|
-
const persistedChanges =
|
|
31646
|
-
const
|
|
32038
|
+
const persistedChanges = parsedOverrides && Object.keys(parsedOverrides).length > 0 ? `Persisted ${Object.keys(parsedOverrides).length} override(s).` : "No overrides were applied.";
|
|
32039
|
+
const textSections = [
|
|
31647
32040
|
`Saved workflow ${workflowId} to ${outputPath}.`,
|
|
31648
|
-
persistedChanges
|
|
31649
|
-
|
|
31650
|
-
|
|
31651
|
-
|
|
32041
|
+
persistedChanges
|
|
32042
|
+
];
|
|
32043
|
+
if (objectInfoAvailability.warning) {
|
|
32044
|
+
textSections.push(objectInfoAvailability.warning);
|
|
32045
|
+
}
|
|
32046
|
+
textSections.push(
|
|
32047
|
+
`Editable inputs (${updatedEditableInputs.length}):`,
|
|
32048
|
+
formatEditableInputsText(updatedEditableInputs),
|
|
32049
|
+
"Copy-ready override example:",
|
|
32050
|
+
formatOverrideExamplesText(overrideExamples.examples)
|
|
32051
|
+
);
|
|
32052
|
+
const text = textSections.join("\n");
|
|
31652
32053
|
return {
|
|
31653
32054
|
content: [{ type: "text", text }],
|
|
31654
32055
|
structuredContent
|
|
@@ -31659,10 +32060,43 @@ async function handleSaveWorkflow(args) {
|
|
|
31659
32060
|
}
|
|
31660
32061
|
async function handleWorkflowRun(args) {
|
|
31661
32062
|
try {
|
|
31662
|
-
|
|
32063
|
+
let loadedWorkflow;
|
|
32064
|
+
let objectInfo;
|
|
32065
|
+
if (args.workflow !== void 0) {
|
|
32066
|
+
objectInfo = await getObjectInfo();
|
|
32067
|
+
const normalized = await normalizeWorkflowPrompt(
|
|
32068
|
+
args.workflow,
|
|
32069
|
+
objectInfo
|
|
32070
|
+
);
|
|
32071
|
+
loadedWorkflow = {
|
|
32072
|
+
targetPath: "[inline workflow]",
|
|
32073
|
+
prompt: normalized.prompt,
|
|
32074
|
+
sourceFormat: normalized.sourceFormat,
|
|
32075
|
+
widgetPathMap: normalized.widgetPathMap
|
|
32076
|
+
};
|
|
32077
|
+
} else {
|
|
32078
|
+
if (!args.workflow_file_path && !args.workflow_id) {
|
|
32079
|
+
return createValidationError(
|
|
32080
|
+
"workflow",
|
|
32081
|
+
"Provide workflow JSON or an existing workflow reference."
|
|
32082
|
+
);
|
|
32083
|
+
}
|
|
32084
|
+
objectInfo = await getObjectInfo();
|
|
32085
|
+
loadedWorkflow = await loadWorkflow(args, objectInfo);
|
|
32086
|
+
}
|
|
32087
|
+
const { prompt } = loadedWorkflow;
|
|
31663
32088
|
const promptObj = { ...prompt };
|
|
31664
|
-
|
|
31665
|
-
|
|
32089
|
+
const parsedOverrides = parseOverrides(args.overrides);
|
|
32090
|
+
if (parsedOverrides) {
|
|
32091
|
+
const editableInputs = analyzeEditableInputs(
|
|
32092
|
+
prompt,
|
|
32093
|
+
objectInfo,
|
|
32094
|
+
loadedWorkflow.widgetPathMap
|
|
32095
|
+
);
|
|
32096
|
+
applyOverrides(promptObj, parsedOverrides, {
|
|
32097
|
+
editableIndex: buildEditableInputIndex(editableInputs),
|
|
32098
|
+
objectInfo
|
|
32099
|
+
});
|
|
31666
32100
|
}
|
|
31667
32101
|
const clientId = `mcp-${Date.now()}`;
|
|
31668
32102
|
const result = IS_MOCK ? MOCK_FIXTURES.prompt_response : await fetchComfyJson("/prompt", {
|