comfyui-mcp 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/README.md +12 -0
- package/ROADMAP.md +15 -0
- package/dist/services/download-auth.d.ts +7 -0
- package/dist/services/download-auth.d.ts.map +1 -1
- package/dist/services/download-auth.js +26 -1
- package/dist/services/download-auth.js.map +1 -1
- package/dist/services/download-cache.d.ts +3 -1
- package/dist/services/download-cache.d.ts.map +1 -1
- package/dist/services/download-cache.js +45 -9
- package/dist/services/download-cache.js.map +1 -1
- package/dist/services/image-convert.d.ts +29 -0
- package/dist/services/image-convert.d.ts.map +1 -0
- package/dist/services/image-convert.js +218 -0
- package/dist/services/image-convert.js.map +1 -0
- package/dist/services/manifest.d.ts +63 -0
- package/dist/services/manifest.d.ts.map +1 -0
- package/dist/services/manifest.js +333 -0
- package/dist/services/manifest.js.map +1 -0
- package/dist/services/model-resolver.d.ts.map +1 -1
- package/dist/services/model-resolver.js +7 -1
- package/dist/services/model-resolver.js.map +1 -1
- package/dist/services/node-authoring.d.ts +5 -0
- package/dist/services/node-authoring.d.ts.map +1 -1
- package/dist/services/node-authoring.js +56 -0
- package/dist/services/node-authoring.js.map +1 -1
- package/dist/services/node-verify.d.ts +35 -0
- package/dist/services/node-verify.d.ts.map +1 -0
- package/dist/services/node-verify.js +138 -0
- package/dist/services/node-verify.js.map +1 -0
- package/dist/services/skill-cache.d.ts +26 -0
- package/dist/services/skill-cache.d.ts.map +1 -0
- package/dist/services/skill-cache.js +172 -0
- package/dist/services/skill-cache.js.map +1 -0
- package/dist/services/storage/azure-blob.d.ts +8 -0
- package/dist/services/storage/azure-blob.d.ts.map +1 -0
- package/dist/services/storage/azure-blob.js +146 -0
- package/dist/services/storage/azure-blob.js.map +1 -0
- package/dist/services/storage/hf.d.ts +7 -0
- package/dist/services/storage/hf.d.ts.map +1 -0
- package/dist/services/storage/hf.js +80 -0
- package/dist/services/storage/hf.js.map +1 -0
- package/dist/services/storage/http.d.ts +6 -0
- package/dist/services/storage/http.d.ts.map +1 -0
- package/dist/services/storage/http.js +41 -0
- package/dist/services/storage/http.js.map +1 -0
- package/dist/services/storage/index.d.ts +28 -0
- package/dist/services/storage/index.d.ts.map +1 -0
- package/dist/services/storage/index.js +42 -0
- package/dist/services/storage/index.js.map +1 -0
- package/dist/services/storage/s3.d.ts +12 -0
- package/dist/services/storage/s3.d.ts.map +1 -0
- package/dist/services/storage/s3.js +84 -0
- package/dist/services/storage/s3.js.map +1 -0
- package/dist/services/storage/types.d.ts +22 -0
- package/dist/services/storage/types.d.ts.map +1 -0
- package/dist/services/storage/types.js +2 -0
- package/dist/services/storage/types.js.map +1 -0
- package/dist/services/storage/utils.d.ts +6 -0
- package/dist/services/storage/utils.d.ts.map +1 -0
- package/dist/services/storage/utils.js +39 -0
- package/dist/services/storage/utils.js.map +1 -0
- package/dist/services/storage-upload.d.ts +18 -0
- package/dist/services/storage-upload.d.ts.map +1 -0
- package/dist/services/storage-upload.js +121 -0
- package/dist/services/storage-upload.js.map +1 -0
- package/dist/tools/image-convert.d.ts +3 -0
- package/dist/tools/image-convert.d.ts.map +1 -0
- package/dist/tools/image-convert.js +58 -0
- package/dist/tools/image-convert.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +8 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/manifest.d.ts +3 -0
- package/dist/tools/manifest.d.ts.map +1 -0
- package/dist/tools/manifest.js +25 -0
- package/dist/tools/manifest.js.map +1 -0
- package/dist/tools/model-management.d.ts.map +1 -1
- package/dist/tools/model-management.js +9 -1
- package/dist/tools/model-management.js.map +1 -1
- package/dist/tools/node-authoring.d.ts.map +1 -1
- package/dist/tools/node-authoring.js +8 -2
- package/dist/tools/node-authoring.js.map +1 -1
- package/dist/tools/node-verify.d.ts +3 -0
- package/dist/tools/node-verify.d.ts.map +1 -0
- package/dist/tools/node-verify.js +42 -0
- package/dist/tools/node-verify.js.map +1 -0
- package/dist/tools/skill-generator.d.ts.map +1 -1
- package/dist/tools/skill-generator.js +16 -3
- package/dist/tools/skill-generator.js.map +1 -1
- package/dist/tools/storage-upload.d.ts +3 -0
- package/dist/tools/storage-upload.d.ts.map +1 -0
- package/dist/tools/storage-upload.js +57 -0
- package/dist/tools/storage-upload.js.map +1 -0
- package/package.json +5 -1
- package/scripts/gen-tool-docs.ts +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,57 @@ 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
|
+
## [0.8.0] - 2026-05-26
|
|
8
|
+
|
|
9
|
+
Completes the custom-node authoring lifecycle, adds cloud storage I/O and
|
|
10
|
+
declarative setup, and adds node discovery — all built and reviewed in a
|
|
11
|
+
codex implement→review→fix loop.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`apply_manifest`** — declarative environment setup from an inline object or
|
|
16
|
+
a JSON/YAML manifest: `pip` packages, `custom_nodes` (registry ids or git URLs
|
|
17
|
+
with `@ref`), and `models`. Idempotent, per-item structured report; `apt`
|
|
18
|
+
entries are accepted but skipped (manual/root). Local-only.
|
|
19
|
+
- **`verify_custom_node`** — the "test" step of the author loop: restarts ComfyUI
|
|
20
|
+
(with a bounded readiness wait) and confirms a pack's `NODE_CLASS_MAPPINGS`
|
|
21
|
+
class_types registered in `/object_info` (a failed import simply never appears).
|
|
22
|
+
- **`scaffold_custom_node`** now also emits `.comfyignore`/`.gitignore` and, with
|
|
23
|
+
`with_ci`, a `.github/workflows/publish_action.yml` (Comfy-Org/publish-node-action).
|
|
24
|
+
- **`convert_image`** — re-encode a generated image (by `asset_id` or output-dir
|
|
25
|
+
path) to PNG/JPEG/WebP via `sharp`; returns inline base64 + optional file write
|
|
26
|
+
(output-dir confined), and reports bytes saved.
|
|
27
|
+
- **Cloud storage** — model downloads may be `s3://` or Azure Blob URLs
|
|
28
|
+
(`download_model` gains `s3` auth); new **`upload_output`** pushes a generated
|
|
29
|
+
output to S3 / Azure / HTTP / Hugging Face and returns URL(s).
|
|
30
|
+
- **`download_model` `auth`** — per-request `bearer`/`basic`/`header`/`query`
|
|
31
|
+
authentication for gated/private hosts (carried over and extended).
|
|
32
|
+
- **`comfy-researcher` agent** — turns a problem statement into ranked custom-node
|
|
33
|
+
pack recommendations (searches the Registry, evaluates, delegates deep dives to
|
|
34
|
+
`comfy-explorer`).
|
|
35
|
+
- **Cached `generate_node_skill`** — read-through cache keyed by source@version
|
|
36
|
+
(`COMFYUI_SKILL_CACHE_DIR`; `refresh` to bypass), so repeat analyses are instant.
|
|
37
|
+
|
|
38
|
+
### Security
|
|
39
|
+
|
|
40
|
+
- `apply_manifest` rejects pip argv-option injection; realpath/symlink-safe path
|
|
41
|
+
containment for manifest model paths, `convert_image`, and upload sources;
|
|
42
|
+
`convert_image` caps source size + sharp pixels.
|
|
43
|
+
- Cloud storage: Azure SAS / AWS presigned secrets redacted from logs/errors;
|
|
44
|
+
Azure URL-vs-env account mismatch rejected; HF-CLI remote-path argv hardening;
|
|
45
|
+
manual redirect handling (no cross-origin auth replay or upload-redirect SSRF).
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- `generate_node_skill` cache resolves the current pack version before lookup
|
|
50
|
+
(no stale docs served after a pack updates) and writes atomically (temp +
|
|
51
|
+
rename with a content-hash check).
|
|
52
|
+
|
|
53
|
+
### Dependencies
|
|
54
|
+
|
|
55
|
+
- Added `yaml` (manifest parsing), `sharp` (image conversion), `@aws-sdk/client-s3`
|
|
56
|
+
and `@azure/storage-blob` (cloud storage). `npm audit`: 0 high vulnerabilities.
|
|
57
|
+
|
|
7
58
|
## [0.7.0] - 2026-05-25
|
|
8
59
|
|
|
9
60
|
Stability + authoring release: hardens model downloads and the ComfyUI process
|
|
@@ -120,6 +171,7 @@ subprocess fallback where the API can't do the job.
|
|
|
120
171
|
|
|
121
172
|
Earlier releases predate this changelog.
|
|
122
173
|
|
|
174
|
+
[0.8.0]: https://github.com/artokun/comfyui-mcp/releases/tag/v0.8.0
|
|
123
175
|
[0.7.0]: https://github.com/artokun/comfyui-mcp/releases/tag/v0.7.0
|
|
124
176
|
[0.6.1]: https://github.com/artokun/comfyui-mcp/releases/tag/v0.6.1
|
|
125
177
|
[0.6.0]: https://github.com/artokun/comfyui-mcp/releases/tag/v0.6.0
|
package/README.md
CHANGED
|
@@ -658,6 +658,18 @@ MIT — see [LICENSE](./LICENSE) for details.
|
|
|
658
658
|
|
|
659
659
|
The full, structured changelog lives in [CHANGELOG.md](./CHANGELOG.md). Recent highlights:
|
|
660
660
|
|
|
661
|
+
### 0.8.0 — 2026-05-26
|
|
662
|
+
|
|
663
|
+
**Lifecycle + I/O + discovery.**
|
|
664
|
+
|
|
665
|
+
- **`apply_manifest`** — declarative env setup (pip / custom_nodes / models) from an inline or JSON/YAML manifest; idempotent.
|
|
666
|
+
- **`verify_custom_node`** — restart + `/object_info` load-check that a scaffolded/installed pack's node types actually registered.
|
|
667
|
+
- **`scaffold_custom_node`** — now also emits `.comfyignore`/`.gitignore` and (with `with_ci`) a GitHub publish workflow.
|
|
668
|
+
- **`convert_image`** — re-encode outputs to PNG/JPEG/WebP via `sharp`.
|
|
669
|
+
- **Cloud storage** — `s3://` / Azure-Blob model downloads + a new **`upload_output`** (S3/Azure/HTTP/HF).
|
|
670
|
+
- **`comfy-researcher` agent** — problem → ranked custom-node recommendations; **`generate_node_skill`** is now cached (source@version).
|
|
671
|
+
- **Security** — pip/argv-injection guards, realpath/symlink-safe path containment, cloud-credential + SAS/presigned redaction, redirect-SSRF hardening.
|
|
672
|
+
|
|
661
673
|
### 0.7.0 — 2026-05-25
|
|
662
674
|
|
|
663
675
|
**Stability + authoring.**
|
package/ROADMAP.md
CHANGED
|
@@ -10,6 +10,21 @@ Comfy Registry.
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## Status — 2026-05-26
|
|
14
|
+
|
|
15
|
+
- **Released:** `0.7.0` on npm — Theme E stability/hardening (E1–E4, E7, E2-auth), custom-node
|
|
16
|
+
authoring tools, experimental agent-panel backend, hosted docs.
|
|
17
|
+
- **Complete (on main, unreleased → queued for `0.8.0`):** Theme E additive (E5 `apply_manifest`,
|
|
18
|
+
E6/E2b cloud storage, E8 `convert_image`), Theme C (C3 `verify_custom_node`, C5 scaffold CI),
|
|
19
|
+
Theme D (D1 `comfy-researcher` + skill cache). **Epics A, C, D, E are closed.**
|
|
20
|
+
- **Pending release:** cut **`0.8.0`** for the unreleased surface above — `comfyui-mcp-yrp` (see beads).
|
|
21
|
+
- **Blocked:** **Theme B** (embedded agent panel UI, B3–B6) is gated on the upstream
|
|
22
|
+
**`@comfyorg/extension-api`** package being published to npm (PRs #12142–#12145 still open). The
|
|
23
|
+
panel *backend* POC (B1/B2) already shipped. Tracked by a watch bead under Epic B; resume the
|
|
24
|
+
codex build loop on B once the package lands.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
13
28
|
## ✅ Shipped (0.6.x)
|
|
14
29
|
- comfy-cli capability port (custom-node mgmt, snapshots, bisect, workflow deps, install/update,
|
|
15
30
|
models, workspace/env, API nodes, manager config) — tools surface ~70+.
|
|
@@ -13,6 +13,13 @@ export type DownloadAuth = {
|
|
|
13
13
|
type: "query";
|
|
14
14
|
query_param: string;
|
|
15
15
|
query_value: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: "s3";
|
|
18
|
+
access_key_id: string;
|
|
19
|
+
secret_access_key: string;
|
|
20
|
+
session_token?: string;
|
|
21
|
+
region?: string;
|
|
22
|
+
endpoint?: string;
|
|
16
23
|
};
|
|
17
24
|
export interface DownloadRequestAuth {
|
|
18
25
|
url: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download-auth.d.ts","sourceRoot":"","sources":["../../src/services/download-auth.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAC7D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"download-auth.d.ts","sourceRoot":"","sources":["../../src/services/download-auth.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAC7D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAC3D;IACE,IAAI,EAAE,IAAI,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEN,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAyDD,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,oBAAoB,GAAE,MAAM,EAAO,GAClC,MAAM,CAcR;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,YAAY,GAClB,mBAAmB,CAuCrB"}
|
|
@@ -3,6 +3,8 @@ const TOKEN_QUERY_RE = /token|key|secret|signature|auth|password|credential/i;
|
|
|
3
3
|
const REDACTED = "[REDACTED]";
|
|
4
4
|
const HEADER_NAME_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
5
5
|
const ASCII_CONTROL_RE = /[\x00-\x1F\x7F]/;
|
|
6
|
+
const AZURE_BLOB_HOST_SUFFIX = ".blob.core.windows.net";
|
|
7
|
+
const AWS_PRESIGNED_QUERY_RE = /^x-amz-/i;
|
|
6
8
|
function rejectControlChars(label, value) {
|
|
7
9
|
if (ASCII_CONTROL_RE.test(value)) {
|
|
8
10
|
throw new ValidationError(`${label} cannot contain ASCII control characters.`);
|
|
@@ -25,17 +27,34 @@ function validateDownloadAuth(auth) {
|
|
|
25
27
|
rejectControlChars("Header auth header_value", auth.header_value);
|
|
26
28
|
return;
|
|
27
29
|
}
|
|
30
|
+
if (auth.type === "s3") {
|
|
31
|
+
rejectControlChars("S3 access_key_id", auth.access_key_id);
|
|
32
|
+
rejectControlChars("S3 secret_access_key", auth.secret_access_key);
|
|
33
|
+
if (auth.session_token)
|
|
34
|
+
rejectControlChars("S3 session_token", auth.session_token);
|
|
35
|
+
if (auth.region)
|
|
36
|
+
rejectControlChars("S3 region", auth.region);
|
|
37
|
+
if (auth.endpoint)
|
|
38
|
+
rejectControlChars("S3 endpoint", auth.endpoint);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
28
41
|
if (auth.query_param.length === 0) {
|
|
29
42
|
throw new ValidationError("Query auth query_param must be a non-empty string.");
|
|
30
43
|
}
|
|
31
44
|
rejectControlChars("Query auth query_param", auth.query_param);
|
|
32
45
|
}
|
|
46
|
+
function shouldRedactAllQueryValues(parsed) {
|
|
47
|
+
if (parsed.hostname.endsWith(AZURE_BLOB_HOST_SUFFIX))
|
|
48
|
+
return true;
|
|
49
|
+
return [...parsed.searchParams.keys()].some((key) => AWS_PRESIGNED_QUERY_RE.test(key));
|
|
50
|
+
}
|
|
33
51
|
export function redactUrlForLogs(url, extraSensitiveParams = []) {
|
|
34
52
|
try {
|
|
35
53
|
const parsed = new URL(url);
|
|
54
|
+
const redactAll = shouldRedactAllQueryValues(parsed);
|
|
36
55
|
const sensitive = new Set(extraSensitiveParams.map((p) => p.toLowerCase()));
|
|
37
56
|
for (const key of [...parsed.searchParams.keys()]) {
|
|
38
|
-
if (sensitive.has(key.toLowerCase()) || TOKEN_QUERY_RE.test(key)) {
|
|
57
|
+
if (redactAll || sensitive.has(key.toLowerCase()) || TOKEN_QUERY_RE.test(key)) {
|
|
39
58
|
parsed.searchParams.set(key, REDACTED);
|
|
40
59
|
}
|
|
41
60
|
}
|
|
@@ -68,6 +87,12 @@ export function applyDownloadAuth(url, auth) {
|
|
|
68
87
|
headers: { [auth.header_name]: auth.header_value },
|
|
69
88
|
};
|
|
70
89
|
}
|
|
90
|
+
if (auth.type === "s3") {
|
|
91
|
+
return {
|
|
92
|
+
url,
|
|
93
|
+
headers: {},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
71
96
|
const parsed = new URL(url);
|
|
72
97
|
parsed.searchParams.set(auth.query_param, auth.query_value);
|
|
73
98
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download-auth.js","sourceRoot":"","sources":["../../src/services/download-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"download-auth.js","sourceRoot":"","sources":["../../src/services/download-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { copyFile, link, mkdir, readdir, rename, rm, stat, utimes } from "node:fs/promises";
|
|
2
|
+
import { type CloudStorageAuth } from "./storage/index.js";
|
|
2
3
|
export declare const downloadCacheFs: {
|
|
3
4
|
copyFile: typeof copyFile;
|
|
4
5
|
link: typeof link;
|
|
@@ -14,6 +15,7 @@ export interface DownloadCacheOptions {
|
|
|
14
15
|
headers: Record<string, string>;
|
|
15
16
|
targetPath: string;
|
|
16
17
|
logUrl?: string;
|
|
18
|
+
storageAuth?: CloudStorageAuth;
|
|
17
19
|
}
|
|
18
20
|
export interface DownloadCacheResult {
|
|
19
21
|
targetPath: string;
|
|
@@ -21,6 +23,6 @@ export interface DownloadCacheResult {
|
|
|
21
23
|
cachePath?: string;
|
|
22
24
|
materializedBy?: "hardlink" | "copy";
|
|
23
25
|
}
|
|
24
|
-
export declare function downloadUrlToFile(url: string, targetPath: string, headers: Record<string, string>, logUrl?: string): Promise<void>;
|
|
26
|
+
export declare function downloadUrlToFile(url: string, targetPath: string, headers: Record<string, string>, logUrl?: string, storageAuth?: CloudStorageAuth): Promise<void>;
|
|
25
27
|
export declare function downloadWithCache(options: DownloadCacheOptions): Promise<DownloadCacheResult>;
|
|
26
28
|
//# sourceMappingURL=download-cache.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download-cache.d.ts","sourceRoot":"","sources":["../../src/services/download-cache.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,OAAO,EACP,MAAM,EACN,EAAE,EACF,IAAI,EACJ,MAAM,EACP,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"download-cache.d.ts","sourceRoot":"","sources":["../../src/services/download-cache.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,OAAO,EACP,MAAM,EACN,EAAE,EACF,IAAI,EACJ,MAAM,EACP,MAAM,kBAAkB,CAAC;AAQ1B,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAO5B,eAAO,MAAM,eAAe;;;;;;;;;CAS3B,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;CACtC;AAoLD,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,MAAM,CAAC,EAAE,MAAM,EACf,WAAW,GAAE,gBAAqB,GACjC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,mBAAmB,CAAC,CAgC9B"}
|
|
@@ -8,8 +8,10 @@ import { pipeline } from "node:stream/promises";
|
|
|
8
8
|
import { ModelError } from "../utils/errors.js";
|
|
9
9
|
import { logger } from "../utils/logger.js";
|
|
10
10
|
import { redactUrlForLogs } from "./download-auth.js";
|
|
11
|
+
import { downloadCloudUrlToFile, supportsCloudDownload, } from "./storage/index.js";
|
|
11
12
|
const DEFAULT_CACHE_DIR = join(homedir(), ".comfyui-mcp", "cache");
|
|
12
13
|
const HASH_CHARS = 32;
|
|
14
|
+
const MAX_HTTP_REDIRECTS = 5;
|
|
13
15
|
const inflight = new Map();
|
|
14
16
|
export const downloadCacheFs = {
|
|
15
17
|
copyFile,
|
|
@@ -46,10 +48,44 @@ async function touch(path) {
|
|
|
46
48
|
const now = new Date();
|
|
47
49
|
await downloadCacheFs.utimes(path, now, now);
|
|
48
50
|
}
|
|
49
|
-
async function streamUrlToFile(url, targetPath, headers, logUrl = redactUrlForLogs(url)) {
|
|
50
|
-
|
|
51
|
+
async function streamUrlToFile(url, targetPath, headers, logUrl = redactUrlForLogs(url), storageAuth = {}) {
|
|
52
|
+
if (supportsCloudDownload(url)) {
|
|
53
|
+
await downloadCloudUrlToFile(url, targetPath, storageAuth);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
let currentUrl = url;
|
|
57
|
+
let currentHeaders = headers;
|
|
58
|
+
let res;
|
|
59
|
+
for (let redirectCount = 0;; redirectCount += 1) {
|
|
60
|
+
res = await fetch(currentUrl, { headers: currentHeaders, redirect: "manual" });
|
|
61
|
+
if (res.status < 300 || res.status >= 400)
|
|
62
|
+
break;
|
|
63
|
+
if (redirectCount >= MAX_HTTP_REDIRECTS) {
|
|
64
|
+
throw new ModelError("Download redirect limit exceeded", {
|
|
65
|
+
url: redactUrlForLogs(currentUrl),
|
|
66
|
+
status: res.status,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const location = res.headers.get("location");
|
|
70
|
+
if (!location)
|
|
71
|
+
break;
|
|
72
|
+
let nextUrl;
|
|
73
|
+
try {
|
|
74
|
+
nextUrl = new URL(location, currentUrl).toString();
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
throw new ModelError("Download redirect location is invalid", {
|
|
78
|
+
url: redactUrlForLogs(currentUrl),
|
|
79
|
+
status: res.status,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const sameOrigin = new URL(nextUrl).origin === new URL(currentUrl).origin;
|
|
83
|
+
currentUrl = nextUrl;
|
|
84
|
+
if (!sameOrigin)
|
|
85
|
+
currentHeaders = {};
|
|
86
|
+
}
|
|
51
87
|
if (!res.ok) {
|
|
52
|
-
throw new ModelError(`Download failed: ${res.status} ${res.statusText}`, { url: logUrl, status: res.status });
|
|
88
|
+
throw new ModelError(`Download failed: ${res.status} ${res.statusText}`, { url: currentUrl === url ? logUrl : redactUrlForLogs(currentUrl), status: res.status });
|
|
53
89
|
}
|
|
54
90
|
if (!res.body) {
|
|
55
91
|
throw new ModelError("Download response has no body", { url: logUrl });
|
|
@@ -58,7 +94,7 @@ async function streamUrlToFile(url, targetPath, headers, logUrl = redactUrlForLo
|
|
|
58
94
|
const fileStream = createWriteStream(targetPath);
|
|
59
95
|
await pipeline(nodeStream, fileStream);
|
|
60
96
|
}
|
|
61
|
-
async function downloadIntoCache(url, headers, logUrl) {
|
|
97
|
+
async function downloadIntoCache(url, headers, logUrl, storageAuth = {}) {
|
|
62
98
|
const target = cachePathForUrl(url);
|
|
63
99
|
const key = target;
|
|
64
100
|
const existing = inflight.get(key);
|
|
@@ -78,7 +114,7 @@ async function downloadIntoCache(url, headers, logUrl) {
|
|
|
78
114
|
}
|
|
79
115
|
const tmp = join(cacheDir(), `.${basename(target)}.${process.pid}.${randomUUID()}.tmp`);
|
|
80
116
|
try {
|
|
81
|
-
await streamUrlToFile(url, tmp, headers, logUrl);
|
|
117
|
+
await streamUrlToFile(url, tmp, headers, logUrl, storageAuth);
|
|
82
118
|
await downloadCacheFs.rename(tmp, target);
|
|
83
119
|
await touch(target);
|
|
84
120
|
return target;
|
|
@@ -137,13 +173,13 @@ async function evictLruIfNeeded() {
|
|
|
137
173
|
break;
|
|
138
174
|
}
|
|
139
175
|
}
|
|
140
|
-
export async function downloadUrlToFile(url, targetPath, headers, logUrl) {
|
|
141
|
-
await streamUrlToFile(url, targetPath, headers, logUrl);
|
|
176
|
+
export async function downloadUrlToFile(url, targetPath, headers, logUrl, storageAuth = {}) {
|
|
177
|
+
await streamUrlToFile(url, targetPath, headers, logUrl, storageAuth);
|
|
142
178
|
}
|
|
143
179
|
export async function downloadWithCache(options) {
|
|
144
180
|
const logUrl = options.logUrl ?? redactUrlForLogs(options.url);
|
|
145
181
|
try {
|
|
146
|
-
const cachePath = await downloadIntoCache(options.url, options.headers, logUrl);
|
|
182
|
+
const cachePath = await downloadIntoCache(options.url, options.headers, logUrl, options.storageAuth);
|
|
147
183
|
const materializedBy = await materializeCacheFile(cachePath, options.targetPath);
|
|
148
184
|
await evictLruIfNeeded();
|
|
149
185
|
return {
|
|
@@ -160,7 +196,7 @@ export async function downloadWithCache(options) {
|
|
|
160
196
|
url: logUrl,
|
|
161
197
|
error: err instanceof Error ? err.message : String(err),
|
|
162
198
|
});
|
|
163
|
-
await downloadUrlToFile(options.url, options.targetPath, options.headers, logUrl);
|
|
199
|
+
await downloadUrlToFile(options.url, options.targetPath, options.headers, logUrl, options.storageAuth);
|
|
164
200
|
return { targetPath: options.targetPath, usedCache: false };
|
|
165
201
|
}
|
|
166
202
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download-cache.js","sourceRoot":"","sources":["../../src/services/download-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EACL,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,OAAO,EACP,MAAM,EACN,EAAE,EACF,IAAI,EACJ,MAAM,GACP,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"download-cache.js","sourceRoot":"","sources":["../../src/services/download-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type ConvertImageFormat = "png" | "jpeg" | "webp";
|
|
2
|
+
export interface ConvertImageOptions {
|
|
3
|
+
asset_id?: string;
|
|
4
|
+
path?: string;
|
|
5
|
+
format: ConvertImageFormat;
|
|
6
|
+
quality?: number;
|
|
7
|
+
progressive?: boolean;
|
|
8
|
+
lossless?: boolean;
|
|
9
|
+
effort?: number;
|
|
10
|
+
out_path?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ConvertImageResult {
|
|
13
|
+
content: Array<{
|
|
14
|
+
type: "text";
|
|
15
|
+
text: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: "image";
|
|
18
|
+
data: string;
|
|
19
|
+
mimeType: string;
|
|
20
|
+
}>;
|
|
21
|
+
format: ConvertImageFormat;
|
|
22
|
+
mimeType: string;
|
|
23
|
+
sourceBytes: number;
|
|
24
|
+
outputBytes: number;
|
|
25
|
+
bytesSaved: number;
|
|
26
|
+
outPath?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function convertImage(opts: ConvertImageOptions): Promise<ConvertImageResult>;
|
|
29
|
+
//# sourceMappingURL=image-convert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-convert.d.ts","sourceRoot":"","sources":["../../src/services/image-convert.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,KAAK,CACV;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAC9B;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CACpD,CAAC;IACF,MAAM,EAAE,kBAAkB,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA4ND,wBAAsB,YAAY,CAChC,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,kBAAkB,CAAC,CA8C7B"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { lstat, mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
+
import sharp from "sharp";
|
|
4
|
+
import { config } from "../config.js";
|
|
5
|
+
import { AssetRegistry } from "./asset-registry.js";
|
|
6
|
+
import { getOutputImage } from "./image-management.js";
|
|
7
|
+
import { ValidationError } from "../utils/errors.js";
|
|
8
|
+
const MIME_BY_FORMAT = {
|
|
9
|
+
png: "image/png",
|
|
10
|
+
jpeg: "image/jpeg",
|
|
11
|
+
webp: "image/webp",
|
|
12
|
+
};
|
|
13
|
+
const SUPPORTED_SOURCE_MIME = /^image\/(png|jpe?g|webp)$/i;
|
|
14
|
+
const DEFAULT_MAX_SOURCE_BYTES = 64 * 1024 * 1024;
|
|
15
|
+
const DEFAULT_LIMIT_INPUT_PIXELS = 100_000_000;
|
|
16
|
+
function getOutputDir() {
|
|
17
|
+
if (!config.comfyuiPath) {
|
|
18
|
+
throw new ValidationError("COMFYUI_PATH is not configured. Set the COMFYUI_PATH environment variable.");
|
|
19
|
+
}
|
|
20
|
+
return resolve(config.comfyuiPath, "output");
|
|
21
|
+
}
|
|
22
|
+
function parsePositiveIntEnv(name, fallback) {
|
|
23
|
+
const raw = process.env[name];
|
|
24
|
+
if (raw === undefined || raw.trim() === "")
|
|
25
|
+
return fallback;
|
|
26
|
+
const parsed = Number(raw);
|
|
27
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
|
|
28
|
+
}
|
|
29
|
+
function maxSourceBytes() {
|
|
30
|
+
return parsePositiveIntEnv("COMFYUI_CONVERT_IMAGE_MAX_SOURCE_BYTES", DEFAULT_MAX_SOURCE_BYTES);
|
|
31
|
+
}
|
|
32
|
+
function limitInputPixels() {
|
|
33
|
+
return parsePositiveIntEnv("COMFYUI_CONVERT_IMAGE_LIMIT_INPUT_PIXELS", DEFAULT_LIMIT_INPUT_PIXELS);
|
|
34
|
+
}
|
|
35
|
+
function resolveOutputPath(path, label) {
|
|
36
|
+
if (path.trim().length === 0) {
|
|
37
|
+
throw new ValidationError(`${label} must be a non-empty path.`);
|
|
38
|
+
}
|
|
39
|
+
const outputDir = getOutputDir();
|
|
40
|
+
const resolved = isAbsolute(path) ? resolve(path) : resolve(outputDir, path);
|
|
41
|
+
if (resolved === outputDir || !resolved.startsWith(outputDir + sep)) {
|
|
42
|
+
throw new ValidationError(`${label} must stay within the ComfyUI output directory.`);
|
|
43
|
+
}
|
|
44
|
+
return resolved;
|
|
45
|
+
}
|
|
46
|
+
async function realOutputRoot() {
|
|
47
|
+
return realpath(getOutputDir());
|
|
48
|
+
}
|
|
49
|
+
function isInsideOrEqual(root, candidate) {
|
|
50
|
+
const rel = relative(root, candidate);
|
|
51
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
52
|
+
}
|
|
53
|
+
function assertInsideRealOutputDir(root, candidate, label, opts = {}) {
|
|
54
|
+
if (!isInsideOrEqual(root, candidate) || (!opts.allowRoot && candidate === root)) {
|
|
55
|
+
throw new ValidationError(`${label} must stay within the ComfyUI output directory.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function validateExistingOutputAncestors(root, parent, label) {
|
|
59
|
+
const outputDir = getOutputDir();
|
|
60
|
+
let cursor = parent;
|
|
61
|
+
while (cursor !== outputDir && cursor.startsWith(outputDir + sep)) {
|
|
62
|
+
try {
|
|
63
|
+
const info = await lstat(cursor);
|
|
64
|
+
if (info.isSymbolicLink()) {
|
|
65
|
+
const realCursor = await realpath(cursor);
|
|
66
|
+
assertInsideRealOutputDir(root, realCursor, label, { allowRoot: true });
|
|
67
|
+
}
|
|
68
|
+
else if (!info.isDirectory()) {
|
|
69
|
+
throw new ValidationError(`${label} parent is not a directory: ${cursor}`);
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
if (err instanceof ValidationError)
|
|
75
|
+
throw err;
|
|
76
|
+
cursor = dirname(cursor);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function resolveSourcePath(path) {
|
|
81
|
+
const lexicalPath = resolveOutputPath(path, "path");
|
|
82
|
+
const root = await realOutputRoot();
|
|
83
|
+
const sourcePath = await realpath(lexicalPath).catch(() => undefined);
|
|
84
|
+
if (!sourcePath) {
|
|
85
|
+
throw new ValidationError(`Source image not found: ${lexicalPath}`);
|
|
86
|
+
}
|
|
87
|
+
assertInsideRealOutputDir(root, sourcePath, "path");
|
|
88
|
+
const info = await stat(sourcePath).catch(() => undefined);
|
|
89
|
+
if (!info?.isFile()) {
|
|
90
|
+
throw new ValidationError(`Source image not found: ${sourcePath}`);
|
|
91
|
+
}
|
|
92
|
+
const maxBytes = maxSourceBytes();
|
|
93
|
+
if (info.size > maxBytes) {
|
|
94
|
+
throw new ValidationError(`Source image is too large (${info.size} bytes). Maximum is ${maxBytes} bytes.`);
|
|
95
|
+
}
|
|
96
|
+
return { path: sourcePath, size: info.size };
|
|
97
|
+
}
|
|
98
|
+
async function resolveWritableOutputPath(path) {
|
|
99
|
+
const targetPath = resolveOutputPath(path, "out_path");
|
|
100
|
+
const root = await realOutputRoot();
|
|
101
|
+
const parent = dirname(targetPath);
|
|
102
|
+
await validateExistingOutputAncestors(root, parent, "out_path");
|
|
103
|
+
await mkdir(parent, { recursive: true });
|
|
104
|
+
const realParent = await realpath(parent);
|
|
105
|
+
assertInsideRealOutputDir(root, realParent, "out_path", { allowRoot: true });
|
|
106
|
+
try {
|
|
107
|
+
const info = await lstat(targetPath);
|
|
108
|
+
if (info.isSymbolicLink()) {
|
|
109
|
+
const realTarget = await realpath(targetPath);
|
|
110
|
+
assertInsideRealOutputDir(root, realTarget, "out_path");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
if (err instanceof ValidationError)
|
|
115
|
+
throw err;
|
|
116
|
+
// Missing target is fine; writeFile will create it.
|
|
117
|
+
}
|
|
118
|
+
return targetPath;
|
|
119
|
+
}
|
|
120
|
+
async function resolveSource(opts) {
|
|
121
|
+
if (opts.asset_id) {
|
|
122
|
+
const record = AssetRegistry.get(opts.asset_id);
|
|
123
|
+
if (!record) {
|
|
124
|
+
throw new ValidationError(`No asset found for id "${opts.asset_id}". It may have expired or never been registered.`);
|
|
125
|
+
}
|
|
126
|
+
const validType = record.type === "output" || record.type === "input" || record.type === "temp";
|
|
127
|
+
const fetchType = validType
|
|
128
|
+
? record.type
|
|
129
|
+
: "output";
|
|
130
|
+
const image = await getOutputImage(record.filename, fetchType, record.subfolder);
|
|
131
|
+
if (!SUPPORTED_SOURCE_MIME.test(image.mimeType)) {
|
|
132
|
+
throw new ValidationError(`Asset "${opts.asset_id}" is not a supported image (mime: ${image.mimeType}).`);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
label: `asset ${opts.asset_id} (${record.filename})`,
|
|
136
|
+
bytes: Buffer.from(image.base64, "base64"),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (!opts.path) {
|
|
140
|
+
throw new ValidationError("convert_image requires either asset_id or path.");
|
|
141
|
+
}
|
|
142
|
+
const source = await resolveSourcePath(opts.path);
|
|
143
|
+
return {
|
|
144
|
+
label: source.path,
|
|
145
|
+
bytes: await readFile(source.path),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function buildEncoder(input, opts) {
|
|
149
|
+
const image = sharp(input, { limitInputPixels: limitInputPixels() });
|
|
150
|
+
if (opts.format === "png") {
|
|
151
|
+
return image.png({ quality: opts.quality });
|
|
152
|
+
}
|
|
153
|
+
if (opts.format === "jpeg") {
|
|
154
|
+
return image.jpeg({
|
|
155
|
+
quality: opts.quality,
|
|
156
|
+
progressive: opts.progressive,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return image.webp({
|
|
160
|
+
quality: opts.quality,
|
|
161
|
+
lossless: opts.lossless,
|
|
162
|
+
effort: opts.effort,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function validateEncodeOptions(opts) {
|
|
166
|
+
if (opts.quality !== undefined &&
|
|
167
|
+
(!Number.isInteger(opts.quality) || opts.quality < 1 || opts.quality > 100)) {
|
|
168
|
+
throw new ValidationError("quality must be an integer between 1 and 100.");
|
|
169
|
+
}
|
|
170
|
+
if (opts.effort !== undefined &&
|
|
171
|
+
(!Number.isInteger(opts.effort) || opts.effort < 0 || opts.effort > 6)) {
|
|
172
|
+
throw new ValidationError("effort must be an integer between 0 and 6.");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
export async function convertImage(opts) {
|
|
176
|
+
if (Boolean(opts.asset_id) === Boolean(opts.path)) {
|
|
177
|
+
throw new ValidationError("Provide exactly one image source: asset_id or path.");
|
|
178
|
+
}
|
|
179
|
+
validateEncodeOptions(opts);
|
|
180
|
+
const source = await resolveSource(opts);
|
|
181
|
+
const converted = await buildEncoder(source.bytes, opts).toBuffer();
|
|
182
|
+
const mimeType = MIME_BY_FORMAT[opts.format];
|
|
183
|
+
const outPath = opts.out_path
|
|
184
|
+
? await resolveWritableOutputPath(opts.out_path)
|
|
185
|
+
: undefined;
|
|
186
|
+
if (outPath) {
|
|
187
|
+
await writeFile(outPath, converted);
|
|
188
|
+
}
|
|
189
|
+
const sourceBytes = source.bytes.length;
|
|
190
|
+
const outputBytes = converted.length;
|
|
191
|
+
const bytesSaved = sourceBytes - outputBytes;
|
|
192
|
+
const summary = {
|
|
193
|
+
source: source.label,
|
|
194
|
+
format: opts.format,
|
|
195
|
+
mime_type: mimeType,
|
|
196
|
+
source_bytes: sourceBytes,
|
|
197
|
+
output_bytes: outputBytes,
|
|
198
|
+
bytes_saved: bytesSaved,
|
|
199
|
+
out_path: outPath,
|
|
200
|
+
};
|
|
201
|
+
return {
|
|
202
|
+
content: [
|
|
203
|
+
{ type: "text", text: JSON.stringify(summary, null, 2) },
|
|
204
|
+
{
|
|
205
|
+
type: "image",
|
|
206
|
+
data: converted.toString("base64"),
|
|
207
|
+
mimeType,
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
format: opts.format,
|
|
211
|
+
mimeType,
|
|
212
|
+
sourceBytes,
|
|
213
|
+
outputBytes,
|
|
214
|
+
bytesSaved,
|
|
215
|
+
outPath,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=image-convert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-convert.js","sourceRoot":"","sources":["../../src/services/image-convert.ts"],"names":[],"mappings":"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"}
|