comfyui-mcp 0.9.0 → 0.9.2

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/CHANGELOG.md CHANGED
@@ -4,6 +4,45 @@ All notable changes to this project are documented here. This project adheres to
4
4
  [Semantic Versioning](https://semver.org/) and the format follows
5
5
  [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
+ ## Unreleased
8
+
9
+ ### Fixed
10
+
11
+ - **Docker build hang on rate-limited CI (e.g. Glama)** — `npm ci` in the
12
+ Dockerfile no longer runs the `cloudflared` postinstall, which fetches a
13
+ ~40 MB binary from GitHub releases over an `https.get()` call with no
14
+ timeout. On networks where GitHub rate-limits (or otherwise stalls)
15
+ unauthenticated requests, that fetch hung indefinitely and blocked image
16
+ builds. Install scripts are now skipped with `--ignore-scripts` and the
17
+ two native deps we actually need (`better-sqlite3`, `sharp`) are rebuilt
18
+ explicitly. The runtime tunnel helper already downloads the cloudflared
19
+ binary lazily on first use, so no functionality is lost.
20
+
21
+ ### Added
22
+
23
+ - **`get_job_status` cloud-mode coverage** — when `COMFYUI_API_KEY` is set,
24
+ `get_job_status` now dispatches to `cloud-client.getJobStatus` (which calls
25
+ `/api/job/<id>/status`) and maps the cloud
26
+ `{ pending | in_progress | completed | failed }` shape to the existing
27
+ local `JobStatus`. Completed jobs are still enriched from history when
28
+ available; failed jobs surface the cloud's error string via
29
+ `error.exception_message`. Closes part of `comfyui-mcp-eik`.
30
+
31
+ ### Changed
32
+
33
+ - Refined the `CLOUD_UNSUPPORTED` error message surfaced by tools that need
34
+ a direct ComfyUI session (workflow library, memory management, etc.). The
35
+ message no longer leaks the internal `getClient` function name and clearly
36
+ tells the user to unset `COMFYUI_API_KEY` to target a local or remote
37
+ ComfyUI.
38
+ - **Upgraded vitest to ^4.1.0** (dev-only). Clears
39
+ [GHSA-5xrq-8626-4rwp](https://github.com/advisories/GHSA-5xrq-8626-4rwp)
40
+ (Vitest UI server arbitrary file read/exec). Test infrastructure tweaks:
41
+ S3 mock now uses a `function` declaration (vitest 4 invokes mocked
42
+ constructors via `new`) and manager-config fallback tests call
43
+ `vi.clearAllMocks()` explicitly (vitest 4's `restoreAllMocks` no longer
44
+ resets `.mock.calls`). Closes `comfyui-mcp-g6e`.
45
+
7
46
  ## [0.9.0] - 2026-06-01
8
47
 
9
48
  Three deployment modes, slimmer install footprint, and first-class
package/Dockerfile CHANGED
@@ -18,11 +18,22 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
18
18
  python3 make g++ \
19
19
  && rm -rf /var/lib/apt/lists/*
20
20
 
21
- # Install against the lockfile. scripts/ is needed because our (safe) postinstall
22
- # runs during install; copying it first keeps the dependency layer cacheable.
21
+ # Install against the lockfile. scripts/ is copied first so the dep layer stays
22
+ # cacheable when only src/ changes.
23
+ #
24
+ # We pass --ignore-scripts to skip ALL install hooks, then explicitly rebuild
25
+ # the native deps we actually need. Why: the optional `cloudflared` package's
26
+ # postinstall downloads a ~40 MB binary from GitHub releases over an
27
+ # https.get() call with no timeout. On rate-limited CI networks (notably
28
+ # Glama's build sandbox) that request hangs indefinitely and the whole image
29
+ # build stalls. The runtime tunnel helper in src/services/tunnel.ts already
30
+ # downloads the binary lazily on first use, so dropping the install-time
31
+ # fetch is safe. better-sqlite3 + sharp still need their `install` scripts
32
+ # to fetch / build their native bindings, hence the explicit rebuild.
23
33
  COPY package.json package-lock.json ./
24
34
  COPY scripts ./scripts
25
- RUN npm ci
35
+ RUN npm ci --ignore-scripts \
36
+ && npm rebuild better-sqlite3 sharp
26
37
 
27
38
  # Compile TypeScript -> dist/
28
39
  COPY tsconfig.json ./
package/README.md CHANGED
@@ -407,12 +407,24 @@ Generates a comprehensive skill file documenting every node, its inputs/outputs,
407
407
 
408
408
  The server auto-detects your ComfyUI installation and port. Override with environment variables if needed:
409
409
 
410
+ ### Deployment modes
411
+
412
+ `comfyui-mcp` operates in one of three modes, auto-selected from the environment:
413
+
414
+ | Mode | Trigger | Local FS / process tools? |
415
+ |------|---------|----------------------------|
416
+ | **Local** | default | yes |
417
+ | **Remote** | `--comfyui-url` / `COMFYUI_URL` points at a non-loopback host | no — server skips `COMFYUI_PATH` auto-detection so stale local installs can't silently absorb uploads |
418
+ | **Cloud** | `COMFYUI_API_KEY` is set (targets [Comfy Cloud](https://cloud.comfy.org)) | no — HTTP primitives route via `cloud.comfy.org` over `X-API-Key`; WebSocket and local-only tools throw `CLOUD_UNSUPPORTED` |
419
+
410
420
  | Variable | Default | Description |
411
421
  |----------|---------|-------------|
412
- | `COMFYUI_URL` | | Full ComfyUI URL, e.g. `https://comfy.example.com:8443` — overrides `COMFYUI_HOST`/`PORT`/`SSL` and skips auto-detection (use for remote ComfyUI) |
422
+ | `COMFYUI_URL` | | Full ComfyUI URL, e.g. `https://comfy.example.com:8443` — overrides `COMFYUI_HOST`/`PORT`/`SSL` and skips auto-detection. Non-loopback hosts opt into **remote mode**. |
413
423
  | `COMFYUI_HOST` | `127.0.0.1` | ComfyUI server address |
414
424
  | `COMFYUI_PORT` | *(auto-detect)* | ComfyUI server port (tries 8188, then 8000) |
415
- | `COMFYUI_PATH` | *(auto-detect)* | Path to ComfyUI data directory |
425
+ | `COMFYUI_PATH` | *(auto-detect)* | Path to ComfyUI data directory. Auto-detection suppressed in remote/cloud modes. |
426
+ | `COMFYUI_API_KEY` | | Comfy Cloud API key. When set, **cloud mode** is active and the server talks to `cloud.comfy.org`. Never logged. |
427
+ | `COMFYUI_CLOUD_URL` | `https://cloud.comfy.org` | Override the Comfy Cloud endpoint (testing/staging). |
416
428
  | `CIVITAI_API_TOKEN` | | CivitAI API token for model downloads |
417
429
  | `HUGGINGFACE_TOKEN` | | HuggingFace token for higher API rate limits |
418
430
  | `GITHUB_TOKEN` | | GitHub token for skill generation (avoids rate limits) |
@@ -659,6 +671,23 @@ MIT — see [LICENSE](./LICENSE) for details.
659
671
 
660
672
  The full, structured changelog lives in [CHANGELOG.md](./CHANGELOG.md). Recent highlights:
661
673
 
674
+ ### 0.9.0 — 2026-06-01
675
+
676
+ **Comfy Cloud + remote mode + slim install.**
677
+
678
+ - **Comfy Cloud** — set `COMFYUI_API_KEY` to route HTTP-backed primitives to [cloud.comfy.org](https://cloud.comfy.org) with `X-API-Key` auth. Architecture and dispatcher pattern ported with attribution from [@picoSols](https://github.com/picoSols)'s `comfyui-cloud-mcp` fork.
679
+ - **Smart-detect remote mode** — `--comfyui-url` at a non-loopback host suppresses `COMFYUI_PATH` auto-detection, closing the root cause of the 0.8.1 upload-fallback bug.
680
+ - **`isCloudMode()` / `isRemoteMode()` / `isLocalMode()`** config helpers + new "Deployment modes" docs section.
681
+ - **Slim install** — seven heavy/feature-gated packages (`@aws-sdk/*`, `@azure/*`, `cloudflared`, `ai`, `@ai-sdk/*`) moved to `optionalDependencies` with lazy dynamic-imports; missing deps now surface `OPTIONAL_DEP_MISSING` with the exact `npm install` hint.
682
+
683
+ ### 0.8.1 — 2026-06-01
684
+
685
+ **Upstream fork picks (with attribution to [@joaolvivas](https://github.com/joaolvivas)).**
686
+
687
+ - **`health_check`** — single-call pre-flight diagnostic (version, GPU/VRAM, queue, per-category `/models` populations, recent `/internal/logs` errors).
688
+ - **`search_custom_nodes` fix** — `api.comfy.org/nodes` was ignoring the `search` param server-side; now fetches a larger window and rank-filters client-side.
689
+ - **`upload_image` / `upload_video` / `upload_audio` HTTP-only** — removed the deceptive filesystem fallback that silently misrouted uploads when `COMFYUI_PATH` auto-detected a stale local install.
690
+
662
691
  ### 0.8.0 — 2026-05-26
663
692
 
664
693
  **Lifecycle + I/O + discovery.**
@@ -103,13 +103,42 @@ A Node HTTP server on `localhost:PORT`:
103
103
 
104
104
  ## 6. Build order
105
105
  0. **v2 authoring skill** (enabler — write the extension correctly).
106
- 1. **Tunnel helper** — port `tunnel-manager` into our server (`startQuickTunnel(port) → url`), behind a flag.
107
- 2. **AI SDK chat endpoint** — `/api/chat` with one server-side tool (`generate_image`) end-to-end.
108
- 3. **Sidebar skeleton** — `defineSidebarTab` + `useChat` hitting the tunnel; render stream.
106
+ 1. **Tunnel helper** — port `tunnel-manager` into our server (`startQuickTunnel(port) → url`), behind a flag. ✅ done (`src/services/tunnel.ts`).
107
+ 2. **AI SDK chat endpoint** — `/api/chat` with one server-side tool (`generate_image`) end-to-end. ✅ done (`src/experimental/{agent-poc,chat-handler}.ts`).
108
+ 3. **Sidebar skeleton** — sidebar tab + chat UI hitting the tunnel; render stream. ✅ done **as a v1 extension** (see §7).
109
109
  4. **Live edit** — one client-side tool (`set_widget_value`) applied via `WidgetHandle`; prove the loop.
110
110
  5. **Wire comfyui-mcp** as the server-side tool surface (MCP client); expand client-side graph tools.
111
111
  6. **Provider switch** (Claude/Codex/Gemini) + connection/key UX + polish into a shippable node pack.
112
112
 
113
+ ## 7. Panel implementation status — v1 now, v2 later
114
+
115
+ `@comfyorg/extension-api` (the v2 package the rest of this doc assumes) is **not yet on npm** as of 2026-06 — PRs #12142–#12145 are still in review and there is no published ETA. We therefore shipped the panel against the **v1 extension API** that every existing ComfyUI extension uses today, and tagged every v1-specific call site `// TODO(v2):` for the upgrade.
116
+
117
+ What lives in the repo now:
118
+
119
+ - `web/extensions/comfyui-mcp-agent-panel/comfyui-mcp-agent-panel.js` — single-file drop-in extension. Vanilla DOM (no framework, no bundler). Registers via `window.app.registerExtension(...)` and mounts a sidebar tab via `app.extensionManager.registerSidebarTab({...})`.
120
+ - `web/extensions/comfyui-mcp-agent-panel/README.md` — install + connection-config instructions; explains backend URL / bearer token settings (stored in `localStorage`).
121
+ - `src/experimental/ui-message-stream-parser.ts` + matching vitest suite — the AI SDK UI message stream consumer (text-start/delta/end, tool-input-available, tool-output-available, finish). The panel JS inlines a byte-equivalent copy of this parser since it ships unbundled.
122
+
123
+ The panel currently implements:
124
+
125
+ - **Connection UX** — paste tunnel URL + bearer token, persisted under `comfyui-mcp.agent-panel.*` localStorage keys.
126
+ - **Chat stream** — POSTs `{ messages: UIMessage[] }` to `<backendUrl>/api/chat`, parses the SSE stream, and renders streaming assistant text plus tool cards for `generate_image` (the POC server-side tool).
127
+ - **Abort on unmount** — the panel cancels any in-flight `fetch` on `destroy()`.
128
+
129
+ ### Migration to v2 (when the package ships)
130
+
131
+ | v1 (today) | v2 (after `@comfyorg/extension-api` is on npm) |
132
+ |------------|-------------------------------------------------|
133
+ | `window.app.registerExtension({ name, setup })` | `defineExtension({ name, setup() { ... } })` |
134
+ | `app.extensionManager.registerSidebarTab({ id, title, icon, type:'custom', render, destroy })` | `defineSidebarTab({ id, title, icon, type:'custom', render, destroy })` |
135
+ | Inlined `parseUiMessageStream` JS in the panel | Real ESM import from `src/experimental/...` once a build step enters the picture |
136
+ | Read `window.app` at module scope | Pure `import` from `@comfyorg/extension-api`; no globals |
137
+
138
+ Cross-reference for the full pattern map: `plugin/skills/comfyui-frontend-extensions/references/migrate-v1-to-v2.md`.
139
+
140
+ Step 4 (live graph edits) is **deferred until v2** unless we decide it's worth writing v1-shim wrappers around `LiteGraph` directly — the v2 `WidgetHandle.setValue` path is much cleaner and the POC works end-to-end without it.
141
+
113
142
  ## References
114
143
  - Ungate (MIT): https://github.com/orchidfiles/ungate — clone at `~/code/ungate`.
115
144
  - `cloudflared` npm: https://www.npmjs.com/package/cloudflared
@@ -10,8 +10,8 @@ import * as cloudClient from "./cloud-client.js";
10
10
  // (picoSols/comfyui-cloud-mcp@7a812069).
11
11
  function requireLocalMode(op) {
12
12
  if (isCloudMode()) {
13
- throw new ComfyUIError(`${op} is not supported in Comfy Cloud mode. ` +
14
- `Unset COMFYUI_API_KEY to target a local/remote ComfyUI.`, "CLOUD_UNSUPPORTED");
13
+ throw new ComfyUIError(`This tool needs a direct ComfyUI session (${op}) and is not available in Comfy Cloud mode. ` +
14
+ `Unset COMFYUI_API_KEY to target a local or remote ComfyUI instance.`, "CLOUD_UNSUPPORTED");
15
15
  }
16
16
  }
17
17
  let clientInstance = null;
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/comfyui/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAGjD,gFAAgF;AAChF,8EAA8E;AAC9E,4EAA4E;AAC5E,iEAAiE;AACjE,yCAAyC;AACzC,SAAS,gBAAgB,CAAC,EAAU;IAClC,IAAI,WAAW,EAAE,EAAE,CAAC;QAClB,MAAM,IAAI,YAAY,CACpB,GAAG,EAAE,yCAAyC;YAC5C,yDAAyD,EAC3D,mBAAmB,CACpB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,IAAI,cAAc,GAAkB,IAAI,CAAC;AAEzC,MAAM,UAAU,SAAS;IACvB,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAC9B,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,cAAc,GAAG,IAAI,MAAM,CAAC;YAC1B,QAAQ,EAAE,iBAAiB,EAAE;YAC7B,GAAG,EAAE,MAAM,CAAC,UAAU;YACtB,QAAQ,EAAE,aAAa;YACvB,qCAAqC;SACtC,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACpC,IAAI,EAAE,iBAAiB,EAAE;SAC1B,CAAC,CAAC;IACL,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,gBAAgB,CAAC,eAAe,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;QACvB,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;QAClD,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,eAAe,CACvB,mCAAmC,iBAAiB,EAAE,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,CACtG,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,gBAAgB,CAAC,iBAAiB,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAE3B,kDAAkD;IAClD,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,wCAAwC;IACxC,MAAM,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;IAC9D,WAAW,EAAE,CAAC;IAEd,IAAI,CAAC;QACH,OAAO,MAAM,aAAa,EAAE,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,mEAAmE;QACnE,MAAM,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;QAClE,WAAW,EAAE,CAAC;QACd,IAAI,CAAC;YACH,OAAO,MAAM,aAAa,EAAE,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,eAAe,CACvB,+CAA+C,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,CAC1F,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,cAAc,EAAE,CAAC;IACvD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC;IAC5C,OAAO,KAA+B,CAAC;AACzC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,aAAa,EAAE,CAAC;IACtD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;IACxC,OAAO,IAA6B,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ;IAC5B,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,QAAQ,EAAE,CAAC;IACjD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,QAAQ,EAA6B,CAAC;IACjE,OAAO;QACL,aAAa,EAAE,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,aAAa,IAAI,EAAE,CAAiC;QAC3F,aAAa,EAAE,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,aAAa,IAAI,EAAE,CAAiC;KAC5F,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,QAAiB;IAC/C,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,MAAM,CAAC,SAAS,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC;AAC3C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAiC,EACjC,SAAmC;IAEnC,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,aAAa,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACzE,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAE3B,wEAAwE;IACxE,4EAA4E;IAC5E,iFAAiF;IACjF,IAAI,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnD,MAAM,GAAG,GAAG,GAAG,kBAAkB,EAAE,MAAM,iBAAiB,EAAE,SAAS,CAAC;QACtE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,MAAM,EAAE,QAAQ;gBAChB,SAAS,EAAE,aAAa;gBACxB,UAAU,EAAE,SAAS;aACtB,CAAC;SACH,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC9C,MAAM,IAAI,eAAe,CACvB,4BAA4B,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAClF,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA2C,CAAC;QAC1E,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,eAAe,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;IACrE,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;IACtD,OAAO;QACL,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,eAAe,EAAE,MAAM,CAAC,SAAS,EAAE,eAAe;KACnD,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,EAAU;IAC9C,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,UAAU,EAAE,CAAC;IACnD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,WAAW,EAAE,CAAC;IACpD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,WAAW,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,aAAa,EAAE,CAAC;IACtD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,aAAa,EAAE,CAAC;AAChC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,cAAc,EAAE,CAAC;IACvD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,WAAW,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ;IAC5B,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,QAAQ,EAAE,CAAC;IACjD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO;IAC3B,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,OAAO,EAAE,CAAC;IAChD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,gBAAgB,EAAE,CAAC;IACzD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,gBAAgB,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,IAAI,cAAc,EAAE,CAAC;QACnB,IAAI,CAAC;YACH,cAAc,CAAC,KAAK,EAAE,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,oDAAoD;QACtD,CAAC;QACD,cAAc,GAAG,IAAI,CAAC;QACtB,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACtC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,cAAc,EAAE,CAAC;IACvD,OAAO,MAAM,CAAC,WAAW,CAAC;AAC5B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO;IAC3B,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,OAAO,EAAE,CAAC;IAChD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAE9B,oEAAoE;IACpE,oDAAoD;IACpD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/B,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,+BAA+B;IACjC,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAC1C,CAAC;AAaD,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,QAAiB;IAEjB,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,YAAY,QAAQ,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;IAC5D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACxC,OAAO,GAAG,CAAC,IAAI,EAA2C,CAAC;AAC7D,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,QAAgB,EAChB,OAAoC,QAAQ,EAC5C,SAAS,GAAG,EAAE;IAEd,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAC5E,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAClE,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,SAAS,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAChE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,CAAC,MAAM,SAAS,QAAQ,GAAG,CAAC,CAAC;IAC5E,CAAC;IACD,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,WAAW,CAAC;IACnE,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAClD,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;IAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC3D,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,IAAY,EACZ,QAAQ,GAAG,WAAW;IAEtB,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,eAAe,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAChF,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IAClD,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACrC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,eAAe,EAAE;QACjD,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,QAAQ;KACf,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,kCAAkC,GAAG,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,EAAgE,CAAC;AAClF,CAAC"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/comfyui/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAGjD,gFAAgF;AAChF,8EAA8E;AAC9E,4EAA4E;AAC5E,iEAAiE;AACjE,yCAAyC;AACzC,SAAS,gBAAgB,CAAC,EAAU;IAClC,IAAI,WAAW,EAAE,EAAE,CAAC;QAClB,MAAM,IAAI,YAAY,CACpB,6CAA6C,EAAE,8CAA8C;YAC3F,qEAAqE,EACvE,mBAAmB,CACpB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,IAAI,cAAc,GAAkB,IAAI,CAAC;AAEzC,MAAM,UAAU,SAAS;IACvB,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAC9B,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,cAAc,GAAG,IAAI,MAAM,CAAC;YAC1B,QAAQ,EAAE,iBAAiB,EAAE;YAC7B,GAAG,EAAE,MAAM,CAAC,UAAU;YACtB,QAAQ,EAAE,aAAa;YACvB,qCAAqC;SACtC,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACpC,IAAI,EAAE,iBAAiB,EAAE;SAC1B,CAAC,CAAC;IACL,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,gBAAgB,CAAC,eAAe,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;QACvB,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;QAClD,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,eAAe,CACvB,mCAAmC,iBAAiB,EAAE,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,CACtG,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,gBAAgB,CAAC,iBAAiB,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAE3B,kDAAkD;IAClD,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,wCAAwC;IACxC,MAAM,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;IAC9D,WAAW,EAAE,CAAC;IAEd,IAAI,CAAC;QACH,OAAO,MAAM,aAAa,EAAE,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,mEAAmE;QACnE,MAAM,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;QAClE,WAAW,EAAE,CAAC;QACd,IAAI,CAAC;YACH,OAAO,MAAM,aAAa,EAAE,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,eAAe,CACvB,+CAA+C,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,CAC1F,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,cAAc,EAAE,CAAC;IACvD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC;IAC5C,OAAO,KAA+B,CAAC;AACzC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,aAAa,EAAE,CAAC;IACtD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;IACxC,OAAO,IAA6B,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ;IAC5B,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,QAAQ,EAAE,CAAC;IACjD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,QAAQ,EAA6B,CAAC;IACjE,OAAO;QACL,aAAa,EAAE,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,aAAa,IAAI,EAAE,CAAiC;QAC3F,aAAa,EAAE,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,aAAa,IAAI,EAAE,CAAiC;KAC5F,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,QAAiB;IAC/C,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,MAAM,CAAC,SAAS,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC;AAC3C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAiC,EACjC,SAAmC;IAEnC,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,aAAa,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACzE,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAE3B,wEAAwE;IACxE,4EAA4E;IAC5E,iFAAiF;IACjF,IAAI,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnD,MAAM,GAAG,GAAG,GAAG,kBAAkB,EAAE,MAAM,iBAAiB,EAAE,SAAS,CAAC;QACtE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,MAAM,EAAE,QAAQ;gBAChB,SAAS,EAAE,aAAa;gBACxB,UAAU,EAAE,SAAS;aACtB,CAAC;SACH,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC9C,MAAM,IAAI,eAAe,CACvB,4BAA4B,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAClF,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA2C,CAAC;QAC1E,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,eAAe,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;IACrE,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;IACtD,OAAO;QACL,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,eAAe,EAAE,MAAM,CAAC,SAAS,EAAE,eAAe;KACnD,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,EAAU;IAC9C,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,UAAU,EAAE,CAAC;IACnD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,WAAW,EAAE,CAAC;IACpD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,WAAW,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,aAAa,EAAE,CAAC;IACtD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,aAAa,EAAE,CAAC;AAChC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,cAAc,EAAE,CAAC;IACvD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,WAAW,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ;IAC5B,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,QAAQ,EAAE,CAAC;IACjD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO;IAC3B,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,OAAO,EAAE,CAAC;IAChD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,gBAAgB,EAAE,CAAC;IACzD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,gBAAgB,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,IAAI,cAAc,EAAE,CAAC;QACnB,IAAI,CAAC;YACH,cAAc,CAAC,KAAK,EAAE,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,oDAAoD;QACtD,CAAC;QACD,cAAc,GAAG,IAAI,CAAC;QACtB,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACtC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,cAAc,EAAE,CAAC;IACvD,OAAO,MAAM,CAAC,WAAW,CAAC;AAC5B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO;IAC3B,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,OAAO,EAAE,CAAC;IAChD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAE9B,oEAAoE;IACpE,oDAAoD;IACpD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/B,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,+BAA+B;IACjC,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAC1C,CAAC;AAaD,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,QAAiB;IAEjB,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,YAAY,QAAQ,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;IAC5D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACxC,OAAO,GAAG,CAAC,IAAI,EAA2C,CAAC;AAC7D,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,QAAgB,EAChB,OAAoC,QAAQ,EAC5C,SAAS,GAAG,EAAE;IAEd,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAC5E,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAClE,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,SAAS,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAChE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,CAAC,MAAM,SAAS,QAAQ,GAAG,CAAC,CAAC;IAC5E,CAAC;IACD,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,WAAW,CAAC;IACnE,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAClD,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;IAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC3D,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,IAAY,EACZ,QAAQ,GAAG,WAAW;IAEtB,IAAI,WAAW,EAAE;QAAE,OAAO,WAAW,CAAC,eAAe,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAChF,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IAClD,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACrC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,eAAe,EAAE;QACjD,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,QAAQ;KACf,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,kCAAkC,GAAG,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,EAAgE,CAAC;AAClF,CAAC"}
@@ -0,0 +1,21 @@
1
+ export interface UiMessageStreamChunk {
2
+ type: string;
3
+ [k: string]: unknown;
4
+ }
5
+ export interface ParseResult {
6
+ chunks: UiMessageStreamChunk[];
7
+ /** Unterminated trailing fragment; pass to the next call. */
8
+ remainder: string;
9
+ /** Whether the `[DONE]` sentinel was seen. */
10
+ done: boolean;
11
+ }
12
+ /**
13
+ * Parse a slice of an AI SDK UI message stream. Stateless — the caller owns
14
+ * the remainder buffer.
15
+ *
16
+ * Frame grammar: events are separated by `\n\n`; each event is one or more
17
+ * `data: <payload>` lines. The terminator is the literal `[DONE]`. We
18
+ * tolerate `\r\n\n` and stray blank lines.
19
+ */
20
+ export declare function parseUiMessageStream(buffer: string): ParseResult;
21
+ //# sourceMappingURL=ui-message-stream-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-message-stream-parser.d.ts","sourceRoot":"","sources":["../../src/experimental/ui-message-stream-parser.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IAIb,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,oBAAoB,EAAE,CAAC;IAC/B,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CA2ChE"}
@@ -0,0 +1,73 @@
1
+ // ---------------------------------------------------------------------------
2
+ // AI SDK UI Message Stream Protocol — consumer-side parser.
3
+ //
4
+ // `POST /api/chat` returns a Server-Sent Events stream whose every `data:`
5
+ // frame is a JSON-encoded `UIMessageChunk` (the AI SDK v6 UI message stream
6
+ // protocol — see `node_modules/ai/dist/index.d.ts` for the full union). The
7
+ // terminator frame is the literal `data: [DONE]`.
8
+ //
9
+ // This module provides a single pure function that turns an arbitrary slice of
10
+ // the raw text body (i.e. however much arrived in the latest `reader.read()`)
11
+ // into:
12
+ // - a list of decoded chunk objects, and
13
+ // - a `remainder` string (an unterminated partial event) the caller must
14
+ // prepend to the NEXT slice before re-invoking.
15
+ //
16
+ // Keeping the parser pure (no state, no DOM, no fetch) makes it cheap to test
17
+ // against canned byte streams in vitest. The exact same logic is *also*
18
+ // inlined into the browser-side panel (web/extensions/...) because that file
19
+ // is dropped directly into ComfyUI's `web/extensions/` directory with no
20
+ // bundler; the two implementations are kept byte-equivalent.
21
+ // ---------------------------------------------------------------------------
22
+ /**
23
+ * Parse a slice of an AI SDK UI message stream. Stateless — the caller owns
24
+ * the remainder buffer.
25
+ *
26
+ * Frame grammar: events are separated by `\n\n`; each event is one or more
27
+ * `data: <payload>` lines. The terminator is the literal `[DONE]`. We
28
+ * tolerate `\r\n\n` and stray blank lines.
29
+ */
30
+ export function parseUiMessageStream(buffer) {
31
+ const chunks = [];
32
+ let done = false;
33
+ // Normalize line endings so the split below works regardless of transport.
34
+ const normalized = buffer.replace(/\r\n/g, "\n");
35
+ // Split on the blank-line frame boundary. The trailing element is whatever
36
+ // is *after* the last `\n\n` — i.e. an unterminated frame in progress.
37
+ const parts = normalized.split("\n\n");
38
+ const remainder = parts.pop() ?? "";
39
+ for (const frame of parts) {
40
+ // A frame can contain multiple `data:` lines (SSE spec joins them with
41
+ // `\n`). In practice AI SDK emits a single line per frame, but we honor
42
+ // the spec to keep the parser interoperable.
43
+ const dataLines = [];
44
+ for (const line of frame.split("\n")) {
45
+ if (!line.startsWith("data:"))
46
+ continue;
47
+ // Strip `data:` then a single optional leading space (per SSE spec).
48
+ let payload = line.slice(5);
49
+ if (payload.startsWith(" "))
50
+ payload = payload.slice(1);
51
+ dataLines.push(payload);
52
+ }
53
+ if (dataLines.length === 0)
54
+ continue;
55
+ const payload = dataLines.join("\n");
56
+ if (payload === "[DONE]") {
57
+ done = true;
58
+ continue;
59
+ }
60
+ try {
61
+ const parsed = JSON.parse(payload);
62
+ if (parsed && typeof parsed === "object" && typeof parsed.type === "string") {
63
+ chunks.push(parsed);
64
+ }
65
+ }
66
+ catch {
67
+ // Malformed frame — skip silently. The AI SDK never emits invalid
68
+ // JSON; this is just defensive for non-conforming proxies.
69
+ }
70
+ }
71
+ return { chunks, remainder, done };
72
+ }
73
+ //# sourceMappingURL=ui-message-stream-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-message-stream-parser.js","sourceRoot":"","sources":["../../src/experimental/ui-message-stream-parser.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,4DAA4D;AAC5D,EAAE;AACF,2EAA2E;AAC3E,4EAA4E;AAC5E,4EAA4E;AAC5E,kDAAkD;AAClD,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,QAAQ;AACR,2CAA2C;AAC3C,2EAA2E;AAC3E,oDAAoD;AACpD,EAAE;AACF,8EAA8E;AAC9E,wEAAwE;AACxE,6EAA6E;AAC7E,yEAAyE;AACzE,6DAA6D;AAC7D,8EAA8E;AAkB9E;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAc;IACjD,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,IAAI,IAAI,GAAG,KAAK,CAAC;IAEjB,2EAA2E;IAC3E,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAEjD,2EAA2E;IAC3E,uEAAuE;IACvE,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;IAEpC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAC1B,uEAAuE;QACvE,wEAAwE;QACxE,6CAA6C;QAC7C,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;gBAAE,SAAS;YACxC,qEAAqE;YACrE,IAAI,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC5B,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACxD,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1B,CAAC;QACD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACrC,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAErC,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;YACzB,IAAI,GAAG,IAAI,CAAC;YACZ,SAAS;QACX,CAAC;QACD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAyB,CAAC;YAC3D,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC5E,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,kEAAkE;YAClE,2DAA2D;QAC7D,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AACrC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"queue-manager.d.ts","sourceRoot":"","sources":["../../src/services/queue-manager.ts"],"names":[],"mappings":"AAUA,OAAO,EAAuB,KAAK,qBAAqB,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAExG,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,YAAY,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,qBAAqB,CAAC;IAC9B,eAAe,CAAC,EAAE,cAAc,CAAC;CAClC;AASD,wBAAsB,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC,CAQ7D;AAED,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,SAAS,CAAC,CAwBpB;AAED,wBAAsB,gBAAgB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGvE;AAED,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGrE;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAGpD"}
1
+ {"version":3,"file":"queue-manager.d.ts","sourceRoot":"","sources":["../../src/services/queue-manager.ts"],"names":[],"mappings":"AAYA,OAAO,EAAuB,KAAK,qBAAqB,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAExG,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,YAAY,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,qBAAqB,CAAC;IAC9B,eAAe,CAAC,EAAE,cAAc,CAAC;CAClC;AASD,wBAAsB,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC,CAQ7D;AA2DD,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,SAAS,CAAC,CA0BpB;AAED,wBAAsB,gBAAgB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGvE;AAED,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGrE;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAGpD"}
@@ -1,4 +1,6 @@
1
1
  import { getClient, getHistory, getQueue as clientGetQueue, interrupt as clientInterrupt, deleteQueueItem as clientDeleteQueueItem, clearQueue as clientClearQueue, } from "../comfyui/client.js";
2
+ import * as cloudClient from "../comfyui/cloud-client.js";
3
+ import { isCloudMode } from "../config.js";
2
4
  import { logger } from "../utils/logger.js";
3
5
  import { analyzeHistoryEntry } from "./job-history.js";
4
6
  function extractJobInfo(items) {
@@ -16,7 +18,65 @@ export async function getQueueSummary() {
16
18
  pending_jobs: extractJobInfo(queue.queue_pending),
17
19
  };
18
20
  }
21
+ async function cloudJobStatus(promptId) {
22
+ // Cloud /api/job/<id>/status returns
23
+ // { status: "pending" | "in_progress" | "completed" | "failed", error?, prompt_id? }
24
+ // Map onto local JobStatus shape so callers don't care about the backend.
25
+ const cloud = await cloudClient.getJobStatus(promptId);
26
+ const done = cloud.status === "completed" || cloud.status === "failed";
27
+ const base = {
28
+ running: cloud.status === "in_progress",
29
+ pending: cloud.status === "pending",
30
+ done,
31
+ status_str: cloud.status,
32
+ };
33
+ if (!done)
34
+ return base;
35
+ // Try to enrich completed jobs from /api/history_v2/<id>; if that fails,
36
+ // fall back to the bare cloud status (with the error message if present).
37
+ try {
38
+ const history = await getHistory(promptId);
39
+ const entry = history[promptId];
40
+ if (!entry) {
41
+ return cloud.error
42
+ ? {
43
+ ...base,
44
+ error: {
45
+ node_id: "",
46
+ node_type: "",
47
+ exception_message: cloud.error,
48
+ },
49
+ }
50
+ : base;
51
+ }
52
+ const analysis = analyzeHistoryEntry(entry);
53
+ return {
54
+ ...base,
55
+ status_str: entry.status?.status_str ?? cloud.status,
56
+ error: analysis.error,
57
+ execution_stats: analysis.execution_stats,
58
+ };
59
+ }
60
+ catch (err) {
61
+ logger.warn("Cloud: could not enrich job status from history", {
62
+ prompt_id: promptId,
63
+ error: err instanceof Error ? err.message : err,
64
+ });
65
+ return cloud.error
66
+ ? {
67
+ ...base,
68
+ error: {
69
+ node_id: "",
70
+ node_type: "",
71
+ exception_message: cloud.error,
72
+ },
73
+ }
74
+ : base;
75
+ }
76
+ }
19
77
  export async function getJobStatus(promptId) {
78
+ if (isCloudMode())
79
+ return cloudJobStatus(promptId);
20
80
  const client = getClient();
21
81
  const status = await client.getPromptStatus(promptId);
22
82
  if (!status.done)
@@ -1 +1 @@
1
- {"version":3,"file":"queue-manager.js","sourceRoot":"","sources":["../../src/services/queue-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,UAAU,EACV,QAAQ,IAAI,cAAc,EAC1B,SAAS,IAAI,eAAe,EAC5B,eAAe,IAAI,qBAAqB,EACxC,UAAU,IAAI,gBAAgB,GAC/B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAmD,MAAM,kBAAkB,CAAC;AAkBxG,SAAS,cAAc,CAAC,KAAkB;IACxC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC1B,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;QACf,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;KACnB,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,KAAK,GAAG,MAAM,cAAc,EAAE,CAAC;IACrC,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,aAAa,CAAC,MAAM;QACnC,OAAO,EAAE,KAAK,CAAC,aAAa,CAAC,MAAM;QACnC,YAAY,EAAE,cAAc,CAAC,KAAK,CAAC,aAAa,CAAC;QACjD,YAAY,EAAE,cAAc,CAAC,KAAK,CAAC,aAAa,CAAC;KAClD,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,QAAgB;IAEhB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;IACtD,IAAI,CAAC,MAAM,CAAC,IAAI;QAAE,OAAO,MAAM,CAAC;IAEhC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChC,IAAI,CAAC,KAAK;YAAE,OAAO,MAAM,CAAC;QAE1B,MAAM,QAAQ,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;QAC5C,OAAO;YACL,GAAG,MAAM;YACT,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,UAAU;YACnC,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,eAAe,EAAE,QAAQ,CAAC,eAAe;SAC1C,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CAAC,0CAA0C,EAAE;YACtD,SAAS,EAAE,QAAQ;YACnB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;SAChD,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,QAAiB;IACtD,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,SAAS,EAAE,QAAQ,IAAI,SAAS,EAAE,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB;IACpD,MAAM,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,gBAAgB,EAAE,CAAC;IACzB,MAAM,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;AACjD,CAAC"}
1
+ {"version":3,"file":"queue-manager.js","sourceRoot":"","sources":["../../src/services/queue-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ # Cloudflare Worker — docs proxy
2
+
3
+ Worker that proxies `https://comfyui-mcp.artokun.io/docs*` to the Mintlify
4
+ deployment at `artokun.mintlify.dev`. Bare-root requests 302 to `/docs`;
5
+ anything else on the host returns 404 (this host is docs-only).
6
+
7
+ - Worker source: `docs-proxy.js`
8
+ - Wrangler config: `wrangler.jsonc`
9
+ - Production URL: <https://comfyui-mcp.artokun.io/docs>
10
+ - Mintlify origin: <https://artokun.mintlify.dev/docs>
11
+
12
+ ## Prerequisites (must all be true for the public URL to work)
13
+
14
+ 1. **DNS record** — `comfyui-mcp` AAAA/A or CNAME record exists on the
15
+ `artokun.io` zone in Cloudflare, **with the proxy (orange cloud) enabled**.
16
+ Without this record the Worker route never fires and clients get NXDOMAIN /
17
+ `curl: (6) Could not resolve host`.
18
+ 2. **Worker deployed** — `comfyui-mcp-docs-proxy` is deployed on the account
19
+ matching `account_id` in `wrangler.jsonc`.
20
+ 3. **Worker route attached** — `comfyui-mcp.artokun.io/*` on `artokun.io` zone
21
+ (declared in `wrangler.jsonc`, applied at deploy time).
22
+ 4. **Mintlify custom domain** — Mintlify project has
23
+ `comfyui-mcp.artokun.io/docs` configured so generated asset/link URLs use
24
+ the `/docs` prefix.
25
+
26
+ ## Deploy
27
+
28
+ ```bash
29
+ cd infra/cloudflare
30
+ npx wrangler deploy
31
+ ```
32
+
33
+ Wrangler must be authenticated against the Cloudflare account that owns the
34
+ `artokun.io` zone (account id `208c358f58d75a3fc684695473f431dd`). Verify with
35
+ `wrangler whoami` — if it shows a different account, run
36
+ `wrangler logout && wrangler login` and pick the right account in the OAuth
37
+ flow.
38
+
39
+ ## Recovery: `comfyui-mcp.artokun.io` returns NXDOMAIN
40
+
41
+ Symptoms:
42
+
43
+ ```text
44
+ $ curl -I https://comfyui-mcp.artokun.io/docs
45
+ curl: (6) Could not resolve host: comfyui-mcp.artokun.io
46
+ $ dig comfyui-mcp.artokun.io @drake.ns.cloudflare.com +short # empty
47
+ ```
48
+
49
+ Cause: the DNS record for the `comfyui-mcp` subdomain has been deleted from
50
+ the `artokun.io` zone, or its proxy was disabled and the underlying target
51
+ is unreachable. The Worker can be deployed and the route attached, but
52
+ without the DNS record the hostname has no IP.
53
+
54
+ Fix (Cloudflare dashboard):
55
+
56
+ 1. Dashboard → `artokun.io` zone → **DNS → Records**.
57
+ 2. Add a record:
58
+ - Type: `AAAA`
59
+ - Name: `comfyui-mcp`
60
+ - IPv6 address: `100::` (RFC 6666 discard prefix — value is irrelevant
61
+ because the request never leaves Cloudflare; the Worker route intercepts
62
+ it). An `A 192.0.2.1` placeholder works equally well.
63
+ - **Proxy status: Proxied (orange cloud)** — this is mandatory.
64
+ - TTL: Auto.
65
+ 3. Save. Propagation on Cloudflare's own resolvers is near-instant.
66
+ 4. Verify:
67
+
68
+ ```bash
69
+ dig comfyui-mcp.artokun.io @drake.ns.cloudflare.com +short # should now return Cloudflare IPs
70
+ curl -I https://comfyui-mcp.artokun.io/docs # HTTP/2 200
71
+ curl -I https://comfyui-mcp.artokun.io/ # HTTP/2 302 → /docs
72
+ ```
73
+
74
+ If `curl` returns 404 from a Cloudflare server, the DNS is fixed but the
75
+ Worker route is missing — re-run `wrangler deploy`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "comfyui-mcp",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "mcpName": "io.github.artokun/comfyui-mcp",
5
5
  "description": "MCP server for ComfyUI — workflow execution, visualization, composition, registry, and skill generation",
6
6
  "homepage": "https://comfyui-mcp.artokun.io/docs",
@@ -49,7 +49,7 @@
49
49
  "cross-env": "^10.1.0",
50
50
  "tsx": "^4.19.2",
51
51
  "typescript": "^5.7.3",
52
- "vitest": "^3.0.5",
52
+ "vitest": "^4.1.8",
53
53
  "zod-to-json-schema": "^3.25.1"
54
54
  },
55
55
  "repository": {
@@ -0,0 +1,97 @@
1
+ # comfyui-mcp Agent Panel — v1 ComfyUI extension
2
+
3
+ A drop-in sidebar tab for ComfyUI that hosts a chat UI talking to the
4
+ [experimental agent backend](../../../src/experimental/agent-poc.ts) shipped
5
+ with `comfyui-mcp`. This is the **v1 implementation** — built against today's
6
+ `app.registerExtension(...)` API. It will be ported to the v2
7
+ `@comfyorg/extension-api` package the moment that ships
8
+ (Comfy-Org PRs #12142–#12145); v1-specific call sites are tagged
9
+ `// TODO(v2):` in the source.
10
+
11
+ ## Install
12
+
13
+ 1. Copy **`comfyui-mcp-agent-panel.js`** (single file, no dependencies) into
14
+ one of these locations — ComfyUI auto-loads any `.js` it finds inside them:
15
+ - `<ComfyUI>/web/extensions/comfyui-mcp-agent-panel.js`, or
16
+ - `<ComfyUI>/custom_nodes/<your-pack>/web/comfyui-mcp-agent-panel.js`.
17
+ 2. Hard-reload the ComfyUI page (`Cmd/Ctrl+Shift+R`).
18
+ 3. A new **Agent** tab (chat icon) appears in the left sidebar.
19
+
20
+ The README and this directory layout exist for repo hygiene only — ComfyUI
21
+ does **not** consume the README.
22
+
23
+ ## Start the backend
24
+
25
+ In a separate terminal at the repo root:
26
+
27
+ ```bash
28
+ COMFYUI_MCP_AGENT_POC=1 \
29
+ COMFYUI_MCP_AGENT_TUNNEL=1 \
30
+ ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY / GOOGLE_GENERATIVE_AI_API_KEY
31
+ npm run dev:agent-poc
32
+ ```
33
+
34
+ The server prints two values you need:
35
+
36
+ - `chat server listening on http://127.0.0.1:8765/api/chat` — the local URL,
37
+ fine if ComfyUI runs on the same machine over `http://`.
38
+ - `public URL: https://<random>.trycloudflare.com` — required if ComfyUI is
39
+ served over HTTPS or on a remote host (browser mixed-content would block a
40
+ plain-`localhost` POST otherwise).
41
+ - `session token: <hex>` — the bearer token.
42
+
43
+ ## Configure the panel
44
+
45
+ Open the **Agent** tab → **Connection** section, then paste:
46
+
47
+ - **Backend URL** — `https://<random>.trycloudflare.com` (or
48
+ `http://localhost:8765` for same-machine HTTP setups).
49
+ - **Bearer token** — the hex string from the server stdout.
50
+
51
+ Click **Save** (or just hit **Send** — the panel persists the live input
52
+ values whenever a request is dispatched, so explicit Save is optional).
53
+ Both values persist in `window.localStorage` under
54
+ `comfyui-mcp.agent-panel.backendUrl` / `comfyui-mcp.agent-panel.token`.
55
+
56
+ > **Backend URL forms accepted.** The panel normalizes `…/`, `…/api`, and
57
+ > `…/api/chat` suffixes — paste whatever the server logged.
58
+
59
+ > **Security:** the bearer token can spend on your provider API keys.
60
+ > `localStorage` is per-origin readable by every script on the ComfyUI page —
61
+ > rotate the token (restart the POC) if you suspect leakage.
62
+
63
+ ## What it can do (POC scope)
64
+
65
+ - Stream chat replies from the configured provider (Claude / Codex / Gemini —
66
+ picked server-side via `src/experimental/provider-registry.ts`).
67
+ - Surface a tool card whenever the model calls the POC `generate_image` tool;
68
+ the backend stubs the actual ComfyUI workflow execution and returns a
69
+ placeholder result.
70
+
71
+ Live graph edits (`set_widget_value`, `add_node`, etc.) are **not yet** wired
72
+ — that's step 4 in `design/embedded-agent-panel.md` and depends on either
73
+ the v2 `extension-api` `WidgetHandle` surface or our own v1 shims around
74
+ `LiteGraph`.
75
+
76
+ ## Files
77
+
78
+ - `comfyui-mcp-agent-panel.js` — the entire extension (one file, no build).
79
+ - `README.md` — this file.
80
+
81
+ The SSE parser embedded in the panel JS is byte-equivalent to
82
+ `src/experimental/ui-message-stream-parser.ts` (which has vitest coverage at
83
+ `src/__tests__/experimental/ui-message-stream-parser.test.ts`). Keep them in
84
+ sync if you change either.
85
+
86
+ ## V1 → V2 migration plan
87
+
88
+ When `@comfyorg/extension-api` lands on npm:
89
+
90
+ | v1 (this file) | v2 |
91
+ |----------------|----|
92
+ | `window.app.registerExtension({ name, setup })` | `defineExtension({ name, setup() {} })` |
93
+ | `app.extensionManager.registerSidebarTab({ id, title, icon, type:'custom', render, destroy })` | `defineSidebarTab({ id, title, icon, type:'custom', render, destroy })` |
94
+ | Embedded `parseUiMessageStream` JS | `import { parseUiMessageStream } from '...'` (a real bundler enters the picture) |
95
+
96
+ See `plugin/skills/comfyui-frontend-extensions/references/migrate-v1-to-v2.md`
97
+ for the full mapping.
@@ -0,0 +1,604 @@
1
+ // =============================================================================
2
+ // comfyui-mcp Agent Panel — v1 ComfyUI frontend extension.
3
+ //
4
+ // Registers a sidebar tab inside ComfyUI that hosts a chat UI talking to our
5
+ // experimental backend (src/experimental/agent-poc.ts). Drop this single file
6
+ // into ComfyUI's `web/extensions/` directory (or the user's
7
+ // `ComfyUI/custom_nodes/<pack>/web/` dir) and reload the page.
8
+ //
9
+ // Wire format: the backend's `POST /api/chat` returns an AI SDK v6 UI Message
10
+ // Stream (Server-Sent Events). We parse `text-start`/`text-delta`/`text-end`
11
+ // to append streaming text, and `tool-input-available` /
12
+ // `tool-output-available` to render a tool card.
13
+ //
14
+ // Settings (panel UI; persisted via window.localStorage under
15
+ // `comfyui-mcp.agent-panel.*`):
16
+ // - `backendUrl` — public URL the panel POSTs to (e.g. the cloudflared
17
+ // trycloudflare.com URL or `http://localhost:8765`).
18
+ // - `token` — bearer token printed on the server's stdout.
19
+ // Both are required before the first message can be sent.
20
+ //
21
+ // SECURITY NOTE: localStorage is per-origin readable by any script on the
22
+ // ComfyUI page. The bearer token grants spend on the user's provider keys —
23
+ // don't share workflow JSON containing it, and rotate it (restart the POC) if
24
+ // you suspect leakage.
25
+ //
26
+ // V1→V2 MIGRATION: this file uses `window.app.registerExtension(...)` (v1) and
27
+ // `app.extensionManager.registerSidebarTab(...)`. When the v2 npm package
28
+ // `@comfyorg/extension-api` (PRs #12142–#12145) ships, the equivalent calls
29
+ // are `defineExtension({ setup() { ... } })` + `defineSidebarTab({ id, title,
30
+ // type: 'custom', icon, render, destroy })`. Every v1-specific call below is
31
+ // marked `// TODO(v2):` — see
32
+ // `plugin/skills/comfyui-frontend-extensions/references/migrate-v1-to-v2.md`.
33
+ // =============================================================================
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // AI SDK UI Message Stream parser. Kept byte-equivalent to the TS module at
37
+ // `src/experimental/ui-message-stream-parser.ts` (which has vitest coverage).
38
+ // We can't import that TS module here because this file is loaded directly
39
+ // by ComfyUI's browser with no bundler.
40
+ // ---------------------------------------------------------------------------
41
+ function parseUiMessageStream(buffer) {
42
+ const chunks = [];
43
+ let done = false;
44
+ const normalized = buffer.replace(/\r\n/g, "\n");
45
+ const parts = normalized.split("\n\n");
46
+ const remainder = parts.pop() ?? "";
47
+
48
+ for (const frame of parts) {
49
+ const dataLines = [];
50
+ for (const line of frame.split("\n")) {
51
+ if (!line.startsWith("data:")) continue;
52
+ let payload = line.slice(5);
53
+ if (payload.startsWith(" ")) payload = payload.slice(1);
54
+ dataLines.push(payload);
55
+ }
56
+ if (dataLines.length === 0) continue;
57
+ const payload = dataLines.join("\n");
58
+ if (payload === "[DONE]") {
59
+ done = true;
60
+ continue;
61
+ }
62
+ try {
63
+ const parsed = JSON.parse(payload);
64
+ if (parsed && typeof parsed === "object" && typeof parsed.type === "string") {
65
+ chunks.push(parsed);
66
+ }
67
+ } catch {
68
+ // ignore malformed frames
69
+ }
70
+ }
71
+ return { chunks, remainder, done };
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // localStorage-backed settings (small, sync, plenty for the POC).
76
+ // ---------------------------------------------------------------------------
77
+ const STORAGE_KEY_BACKEND = "comfyui-mcp.agent-panel.backendUrl";
78
+ const STORAGE_KEY_TOKEN = "comfyui-mcp.agent-panel.token";
79
+
80
+ function loadSettings() {
81
+ try {
82
+ return {
83
+ backendUrl: window.localStorage.getItem(STORAGE_KEY_BACKEND) ?? "",
84
+ token: window.localStorage.getItem(STORAGE_KEY_TOKEN) ?? "",
85
+ };
86
+ } catch {
87
+ return { backendUrl: "", token: "" };
88
+ }
89
+ }
90
+
91
+ function saveSettings(s) {
92
+ try {
93
+ window.localStorage.setItem(STORAGE_KEY_BACKEND, s.backendUrl ?? "");
94
+ window.localStorage.setItem(STORAGE_KEY_TOKEN, s.token ?? "");
95
+ } catch {
96
+ // localStorage may be unavailable in private/locked-down browsers; the
97
+ // panel just becomes session-scoped in that case.
98
+ }
99
+ }
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // Tiny id helper for UIMessage ids. The AI SDK accepts any unique string.
103
+ // ---------------------------------------------------------------------------
104
+ function uid() {
105
+ if (typeof crypto !== "undefined" && crypto.randomUUID) return crypto.randomUUID();
106
+ return `m_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
107
+ }
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // Build the panel DOM. Returns { root, destroy } so the host can mount/unmount.
111
+ // ---------------------------------------------------------------------------
112
+ function buildPanel() {
113
+ const root = document.createElement("div");
114
+ root.className = "comfyui-mcp-agent-panel";
115
+ root.style.cssText = `
116
+ display: flex; flex-direction: column; height: 100%;
117
+ padding: 8px; gap: 8px; box-sizing: border-box;
118
+ font: 13px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
119
+ color: var(--input-text, #ddd); background: var(--comfy-menu-bg, #222);
120
+ `;
121
+
122
+ // ---- Settings strip ------------------------------------------------------
123
+ const settingsBox = document.createElement("details");
124
+ settingsBox.style.cssText = "border: 1px solid #444; border-radius: 4px; padding: 6px;";
125
+ const settingsSummary = document.createElement("summary");
126
+ settingsSummary.textContent = "Connection";
127
+ settingsSummary.style.cssText = "cursor: pointer; user-select: none; font-weight: 600;";
128
+ settingsBox.appendChild(settingsSummary);
129
+
130
+ const settings = loadSettings();
131
+ settingsBox.open = !settings.backendUrl || !settings.token;
132
+
133
+ const makeRow = (labelText, type, value, placeholder) => {
134
+ const row = document.createElement("div");
135
+ row.style.cssText = "display: flex; flex-direction: column; gap: 2px; margin-top: 6px;";
136
+ const label = document.createElement("label");
137
+ label.textContent = labelText;
138
+ label.style.cssText = "font-size: 11px; opacity: 0.7;";
139
+ const input = document.createElement("input");
140
+ input.type = type;
141
+ input.value = value;
142
+ input.placeholder = placeholder;
143
+ input.style.cssText = `
144
+ width: 100%; padding: 4px 6px; border: 1px solid #555; border-radius: 3px;
145
+ background: var(--comfy-input-bg, #181818); color: inherit; box-sizing: border-box;
146
+ `;
147
+ row.append(label, input);
148
+ return { row, input };
149
+ };
150
+
151
+ const { row: urlRow, input: urlInput } = makeRow(
152
+ "Backend URL",
153
+ "url",
154
+ settings.backendUrl,
155
+ "https://<random>.trycloudflare.com",
156
+ );
157
+ const { row: tokenRow, input: tokenInput } = makeRow(
158
+ "Bearer token",
159
+ "password",
160
+ settings.token,
161
+ "from server stdout: 'session token: ...'",
162
+ );
163
+
164
+ const saveBtn = document.createElement("button");
165
+ saveBtn.type = "button";
166
+ saveBtn.textContent = "Save";
167
+ saveBtn.style.cssText =
168
+ "margin-top: 8px; padding: 4px 10px; cursor: pointer; align-self: flex-start;";
169
+ saveBtn.addEventListener("click", () => {
170
+ saveSettings({
171
+ backendUrl: urlInput.value.trim(),
172
+ token: tokenInput.value.trim(),
173
+ });
174
+ appendSystem("Connection saved.");
175
+ settingsBox.open = false;
176
+ });
177
+
178
+ settingsBox.append(urlRow, tokenRow, saveBtn);
179
+ root.appendChild(settingsBox);
180
+
181
+ // ---- Message log ---------------------------------------------------------
182
+ const log = document.createElement("div");
183
+ log.style.cssText = `
184
+ flex: 1 1 auto; overflow-y: auto; padding: 6px;
185
+ border: 1px solid #444; border-radius: 4px;
186
+ display: flex; flex-direction: column; gap: 6px;
187
+ `;
188
+ root.appendChild(log);
189
+
190
+ // ---- Input row -----------------------------------------------------------
191
+ const form = document.createElement("form");
192
+ form.style.cssText = "display: flex; gap: 6px;";
193
+ const input = document.createElement("textarea");
194
+ input.placeholder = "Ask the agent... (Enter to send, Shift+Enter for newline)";
195
+ input.rows = 2;
196
+ input.style.cssText = `
197
+ flex: 1; padding: 6px; border: 1px solid #555; border-radius: 3px;
198
+ background: var(--comfy-input-bg, #181818); color: inherit; resize: vertical;
199
+ font: inherit;
200
+ `;
201
+ const sendBtn = document.createElement("button");
202
+ sendBtn.type = "submit";
203
+ sendBtn.textContent = "Send";
204
+ sendBtn.style.cssText = "padding: 6px 12px; cursor: pointer;";
205
+ form.append(input, sendBtn);
206
+ root.appendChild(form);
207
+
208
+ // ---- DOM helpers ---------------------------------------------------------
209
+ const messages = []; // UIMessage[] for /api/chat history.
210
+
211
+ function makeBubble(role) {
212
+ const bubble = document.createElement("div");
213
+ bubble.style.cssText = `
214
+ padding: 6px 8px; border-radius: 4px; max-width: 95%;
215
+ white-space: pre-wrap; word-wrap: break-word;
216
+ `;
217
+ if (role === "user") {
218
+ bubble.style.background = "#2a4d6e";
219
+ bubble.style.alignSelf = "flex-end";
220
+ } else if (role === "system") {
221
+ bubble.style.background = "#3a3a3a";
222
+ bubble.style.fontStyle = "italic";
223
+ bubble.style.opacity = "0.8";
224
+ bubble.style.alignSelf = "center";
225
+ bubble.style.fontSize = "11px";
226
+ } else {
227
+ bubble.style.background = "#333";
228
+ bubble.style.alignSelf = "flex-start";
229
+ }
230
+ log.appendChild(bubble);
231
+ log.scrollTop = log.scrollHeight;
232
+ return bubble;
233
+ }
234
+
235
+ function appendUser(text) {
236
+ const bubble = makeBubble("user");
237
+ bubble.textContent = text;
238
+ }
239
+
240
+ function appendSystem(text) {
241
+ const bubble = makeBubble("system");
242
+ bubble.textContent = text;
243
+ }
244
+
245
+ function appendAssistantStub() {
246
+ const bubble = makeBubble("assistant");
247
+ bubble.dataset.role = "assistant";
248
+ return bubble;
249
+ }
250
+
251
+ function appendToolCard({ toolCallId, toolName, input: toolInput, output }) {
252
+ const card = document.createElement("div");
253
+ card.style.cssText = `
254
+ align-self: flex-start; padding: 6px 8px; border-radius: 4px;
255
+ background: #2c2c2c; border-left: 3px solid #6aa84f;
256
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px;
257
+ max-width: 95%; white-space: pre-wrap; word-wrap: break-word;
258
+ `;
259
+ const head = document.createElement("div");
260
+ head.style.cssText = "font-weight: 600; margin-bottom: 4px;";
261
+ head.textContent = `tool ${toolName ?? "?"} (${toolCallId.slice(0, 8)}…)`;
262
+ card.appendChild(head);
263
+ if (toolInput !== undefined) {
264
+ const inDiv = document.createElement("div");
265
+ inDiv.textContent = `input: ${safeStringify(toolInput)}`;
266
+ card.appendChild(inDiv);
267
+ }
268
+ if (output !== undefined) {
269
+ const outDiv = document.createElement("div");
270
+ outDiv.style.opacity = "0.85";
271
+ outDiv.textContent = `output: ${safeStringify(output)}`;
272
+ card.appendChild(outDiv);
273
+ }
274
+ log.appendChild(card);
275
+ log.scrollTop = log.scrollHeight;
276
+ return card;
277
+ }
278
+
279
+ function safeStringify(v) {
280
+ try {
281
+ return JSON.stringify(v);
282
+ } catch {
283
+ return String(v);
284
+ }
285
+ }
286
+
287
+ // ---- Send pipeline -------------------------------------------------------
288
+ let inFlight = null; // AbortController for the current request.
289
+
290
+ /** Read the live connection settings, preferring the form inputs over storage
291
+ * so that a user who types into Connection and hits Send (without clicking
292
+ * Save first) gets the expected behavior — and we silently persist the
293
+ * values they implicitly approved by sending. */
294
+ function readConnection() {
295
+ const liveUrl = urlInput.value.trim();
296
+ const liveToken = tokenInput.value.trim();
297
+ const stored = loadSettings();
298
+ const backendUrl = liveUrl || stored.backendUrl;
299
+ const token = liveToken || stored.token;
300
+ if (
301
+ backendUrl &&
302
+ token &&
303
+ (backendUrl !== stored.backendUrl || token !== stored.token)
304
+ ) {
305
+ saveSettings({ backendUrl, token });
306
+ }
307
+ return { backendUrl, token };
308
+ }
309
+
310
+ /** Normalize a user-pasted backend URL into a `/api/chat` endpoint.
311
+ * Accepts forms like:
312
+ * https://abc.trycloudflare.com
313
+ * https://abc.trycloudflare.com/
314
+ * https://abc.trycloudflare.com/api
315
+ * https://abc.trycloudflare.com/api/chat
316
+ * and returns the canonical `<origin>/api/chat`. */
317
+ function toChatUrl(raw) {
318
+ // Strip whitespace + trailing slashes.
319
+ let s = raw.trim().replace(/\/+$/, "");
320
+ // Strip a trailing `/api/chat` or `/api` segment if the user copied either.
321
+ s = s.replace(/\/api\/chat$/i, "").replace(/\/api$/i, "");
322
+ return s + "/api/chat";
323
+ }
324
+
325
+ async function sendMessage(text) {
326
+ const { backendUrl, token } = readConnection();
327
+ if (!backendUrl || !token) {
328
+ appendSystem("Set the backend URL and bearer token in the Connection section first.");
329
+ settingsBox.open = true;
330
+ return;
331
+ }
332
+
333
+ const userMsg = {
334
+ id: uid(),
335
+ role: "user",
336
+ parts: [{ type: "text", text }],
337
+ };
338
+ messages.push(userMsg);
339
+ appendUser(text);
340
+
341
+ sendBtn.disabled = true;
342
+ input.disabled = true;
343
+ const assistantBubble = appendAssistantStub();
344
+ let assistantText = "";
345
+ // Track open tool calls so we can fill in their output when it arrives,
346
+ // AND so the assistant message we persist to history includes the
347
+ // dynamic-tool parts the model actually emitted (otherwise multi-turn
348
+ // tool conversations lose the tool context — the model can't see what
349
+ // it called or got back on the next turn).
350
+ const toolCards = new Map();
351
+ const toolParts = new Map(); // toolCallId -> dynamic-tool UIMessagePart
352
+
353
+ // Single chunk-dispatcher; closes over the per-request mutable state.
354
+ // Declared as a `const` so its binding is unambiguously available before
355
+ // the read loop runs (block-scoped function decls in `try` are spec-fuzzy
356
+ // across engines and strict mode).
357
+ const processChunk = (chunk) => {
358
+ switch (chunk.type) {
359
+ case "text-start":
360
+ // Nothing to render; bubble is already in place.
361
+ break;
362
+ case "text-delta":
363
+ if (typeof chunk.delta === "string") {
364
+ assistantText += chunk.delta;
365
+ assistantBubble.textContent = assistantText;
366
+ log.scrollTop = log.scrollHeight;
367
+ }
368
+ break;
369
+ case "text-end":
370
+ break;
371
+ case "tool-input-available": {
372
+ const id = String(chunk.toolCallId ?? uid());
373
+ const toolName = String(chunk.toolName ?? "");
374
+ const card = appendToolCard({
375
+ toolCallId: id,
376
+ toolName,
377
+ input: chunk.input,
378
+ });
379
+ toolCards.set(id, card);
380
+ // Build the assistant-message part so a follow-up turn can see
381
+ // this call. Mirrors the AI SDK's DynamicToolUIPart shape.
382
+ toolParts.set(id, {
383
+ type: "dynamic-tool",
384
+ toolName,
385
+ toolCallId: id,
386
+ state: "input-available",
387
+ input: chunk.input,
388
+ });
389
+ break;
390
+ }
391
+ case "tool-output-available": {
392
+ // The backend POC `generate_image` tool resolves server-side and
393
+ // the result rides this chunk. We just surface the payload in
394
+ // the corresponding card (or open a new one if we missed the
395
+ // input event for some reason). This is the "one tool-execution
396
+ // path end-to-end" required by the build order.
397
+ const id = String(chunk.toolCallId ?? uid());
398
+ let card = toolCards.get(id);
399
+ if (!card) {
400
+ card = appendToolCard({ toolCallId: id, toolName: "(tool)" });
401
+ toolCards.set(id, card);
402
+ }
403
+ const outDiv = document.createElement("div");
404
+ outDiv.style.opacity = "0.85";
405
+ outDiv.textContent = `output: ${safeStringify(chunk.output)}`;
406
+ card.appendChild(outDiv);
407
+ log.scrollTop = log.scrollHeight;
408
+ // Promote the persisted part to output-available so multi-turn
409
+ // history carries the tool result back to the model.
410
+ const prior = toolParts.get(id) ?? {
411
+ type: "dynamic-tool",
412
+ toolName: "(tool)",
413
+ toolCallId: id,
414
+ };
415
+ toolParts.set(id, {
416
+ ...prior,
417
+ state: "output-available",
418
+ output: chunk.output,
419
+ });
420
+ break;
421
+ }
422
+ case "tool-output-error": {
423
+ const id = String(chunk.toolCallId ?? uid());
424
+ const card = toolCards.get(id);
425
+ const errDiv = document.createElement("div");
426
+ errDiv.style.color = "#f08";
427
+ errDiv.textContent = `error: ${chunk.errorText ?? "tool failed"}`;
428
+ (card ?? appendToolCard({ toolCallId: id, toolName: "(tool)" })).appendChild(
429
+ errDiv,
430
+ );
431
+ const prior = toolParts.get(id) ?? {
432
+ type: "dynamic-tool",
433
+ toolName: "(tool)",
434
+ toolCallId: id,
435
+ };
436
+ toolParts.set(id, {
437
+ ...prior,
438
+ state: "output-error",
439
+ errorText: String(chunk.errorText ?? "tool failed"),
440
+ });
441
+ break;
442
+ }
443
+ case "error":
444
+ assistantBubble.textContent = String(chunk.errorText ?? "stream error");
445
+ assistantBubble.style.background = "#5a2828";
446
+ break;
447
+ case "finish":
448
+ // Loop exits on `[DONE]`.
449
+ break;
450
+ default:
451
+ // Unknown chunk types (data-*, reasoning-*, etc.) are silently
452
+ // ignored for the POC.
453
+ break;
454
+ }
455
+ };
456
+
457
+ inFlight = new AbortController();
458
+ try {
459
+ const res = await fetch(toChatUrl(backendUrl), {
460
+ method: "POST",
461
+ headers: {
462
+ "content-type": "application/json",
463
+ authorization: `Bearer ${token}`,
464
+ },
465
+ body: JSON.stringify({ messages }),
466
+ signal: inFlight.signal,
467
+ });
468
+
469
+ if (!res.ok || !res.body) {
470
+ const errText = await res.text().catch(() => "");
471
+ assistantBubble.textContent = `Error ${res.status}: ${errText || res.statusText}`;
472
+ assistantBubble.style.background = "#5a2828";
473
+ return;
474
+ }
475
+
476
+ const reader = res.body.getReader();
477
+ const decoder = new TextDecoder();
478
+ let buffer = "";
479
+ let streamDone = false;
480
+
481
+ while (!streamDone) {
482
+ const { value, done } = await reader.read();
483
+ if (done) {
484
+ // Flush any pending multi-byte UTF-8 bytes the decoder is buffering
485
+ // (a final TCP read could split a multi-byte char in half; without
486
+ // this flush the trailing bytes are silently dropped).
487
+ buffer += decoder.decode();
488
+ const tail = parseUiMessageStream(buffer);
489
+ buffer = tail.remainder;
490
+ if (tail.done) streamDone = true;
491
+ for (const chunk of tail.chunks) processChunk(chunk);
492
+ break;
493
+ }
494
+ buffer += decoder.decode(value, { stream: true });
495
+ const result = parseUiMessageStream(buffer);
496
+ buffer = result.remainder;
497
+ if (result.done) streamDone = true;
498
+
499
+ for (const chunk of result.chunks) processChunk(chunk);
500
+ }
501
+
502
+ // Persist the assistant message so subsequent turns include it. We
503
+ // record any tool parts FIRST (matching the AI SDK's typical part
504
+ // order — tool invocations precede the final text summary) and only
505
+ // emit a message if the model produced any content this turn.
506
+ const parts = [];
507
+ for (const part of toolParts.values()) parts.push(part);
508
+ if (assistantText) parts.push({ type: "text", text: assistantText });
509
+ if (parts.length > 0) {
510
+ messages.push({ id: uid(), role: "assistant", parts });
511
+ }
512
+ } catch (err) {
513
+ if (err && err.name === "AbortError") {
514
+ assistantBubble.textContent += "\n[aborted]";
515
+ } else {
516
+ const msg = err && err.message ? err.message : String(err);
517
+ assistantBubble.textContent = `Request failed: ${msg}`;
518
+ assistantBubble.style.background = "#5a2828";
519
+ }
520
+ } finally {
521
+ inFlight = null;
522
+ sendBtn.disabled = false;
523
+ input.disabled = false;
524
+ input.focus();
525
+ }
526
+ }
527
+
528
+ form.addEventListener("submit", (ev) => {
529
+ ev.preventDefault();
530
+ const text = input.value.trim();
531
+ if (!text || inFlight) return;
532
+ input.value = "";
533
+ void sendMessage(text);
534
+ });
535
+
536
+ input.addEventListener("keydown", (ev) => {
537
+ // Enter sends; Shift+Enter inserts a newline.
538
+ if (ev.key === "Enter" && !ev.shiftKey) {
539
+ ev.preventDefault();
540
+ form.requestSubmit();
541
+ }
542
+ });
543
+
544
+ return {
545
+ root,
546
+ destroy() {
547
+ try {
548
+ inFlight?.abort();
549
+ } catch {}
550
+ root.remove();
551
+ },
552
+ };
553
+ }
554
+
555
+ // ---------------------------------------------------------------------------
556
+ // v1 registration. We reach for `window.app` lazily — at module-eval time
557
+ // `app` may not yet be on `window`, but `registerExtension` itself queues.
558
+ // ---------------------------------------------------------------------------
559
+ const app = window.app ?? globalThis.app;
560
+ if (!app || typeof app.registerExtension !== "function") {
561
+ console.error(
562
+ "[comfyui-mcp] window.app.registerExtension is unavailable. " +
563
+ "This extension targets the v1 ComfyUI frontend API.",
564
+ );
565
+ } else {
566
+ // TODO(v2): replace with `defineExtension({ name, setup() {...} })`.
567
+ app.registerExtension({
568
+ name: "comfyui-mcp.agent-panel",
569
+ async setup() {
570
+ const tabId = "comfyui-mcp.agent";
571
+ let mounted = null; // { root, destroy }
572
+
573
+ const tabSpec = {
574
+ id: tabId,
575
+ title: "Agent",
576
+ // ComfyUI ships PrimeIcons; `pi-comments` is the closest "chat" glyph.
577
+ icon: "pi pi-comments",
578
+ tooltip: "comfyui-mcp Agent",
579
+ type: "custom",
580
+ render: (container) => {
581
+ if (mounted) mounted.destroy();
582
+ mounted = buildPanel();
583
+ container.appendChild(mounted.root);
584
+ },
585
+ destroy: () => {
586
+ mounted?.destroy();
587
+ mounted = null;
588
+ },
589
+ };
590
+
591
+ // TODO(v2): replace with `defineSidebarTab({ id, title, type: 'custom',
592
+ // icon, render, destroy })` imported from '@comfyorg/extension-api'.
593
+ const mgr = app.extensionManager;
594
+ if (mgr && typeof mgr.registerSidebarTab === "function") {
595
+ mgr.registerSidebarTab(tabSpec);
596
+ } else {
597
+ console.error(
598
+ "[comfyui-mcp] app.extensionManager.registerSidebarTab is unavailable; " +
599
+ "the agent panel cannot mount. Update ComfyUI to a version that exposes the extension manager.",
600
+ );
601
+ }
602
+ },
603
+ });
604
+ }