-
Notifications
You must be signed in to change notification settings - Fork 149
feat(generate): --emit-ops writes a frontend-format workflow plus a stamped op batch (BE-11131) #840
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat(generate): --emit-ops writes a frontend-format workflow plus a stamped op batch (BE-11131) #840
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -284,7 +284,19 @@ def _generate_entry( | |
|
|
||
| def _separate_meta_flags(extra_args: list[str]) -> tuple[list[str], dict[str, str | bool]]: | ||
| """Pull run-level flags out of the user's argv tail.""" | ||
| meta_names = {"download", "async", "json", "timeout", "api-key", "emit-workflow", "output-prefix", "yes"} | ||
| meta_names = { | ||
| "download", | ||
| "async", | ||
| "json", | ||
| "timeout", | ||
| "api-key", | ||
| "emit-workflow", | ||
| "emit-ops", | ||
| "actor", | ||
| "base-version", | ||
| "output-prefix", | ||
| "yes", | ||
| } | ||
| meta: dict[str, str | bool] = {} | ||
| remaining: list[str] = [] | ||
| i = 0 | ||
|
|
@@ -296,7 +308,7 @@ def _separate_meta_flags(extra_args: list[str]) -> tuple[list[str], dict[str, st | |
| if "=" in body: | ||
| body, raw = body.split("=", 1) | ||
| if body in meta_names: | ||
| if body in {"async", "json", "yes"}: | ||
| if body in {"async", "json", "yes", "emit-ops"}: | ||
| meta[body] = True if raw is None else raw.lower() not in {"false", "0", "no"} | ||
| i += 1 | ||
| continue | ||
|
|
@@ -564,14 +576,53 @@ def _track_error(error_kind: str, exc: BaseException) -> None: | |
| hint=f"Run `comfy generate schema {name}` for the full parameter list.", | ||
| ) | ||
|
|
||
| emit_ops_mode = bool(meta.get("emit-ops", False)) | ||
| if emit_ops_mode and not emit_path: | ||
| get_renderer().error( | ||
| code="generate_bad_args", | ||
| message="--emit-ops requires --emit-workflow <path>: the op batch describes the workflow written there", | ||
| hint="add --emit-workflow workflow.json", | ||
| ) | ||
| raise typer.Exit(code=1) | ||
| if emit_path: | ||
| # Emit a runnable workflow that drives the partner *node* and return | ||
| # — no proxy call, no API key required. The artifact is the result. | ||
| name = gen_props["model_alias"] or ep.id | ||
| prefix = meta.get("output-prefix") if isinstance(meta.get("output-prefix"), str) else "generate" | ||
| renderer = get_renderer() | ||
| ops: list | None = None | ||
| try: | ||
| workflow = emit.write_workflow(name, values, Path(emit_path).expanduser(), output_prefix=prefix) | ||
| if emit_ops_mode: | ||
| # FRONTEND-format file + a stamped replace_ops batch, so the | ||
| # written graph is canvas-editable and a shared-document | ||
| # consumer folds it in as attributed ops instead of a | ||
| # wholesale replacement — same contract as | ||
| # `templates fetch --emit-ops`. The graph loads through the | ||
| # same resilient path every workflow edit verb uses | ||
| # (COMFY_OBJECT_INFO_FILE honored, cache fallback). | ||
| from comfy_cli.command.workflow import _get_graph | ||
|
|
||
| actor = meta.get("actor") if isinstance(meta.get("actor"), str) else "cli" | ||
| try: | ||
| base_version = int(meta.get("base-version", 0)) | ||
| except (TypeError, ValueError): | ||
| renderer.error( | ||
| code="generate_bad_args", | ||
| message=f"--base-version must be an integer, got {meta.get('base-version')!r}", | ||
| ) | ||
| raise typer.Exit(code=1) from None | ||
| graph = _get_graph(None, None, None) | ||
| workflow, ops = emit.write_frontend_workflow( | ||
| name, | ||
| values, | ||
|
Comment on lines
+607
to
+617
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Route invalid When 🤖 Prompt for AI Agents |
||
| Path(emit_path).expanduser(), | ||
| graph, | ||
| actor=actor, | ||
| base_version=base_version, | ||
| output_prefix=prefix, | ||
| ) | ||
| else: | ||
| workflow = emit.write_workflow(name, values, Path(emit_path).expanduser(), output_prefix=prefix) | ||
| except emit.UnsupportedModelError as e: | ||
| # Its own code: the remedy is "pick another model", which is | ||
| # not what the umbrella `emit_workflow_failed` hint says, and | ||
|
|
@@ -600,14 +651,20 @@ def _track_error(error_kind: str, exc: BaseException) -> None: | |
| hint=hint, | ||
| ) | ||
| raise typer.Exit(code=1) from e | ||
| tracking.track_event("generate:emit", {**gen_props, "node_count": len(workflow)}) | ||
| node_count = len(workflow["nodes"]) if emit_ops_mode else len(workflow) | ||
| tracking.track_event("generate:emit", {**gen_props, "node_count": node_count}) | ||
| if renderer.is_pretty(): | ||
| rprint(f"[bold green]Wrote workflow:[/bold green] {emit_path}") | ||
| rprint(f" run it: comfy run --workflow {emit_path}") | ||
| renderer.emit( | ||
| {"out": str(Path(emit_path).expanduser()), "model": name, "nodes": len(workflow)}, | ||
| command="generate emit-workflow", | ||
| ) | ||
| payload = { | ||
| "out": str(Path(emit_path).expanduser()), | ||
| "model": name, | ||
| "nodes": node_count, | ||
| "format": "frontend" if emit_ops_mode else "api", | ||
| } | ||
| if ops is not None: | ||
| payload["ops"] = ops | ||
| renderer.emit(payload, command="generate emit-workflow") | ||
| return | ||
|
|
||
| # Spend gate — a proxy call spends Comfy credits, so consent comes | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Track
_get_graphexits before re-raising.When
comfy_cli.command.workflow._get_graphcatchesLoadError, it emitscql_no_graphand raisestyper.Exit. The emit block and outertyper.Exithandler only re-raise it, so nogenerate:errorfollowsgenerate:start. Catch this exit around the call, invoke_track_error("emit", exc), then re-raise so the event trail has a terminal tail.🤖 Prompt for AI Agents