gaoding-cli 1.0.0-alpha.5
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 +88 -0
- package/LICENSE +21 -0
- package/README.md +136 -0
- package/SECURITY.md +34 -0
- package/dist/bin/gd-cli.d.ts +2 -0
- package/dist/bin/gd-cli.js +10 -0
- package/dist/src/api/app-runner.d.ts +33 -0
- package/dist/src/api/app-runner.js +1177 -0
- package/dist/src/api/dam-client.d.ts +111 -0
- package/dist/src/api/dam-client.js +897 -0
- package/dist/src/api/direct-auth.d.ts +3 -0
- package/dist/src/api/direct-auth.js +11 -0
- package/dist/src/api/direct-client.d.ts +27 -0
- package/dist/src/api/direct-client.js +151 -0
- package/dist/src/api/direct-tools.d.ts +43 -0
- package/dist/src/api/direct-tools.js +1155 -0
- package/dist/src/api/hub-client.d.ts +20 -0
- package/dist/src/api/hub-client.js +99 -0
- package/dist/src/api/sso-client.d.ts +60 -0
- package/dist/src/api/sso-client.js +140 -0
- package/dist/src/api/task-poller.d.ts +38 -0
- package/dist/src/api/task-poller.js +72 -0
- package/dist/src/api/tool-invoker.d.ts +12 -0
- package/dist/src/api/tool-invoker.js +11 -0
- package/dist/src/api/usage-client.d.ts +39 -0
- package/dist/src/api/usage-client.js +179 -0
- package/dist/src/auth/credential-store-v2.d.ts +12 -0
- package/dist/src/auth/credential-store-v2.js +59 -0
- package/dist/src/commands/auth.d.ts +2 -0
- package/dist/src/commands/auth.js +191 -0
- package/dist/src/commands/dam.d.ts +2 -0
- package/dist/src/commands/dam.js +293 -0
- package/dist/src/commands/models.d.ts +2 -0
- package/dist/src/commands/models.js +78 -0
- package/dist/src/commands/org.d.ts +2 -0
- package/dist/src/commands/org.js +137 -0
- package/dist/src/commands/skills.d.ts +2 -0
- package/dist/src/commands/skills.js +213 -0
- package/dist/src/commands/tools.d.ts +4 -0
- package/dist/src/commands/tools.js +383 -0
- package/dist/src/commands/update.d.ts +2 -0
- package/dist/src/commands/update.js +167 -0
- package/dist/src/commands/usage.d.ts +7 -0
- package/dist/src/commands/usage.js +43 -0
- package/dist/src/commands/workflows.d.ts +3 -0
- package/dist/src/commands/workflows.js +398 -0
- package/dist/src/core/auth/auth-provider.d.ts +8 -0
- package/dist/src/core/auth/auth-provider.js +12 -0
- package/dist/src/core/auth/device-oauth-provider.d.ts +12 -0
- package/dist/src/core/auth/device-oauth-provider.js +124 -0
- package/dist/src/core/auth/org-manager.d.ts +26 -0
- package/dist/src/core/auth/org-manager.js +224 -0
- package/dist/src/core/auth/signature.d.ts +19 -0
- package/dist/src/core/auth/signature.js +33 -0
- package/dist/src/core/auth/types.d.ts +52 -0
- package/dist/src/core/auth/types.js +3 -0
- package/dist/src/core/output/cli-error.d.ts +5 -0
- package/dist/src/core/output/cli-error.js +8 -0
- package/dist/src/core/output/detector.d.ts +5 -0
- package/dist/src/core/output/detector.js +14 -0
- package/dist/src/core/output/engine.d.ts +32 -0
- package/dist/src/core/output/engine.js +77 -0
- package/dist/src/core/output/envelope.d.ts +40 -0
- package/dist/src/core/output/envelope.js +119 -0
- package/dist/src/core/output/index.d.ts +4 -0
- package/dist/src/core/output/index.js +4 -0
- package/dist/src/core/output/live-display.d.ts +15 -0
- package/dist/src/core/output/live-display.js +111 -0
- package/dist/src/core/output/result-card.d.ts +18 -0
- package/dist/src/core/output/result-card.js +39 -0
- package/dist/src/core/prompt/index.d.ts +31 -0
- package/dist/src/core/prompt/index.js +36 -0
- package/dist/src/io.d.ts +14 -0
- package/dist/src/io.js +31 -0
- package/dist/src/middleware/auth.d.ts +3 -0
- package/dist/src/middleware/auth.js +74 -0
- package/dist/src/middleware/error-handler.d.ts +3 -0
- package/dist/src/middleware/error-handler.js +431 -0
- package/dist/src/middleware/interactive-params.d.ts +14 -0
- package/dist/src/middleware/interactive-params.js +53 -0
- package/dist/src/middleware/output.d.ts +14 -0
- package/dist/src/middleware/output.js +546 -0
- package/dist/src/models/model-catalog.d.ts +26 -0
- package/dist/src/models/model-catalog.js +216 -0
- package/dist/src/program.d.ts +2 -0
- package/dist/src/program.js +152 -0
- package/dist/src/registry/agent-contracts.d.ts +43 -0
- package/dist/src/registry/agent-contracts.js +483 -0
- package/dist/src/registry/app-loader.d.ts +2 -0
- package/dist/src/registry/app-loader.js +70 -0
- package/dist/src/registry/direct-registry.d.ts +11 -0
- package/dist/src/registry/direct-registry.js +234 -0
- package/dist/src/registry/registry-cache.d.ts +10 -0
- package/dist/src/registry/registry-cache.js +52 -0
- package/dist/src/registry/registry-client.d.ts +28 -0
- package/dist/src/registry/registry-client.js +50 -0
- package/dist/src/registry/types.d.ts +69 -0
- package/dist/src/registry/types.js +1 -0
- package/dist/src/tty/auth-presenter.d.ts +3 -0
- package/dist/src/tty/auth-presenter.js +21 -0
- package/dist/src/update/install-source.d.ts +19 -0
- package/dist/src/update/install-source.js +139 -0
- package/dist/src/update/notifier.d.ts +2 -0
- package/dist/src/update/notifier.js +59 -0
- package/dist/src/update/update-checker.d.ts +22 -0
- package/dist/src/update/update-checker.js +110 -0
- package/dist/src/update/version.d.ts +9 -0
- package/dist/src/update/version.js +52 -0
- package/dist/src/utils/agent-skill-installer.d.ts +57 -0
- package/dist/src/utils/agent-skill-installer.js +389 -0
- package/dist/src/utils/capability-metadata.d.ts +18 -0
- package/dist/src/utils/capability-metadata.js +88 -0
- package/dist/src/utils/capability-presenter.d.ts +4 -0
- package/dist/src/utils/capability-presenter.js +190 -0
- package/dist/src/utils/config.d.ts +16 -0
- package/dist/src/utils/config.js +54 -0
- package/dist/src/utils/credential-permissions.d.ts +2 -0
- package/dist/src/utils/credential-permissions.js +22 -0
- package/dist/src/utils/ecommerce-input-guidance.d.ts +15 -0
- package/dist/src/utils/ecommerce-input-guidance.js +85 -0
- package/dist/src/utils/endpoints.d.ts +52 -0
- package/dist/src/utils/endpoints.js +258 -0
- package/dist/src/utils/enrich-upload-params.d.ts +6 -0
- package/dist/src/utils/enrich-upload-params.js +11 -0
- package/dist/src/utils/entitlement.d.ts +17 -0
- package/dist/src/utils/entitlement.js +146 -0
- package/dist/src/utils/gd-home.d.ts +7 -0
- package/dist/src/utils/gd-home.js +26 -0
- package/dist/src/utils/help-verify.d.ts +9 -0
- package/dist/src/utils/help-verify.js +41 -0
- package/dist/src/utils/input-json.d.ts +3 -0
- package/dist/src/utils/input-json.js +61 -0
- package/dist/src/utils/list-output-format.d.ts +10 -0
- package/dist/src/utils/list-output-format.js +34 -0
- package/dist/src/utils/logger.d.ts +6 -0
- package/dist/src/utils/logger.js +19 -0
- package/dist/src/utils/matting-compose.d.ts +10 -0
- package/dist/src/utils/matting-compose.js +45 -0
- package/dist/src/utils/open-browser.d.ts +1 -0
- package/dist/src/utils/open-browser.js +4 -0
- package/dist/src/utils/output-path.d.ts +5 -0
- package/dist/src/utils/output-path.js +70 -0
- package/dist/src/utils/redact.d.ts +3 -0
- package/dist/src/utils/redact.js +96 -0
- package/dist/src/utils/reference-image.d.ts +8 -0
- package/dist/src/utils/reference-image.js +43 -0
- package/dist/src/utils/resolve-app-local-images.d.ts +8 -0
- package/dist/src/utils/resolve-app-local-images.js +84 -0
- package/dist/src/utils/sensitive-path.d.ts +6 -0
- package/dist/src/utils/sensitive-path.js +69 -0
- package/dist/src/utils/settings-store.d.ts +42 -0
- package/dist/src/utils/settings-store.js +50 -0
- package/dist/src/utils/sleep.d.ts +1 -0
- package/dist/src/utils/sleep.js +3 -0
- package/dist/src/utils/spinner.d.ts +2 -0
- package/dist/src/utils/spinner.js +4 -0
- package/dist/src/utils/static-help.d.ts +2 -0
- package/dist/src/utils/static-help.js +3 -0
- package/dist/src/utils/tool-display.d.ts +10 -0
- package/dist/src/utils/tool-display.js +55 -0
- package/dist/src/utils/transient-network-error.d.ts +4 -0
- package/dist/src/utils/transient-network-error.js +40 -0
- package/dist/src/utils/url-safety.d.ts +6 -0
- package/dist/src/utils/url-safety.js +67 -0
- package/dist/src/utils/write-app-deliverables.d.ts +18 -0
- package/dist/src/utils/write-app-deliverables.js +386 -0
- package/package.json +91 -0
- package/skills/gd-cli/SKILL.md +57 -0
- package/skills/gd-cli/references/auth.md +31 -0
- package/skills/gd-cli/references/deliverables.md +22 -0
- package/skills/gd-cli/references/entitlement.md +21 -0
- package/skills/gd-cli/references/errors.md +19 -0
- package/skills/gd-cli/references/org.md +18 -0
- package/skills/gd-cli/references/sop.md +25 -0
- package/skills/gd-cli/references/tools.md +29 -0
- package/skills/gd-cli/references/update.md +16 -0
- package/skills/gd-cli/references/workflows.md +22 -0
|
@@ -0,0 +1,1155 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { CredentialStoreV2 } from "../auth/credential-store-v2.js";
|
|
6
|
+
import { composeMattingImage, fetchImageBuffer } from "../utils/matting-compose.js";
|
|
7
|
+
import { isTransientNetworkError } from "../utils/transient-network-error.js";
|
|
8
|
+
import { detectEntitlementIssue } from "../utils/entitlement.js";
|
|
9
|
+
import { resolveEndpoints } from "../utils/endpoints.js";
|
|
10
|
+
import { DamClient } from "./dam-client.js";
|
|
11
|
+
import { DirectApiClient, unwrapEnvelope } from "./direct-client.js";
|
|
12
|
+
import { getConfiguredModel, modelEntriesForType, publicModelByCode, resolveModelEntry, resolveModelOrder, } from "../models/model-catalog.js";
|
|
13
|
+
import { readSettings, writeSettings } from "../utils/settings-store.js";
|
|
14
|
+
import { assertSafeHttpUrl } from "../utils/url-safety.js";
|
|
15
|
+
export class MediaResourceError extends Error {
|
|
16
|
+
url;
|
|
17
|
+
code = "MEDIA_RESOURCE_UNREACHABLE";
|
|
18
|
+
constructor(message, url) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.url = url;
|
|
21
|
+
this.name = "MediaResourceError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const FAILURE_STATUSES = new Set(["FAIL", "NOT_PASSED", "AUDITED_FAILED"]);
|
|
25
|
+
const RUNNING_STATUSES = new Set(["WAITING", "RUNNING", "PENDING", "STARTED", "INIT"]);
|
|
26
|
+
const MODEL_CATEGORY_CODES = {
|
|
27
|
+
image: "image-model-art",
|
|
28
|
+
video: "AI-video-generation",
|
|
29
|
+
};
|
|
30
|
+
export class DirectToolInvoker {
|
|
31
|
+
client;
|
|
32
|
+
dam;
|
|
33
|
+
constructor(client = new DirectApiClient()) {
|
|
34
|
+
this.client = client;
|
|
35
|
+
this.dam = new DamClient(this.client);
|
|
36
|
+
}
|
|
37
|
+
async invoke(toolName, params, options = {}) {
|
|
38
|
+
const normalizedName = normalizeToolName(toolName);
|
|
39
|
+
const handler = this.handlers[normalizedName];
|
|
40
|
+
if (!handler) {
|
|
41
|
+
throw new Error(`暂不支持直连工具: ${toolName}。可用工具: ${Object.keys(this.handlers).join(", ")}`);
|
|
42
|
+
}
|
|
43
|
+
return handler(params, options);
|
|
44
|
+
}
|
|
45
|
+
handlers = {
|
|
46
|
+
upload: async (params) => {
|
|
47
|
+
const file = requiredString(params.file ?? params.image ?? params.video, "upload 需要 --file");
|
|
48
|
+
const asset = await this.dam.uploadLocalAsset(file, {
|
|
49
|
+
allowSensitivePath: allowSensitivePath(params),
|
|
50
|
+
});
|
|
51
|
+
return completed("asset-upload", {
|
|
52
|
+
id: asset.id,
|
|
53
|
+
storage_id: asset.storage_id,
|
|
54
|
+
url: asset.url,
|
|
55
|
+
filename: asset.filename,
|
|
56
|
+
format: asset.format,
|
|
57
|
+
mime_type: asset.mime_type,
|
|
58
|
+
object_name: asset.object_name,
|
|
59
|
+
registered: false,
|
|
60
|
+
scope: "cdn",
|
|
61
|
+
source: asset.source,
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
title: async (params, options) => {
|
|
65
|
+
const prompt = requiredString(params.prompt ?? params.brief ?? params.input, "title 需要 --prompt 或 --brief");
|
|
66
|
+
const contentId = optionalString(params.content_id);
|
|
67
|
+
const summary = await this.callSyncTool("title-generation", withOptionalContentId({
|
|
68
|
+
input: prompt,
|
|
69
|
+
}, contentId), options);
|
|
70
|
+
return completed("title-generation", withOptionalContentId({
|
|
71
|
+
text: primaryText(summary),
|
|
72
|
+
}, contentId));
|
|
73
|
+
},
|
|
74
|
+
prompt: async (params, options) => {
|
|
75
|
+
const prompt = requiredString(params.prompt ?? params.brief ?? params.input, "prompt 需要 --prompt 或 --brief");
|
|
76
|
+
const media = await this.optionalRemoteImage(params);
|
|
77
|
+
assertRequiredReferenceImage(media, params, "text.prompt");
|
|
78
|
+
const payload = { prompt };
|
|
79
|
+
if (media) {
|
|
80
|
+
payload.image = media.url;
|
|
81
|
+
}
|
|
82
|
+
else if (params.image_prompt) {
|
|
83
|
+
payload.image_prompt = String(params.image_prompt);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
payload.image_prompt = prompt;
|
|
87
|
+
}
|
|
88
|
+
const summary = await this.callAsyncTool("autoPrompt-advise", payload, options);
|
|
89
|
+
return resultFromSummary("autoPrompt-advise", summary);
|
|
90
|
+
},
|
|
91
|
+
cutout: async (params, options) => {
|
|
92
|
+
const image = await this.requireRemoteImage(params);
|
|
93
|
+
assertMattingSupportedLocalImage(image.uploaded);
|
|
94
|
+
const requestedMode = optionalString(params.mode) ?? "auto";
|
|
95
|
+
const mode = await this.resolveMattingMode(image.url, requestedMode, options);
|
|
96
|
+
const toolMap = {
|
|
97
|
+
portrait: "matting-person",
|
|
98
|
+
product: "matting-product",
|
|
99
|
+
graphic: "matting-graphs",
|
|
100
|
+
};
|
|
101
|
+
const summary = await this.callSyncTool(toolMap[mode], { image: image.url, result_img_path: mattingResultPath() }, options);
|
|
102
|
+
const result = resultFromSummary(toolMap[mode], summary, {
|
|
103
|
+
mode,
|
|
104
|
+
requested_mode: requestedMode,
|
|
105
|
+
uploaded: image.uploaded,
|
|
106
|
+
});
|
|
107
|
+
const outputs = result.outputs;
|
|
108
|
+
if (outputs && typeof outputs.url === "string") {
|
|
109
|
+
const maskUrl = outputs.url;
|
|
110
|
+
const composeMask = shouldComposeMattingMask(params);
|
|
111
|
+
if (composeMask) {
|
|
112
|
+
try {
|
|
113
|
+
const [sourceBuf, maskBuf] = await Promise.all([
|
|
114
|
+
fetchImageBuffer(image.url),
|
|
115
|
+
fetchImageBuffer(maskUrl),
|
|
116
|
+
]);
|
|
117
|
+
const composed = await composeMattingImage(sourceBuf, maskBuf);
|
|
118
|
+
const uploaded = await this.uploadPngBuffer(composed);
|
|
119
|
+
outputs.mask_url = maskUrl;
|
|
120
|
+
outputs.url = uploaded.url;
|
|
121
|
+
outputs.mime_type = uploaded.mime_type;
|
|
122
|
+
outputs.composed = true;
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
outputs.mask_url = maskUrl;
|
|
126
|
+
outputs.composed = false;
|
|
127
|
+
outputs.compose_error = errorMessage(error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
outputs.mask_url = maskUrl;
|
|
132
|
+
outputs.composed = false;
|
|
133
|
+
}
|
|
134
|
+
if (outputs.composed !== true) {
|
|
135
|
+
outputs.mime_type = inferMimeTypeFromUrl(maskUrl);
|
|
136
|
+
}
|
|
137
|
+
outputs.assets = buildMattingAssets(outputs, image);
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
},
|
|
141
|
+
generate: async (params, options) => {
|
|
142
|
+
const prompt = requiredString(params.prompt ?? params.input, "generate 需要 prompt");
|
|
143
|
+
const image = await this.optionalRemoteImage(params);
|
|
144
|
+
assertRequiredReferenceImage(image, params, "image.generate");
|
|
145
|
+
if (image)
|
|
146
|
+
await ensureReferenceImageDownloadable(image, "image");
|
|
147
|
+
return this.invokeModelOrderedAsyncTool({
|
|
148
|
+
modelType: "image",
|
|
149
|
+
params,
|
|
150
|
+
promptItems: buildPromptItems(prompt, image ? [image] : []),
|
|
151
|
+
parameters: pickStringParams(params, ["ratio", "resolution"]),
|
|
152
|
+
uploaded: image?.uploaded,
|
|
153
|
+
options,
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
video: async (params, options) => {
|
|
157
|
+
const prompt = requiredString(params.prompt ?? params.input, "video 需要 prompt");
|
|
158
|
+
const image = await this.optionalRemoteImage(params);
|
|
159
|
+
assertRequiredReferenceImage(image, params, "video.generate");
|
|
160
|
+
const videoReference = image
|
|
161
|
+
? await this.prepareVideoReferenceImage(image, options)
|
|
162
|
+
: undefined;
|
|
163
|
+
return this.invokeModelOrderedAsyncTool({
|
|
164
|
+
modelType: "video",
|
|
165
|
+
params,
|
|
166
|
+
promptItems: buildPromptItems(prompt, videoReference ? [videoReference.media] : [], ["高清首帧参考图"]),
|
|
167
|
+
parameters: pickStringParams(params, ["ratio", "resolution", "duration"]),
|
|
168
|
+
uploaded: image?.uploaded,
|
|
169
|
+
extraOutputs: videoReference?.outputs,
|
|
170
|
+
options,
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
vectorize: async (params) => {
|
|
174
|
+
const image = await this.requireRemoteImage(params);
|
|
175
|
+
const response = await this.client.post("/open/editor/ai/tools/v10/img2svg", {
|
|
176
|
+
image_url: image.url,
|
|
177
|
+
mode: optionalString(params.mode) ?? "auto",
|
|
178
|
+
});
|
|
179
|
+
const data = unwrapEnvelope(response);
|
|
180
|
+
if (typeof data.code === "number" && data.code !== 0) {
|
|
181
|
+
throw new Error(data.message || data.msg || "图转 SVG 失败");
|
|
182
|
+
}
|
|
183
|
+
if (!data.url)
|
|
184
|
+
throw new Error("图转 SVG 失败:接口没有返回 url");
|
|
185
|
+
return completed("img2svg", {
|
|
186
|
+
url: data.url,
|
|
187
|
+
mime_type: "image/svg+xml",
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
upscale: async (params, options) => {
|
|
191
|
+
const image = await this.requireRemoteImage(params);
|
|
192
|
+
const mode = optionalString(params.mode) ?? "uhd-plus";
|
|
193
|
+
if (mode === "uhd-plus") {
|
|
194
|
+
const contentId = resolveNumericContentId(optionalString(params.content_id));
|
|
195
|
+
const summary = await this.callAsyncTool("super-resolution-plus", withOptionalContentId({
|
|
196
|
+
image: image.url,
|
|
197
|
+
}, contentId.value), options);
|
|
198
|
+
return resultFromSummary("super-resolution-plus", summary, {
|
|
199
|
+
mode,
|
|
200
|
+
content_id: contentId.value,
|
|
201
|
+
content_id_generated: contentId.generated,
|
|
202
|
+
...(contentId.requested && contentId.requested !== contentId.value
|
|
203
|
+
? { requested_content_id: contentId.requested }
|
|
204
|
+
: {}),
|
|
205
|
+
uploaded: image.uploaded,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
const sceneCode = mode === "hd" ? "super-resolution" : "super-resolution-4x";
|
|
209
|
+
const create = unwrapEnvelope(await this.client.post("/gdesign/tool/ai/super-resolution", {
|
|
210
|
+
url: image.url,
|
|
211
|
+
scene_code: sceneCode,
|
|
212
|
+
}));
|
|
213
|
+
if (create.code !== 0 || !create.task_id) {
|
|
214
|
+
throw new Error(create.message || "创建超分任务失败");
|
|
215
|
+
}
|
|
216
|
+
if (options.async) {
|
|
217
|
+
return submitted(create.task_id, "super-resolution", {
|
|
218
|
+
mode,
|
|
219
|
+
scene_code: sceneCode,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return this.pollSuperResolution(create.task_id, sceneCode, options);
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
async requireRemoteImage(params) {
|
|
226
|
+
const value = params.image ?? params.image_url ?? params.image_file ?? params.file;
|
|
227
|
+
const image = await this.remoteMedia(value, params);
|
|
228
|
+
if (!image) {
|
|
229
|
+
throw new Error("需要提供图片:--image / --image-url / --image-file / --file");
|
|
230
|
+
}
|
|
231
|
+
return image;
|
|
232
|
+
}
|
|
233
|
+
async optionalRemoteImage(params) {
|
|
234
|
+
const value = params.image ?? params.image_url ?? params.image_file ?? params.file;
|
|
235
|
+
return this.remoteMedia(value, params);
|
|
236
|
+
}
|
|
237
|
+
async remoteMedia(value, params) {
|
|
238
|
+
const input = optionalString(value);
|
|
239
|
+
if (!input)
|
|
240
|
+
return null;
|
|
241
|
+
if (/^https?:\/\//i.test(input)) {
|
|
242
|
+
return {
|
|
243
|
+
url: assertSafeHttpUrl(input, { context: "媒体 URL" }),
|
|
244
|
+
source: "url",
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const filePath = resolve(process.cwd(), input);
|
|
248
|
+
if (!existsSync(filePath)) {
|
|
249
|
+
throw new Error(`文件不存在或不是 URL: ${input}`);
|
|
250
|
+
}
|
|
251
|
+
const uploaded = await this.dam.uploadLocalAsset(filePath, {
|
|
252
|
+
allowSensitivePath: allowSensitivePath(params),
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
url: uploaded.url,
|
|
256
|
+
source: "uploaded",
|
|
257
|
+
uploaded,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
async uploadPngBuffer(buffer) {
|
|
261
|
+
const dir = await mkdtemp(join(tmpdir(), "gd-cli-matting-"));
|
|
262
|
+
const filePath = join(dir, `cutout-${Date.now()}.png`);
|
|
263
|
+
try {
|
|
264
|
+
await writeFile(filePath, buffer);
|
|
265
|
+
return await this.dam.uploadLocalAsset(filePath);
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
await rm(dir, { recursive: true, force: true });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
async resolveMattingMode(image, requestedMode, options) {
|
|
272
|
+
if (requestedMode === "portrait" || requestedMode === "product" || requestedMode === "graphic") {
|
|
273
|
+
return requestedMode;
|
|
274
|
+
}
|
|
275
|
+
if (requestedMode !== "auto") {
|
|
276
|
+
throw new Error("cutout 仅支持 --mode auto、portrait、product、graphic");
|
|
277
|
+
}
|
|
278
|
+
const summary = await this.callSyncTool("matting-classify", { image }, options);
|
|
279
|
+
const classifyType = Number(primaryText(summary));
|
|
280
|
+
const map = {
|
|
281
|
+
0: "graphic",
|
|
282
|
+
1: "portrait",
|
|
283
|
+
2: "product",
|
|
284
|
+
};
|
|
285
|
+
const mode = map[classifyType];
|
|
286
|
+
if (!mode)
|
|
287
|
+
throw new Error(`无法识别抠图分类结果: ${classifyType}`);
|
|
288
|
+
return mode;
|
|
289
|
+
}
|
|
290
|
+
async prepareVideoReferenceImage(image, options) {
|
|
291
|
+
await ensureReferenceImageDownloadable(image, "image");
|
|
292
|
+
const upscale = await this.handlers.upscale({ image: image.url, mode: "hd" }, {
|
|
293
|
+
...options,
|
|
294
|
+
async: false,
|
|
295
|
+
timeout: Math.max(options.timeout ?? 0, 600),
|
|
296
|
+
});
|
|
297
|
+
const hdUrl = taskOutputString(upscale, "url", "视频参考图高清化结果缺少 url");
|
|
298
|
+
const media = { url: hdUrl, source: "url" };
|
|
299
|
+
await ensureReferenceImageDownloadable(media, "video");
|
|
300
|
+
return {
|
|
301
|
+
media,
|
|
302
|
+
outputs: {
|
|
303
|
+
reference_image_url: image.url,
|
|
304
|
+
reference_image_source: image.source,
|
|
305
|
+
video_reference_image_url: hdUrl,
|
|
306
|
+
video_reference_preprocess: {
|
|
307
|
+
tool: "image.super-res",
|
|
308
|
+
mode: "hd",
|
|
309
|
+
status: upscale.status,
|
|
310
|
+
outputs: upscale.outputs,
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
async invokeModelOrderedAsyncTool(payload) {
|
|
316
|
+
const legacyStyleCode = optionalString(payload.params.style_code);
|
|
317
|
+
const explicitModel = optionalString(payload.params.model);
|
|
318
|
+
const explicitModelCode = resolveRequestedModelCode(payload.modelType, explicitModel);
|
|
319
|
+
const configuredModelCode = explicitModel === undefined
|
|
320
|
+
? resolveRequestedModelCode(payload.modelType, getConfiguredModel(payload.modelType))
|
|
321
|
+
: undefined;
|
|
322
|
+
const sceneCodes = legacyStyleCode
|
|
323
|
+
? [legacyStyleCode]
|
|
324
|
+
: await this.resolveDailyCachedModelOrder(payload.modelType, explicitModel);
|
|
325
|
+
const strictSingleModel = Boolean(legacyStyleCode);
|
|
326
|
+
const attemptedModels = [];
|
|
327
|
+
let lastError;
|
|
328
|
+
for (const sceneCode of sceneCodes) {
|
|
329
|
+
attemptedModels.push(sceneCode);
|
|
330
|
+
let apiPath = "/ai-agent/v1/tool-intent";
|
|
331
|
+
let resolvedTool;
|
|
332
|
+
try {
|
|
333
|
+
const toolIntent = await this.resolveToolIntent({
|
|
334
|
+
prompt: payload.promptItems,
|
|
335
|
+
scene_code: sceneCode,
|
|
336
|
+
parameters: payload.parameters,
|
|
337
|
+
});
|
|
338
|
+
resolvedTool = toolIntent.sceneCode;
|
|
339
|
+
apiPath = "/gdesign/tool/v1/dify/call_async";
|
|
340
|
+
const contentId = resolveNumericContentId(optionalString(payload.params.content_id));
|
|
341
|
+
const summary = await this.callAsyncTool(toolIntent.sceneCode, withOptionalContentId({
|
|
342
|
+
...normalizeMediaUrlArguments(toolIntent.argumentsPayload),
|
|
343
|
+
}, contentId.value), payload.options);
|
|
344
|
+
return resultFromSummary(toolIntent.sceneCode, summary, {
|
|
345
|
+
content_id: contentId.value,
|
|
346
|
+
content_id_generated: contentId.generated,
|
|
347
|
+
...(contentId.requested && contentId.requested !== contentId.value
|
|
348
|
+
? { requested_content_id: contentId.requested }
|
|
349
|
+
: {}),
|
|
350
|
+
style_code: sceneCode,
|
|
351
|
+
selected_model: selectedModelOutput(payload.modelType, sceneCode, {
|
|
352
|
+
source: legacyStyleCode
|
|
353
|
+
? "style_code"
|
|
354
|
+
: explicitModelCode
|
|
355
|
+
? "model"
|
|
356
|
+
: configuredModelCode
|
|
357
|
+
? "settings"
|
|
358
|
+
: "platform",
|
|
359
|
+
}),
|
|
360
|
+
attempted_models: attemptedModels,
|
|
361
|
+
uploaded: payload.uploaded,
|
|
362
|
+
...(payload.extraOutputs ?? {}),
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
const contextualError = maybeAttachEntitlementContext(error, buildEntitlementDiagnosticContext({
|
|
367
|
+
apiPath,
|
|
368
|
+
selectedModel: sceneCode,
|
|
369
|
+
resolvedTool,
|
|
370
|
+
attemptedModels,
|
|
371
|
+
}));
|
|
372
|
+
lastError = contextualError;
|
|
373
|
+
const isLast = attemptedModels.length >= sceneCodes.length;
|
|
374
|
+
if (strictSingleModel || isLast || !shouldRetryModelFallback(contextualError)) {
|
|
375
|
+
throw contextualError;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
380
|
+
}
|
|
381
|
+
async resolveDailyCachedModelOrder(type, explicitModel) {
|
|
382
|
+
const baseOrder = resolveModelOrder(type, explicitModel);
|
|
383
|
+
const preferredModelCode = resolvePreferredModelCode(type, explicitModel);
|
|
384
|
+
const settings = readSettings();
|
|
385
|
+
const date = todayKey();
|
|
386
|
+
const cached = settings.model_cache?.[type];
|
|
387
|
+
if (cached?.date === date && cached.codes.length > 0) {
|
|
388
|
+
return cached.source === "remote"
|
|
389
|
+
? orderFromPlatformModelCache(type, cached.codes, preferredModelCode)
|
|
390
|
+
: orderFromModelCache(baseOrder, cached.codes);
|
|
391
|
+
}
|
|
392
|
+
const staleRemoteCodes = cached?.source === "remote"
|
|
393
|
+
? activePlatformModelCodes(type, cached.codes.map((code) => ({ model: { code, status: 1 } })))
|
|
394
|
+
: [];
|
|
395
|
+
let remoteCodes;
|
|
396
|
+
try {
|
|
397
|
+
const models = unwrapEnvelope(await this.client.get("/mns/models", {
|
|
398
|
+
query: { codes: [MODEL_CATEGORY_CODES[type]] },
|
|
399
|
+
}));
|
|
400
|
+
const activeCodes = activePlatformModelCodes(type, models);
|
|
401
|
+
if (activeCodes.length > 0) {
|
|
402
|
+
remoteCodes = activeCodes;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
// MNS 只用于模型排序,刷新失败时不能阻断生成。
|
|
407
|
+
}
|
|
408
|
+
const cacheCodes = remoteCodes
|
|
409
|
+
?? (staleRemoteCodes.length > 0 ? staleRemoteCodes : modelEntriesForType(type).map((model) => model.code));
|
|
410
|
+
const source = remoteCodes || staleRemoteCodes.length > 0 ? "remote" : "fallback";
|
|
411
|
+
const latestSettings = readSettings();
|
|
412
|
+
writeSettings({
|
|
413
|
+
...latestSettings,
|
|
414
|
+
model_cache: {
|
|
415
|
+
...(latestSettings.model_cache ?? {}),
|
|
416
|
+
[type]: {
|
|
417
|
+
date,
|
|
418
|
+
codes: cacheCodes,
|
|
419
|
+
source,
|
|
420
|
+
fetched_at: remoteCodes
|
|
421
|
+
? new Date().toISOString()
|
|
422
|
+
: cached?.fetched_at ?? new Date().toISOString(),
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
return source === "remote"
|
|
427
|
+
? orderFromPlatformModelCache(type, cacheCodes, preferredModelCode)
|
|
428
|
+
: orderFromModelCache(baseOrder, cacheCodes);
|
|
429
|
+
}
|
|
430
|
+
async resolveSceneCode(explicit, categoryCode, errorMessage) {
|
|
431
|
+
if (explicit)
|
|
432
|
+
return explicit;
|
|
433
|
+
const models = unwrapEnvelope(await this.client.get("/mns/models", {
|
|
434
|
+
query: { codes: [categoryCode] },
|
|
435
|
+
}));
|
|
436
|
+
const active = models.find((item) => item.model?.code && item.model.status === 1);
|
|
437
|
+
const fallback = models.find((item) => item.model?.code);
|
|
438
|
+
const code = active?.model?.code ?? fallback?.model?.code;
|
|
439
|
+
if (!code)
|
|
440
|
+
throw new Error(errorMessage);
|
|
441
|
+
return code;
|
|
442
|
+
}
|
|
443
|
+
async resolveToolIntent(payload) {
|
|
444
|
+
const response = unwrapEnvelope(await this.client.post("/ai-agent/v1/tool-intent", payload));
|
|
445
|
+
if (!response.scene_code) {
|
|
446
|
+
throw new Error("tool-intent 没有返回 scene_code");
|
|
447
|
+
}
|
|
448
|
+
const args = typeof response.arguments === "string"
|
|
449
|
+
? safeJsonObject(response.arguments)
|
|
450
|
+
: response.arguments;
|
|
451
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) {
|
|
452
|
+
throw new Error("tool-intent 没有返回有效 arguments");
|
|
453
|
+
}
|
|
454
|
+
return {
|
|
455
|
+
sceneCode: response.scene_code,
|
|
456
|
+
argumentsPayload: args,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
async callSyncTool(name, args, options) {
|
|
460
|
+
const response = unwrapEnvelope(await this.client.post("/gdesign/tool/v1/dify/call_sync", buildToolCallBody(name, args)));
|
|
461
|
+
assertDifyOk(response);
|
|
462
|
+
if (hasFinalPayload(response))
|
|
463
|
+
return summarize(response);
|
|
464
|
+
if (!response.task_id)
|
|
465
|
+
throw new Error("call_sync 返回缺少 task_id");
|
|
466
|
+
if (options.async)
|
|
467
|
+
return summarize(response);
|
|
468
|
+
return this.pollDifyTask(response.task_id, true, options);
|
|
469
|
+
}
|
|
470
|
+
async callAsyncTool(name, args, options) {
|
|
471
|
+
const response = unwrapEnvelope(await this.client.post("/gdesign/tool/v1/dify/call_async", buildToolCallBody(name, args)));
|
|
472
|
+
assertDifyOk(response);
|
|
473
|
+
if (hasFinalPayload(response))
|
|
474
|
+
return summarize(response);
|
|
475
|
+
if (!response.task_id)
|
|
476
|
+
throw new Error("call_async 返回缺少 task_id");
|
|
477
|
+
if (options.async)
|
|
478
|
+
return summarize(response);
|
|
479
|
+
return this.pollDifyTask(response.task_id, false, options);
|
|
480
|
+
}
|
|
481
|
+
async pollDifyTask(taskId, syncTask, options) {
|
|
482
|
+
const timeoutMs = (options.timeout ?? 120) * 1000;
|
|
483
|
+
const startedAt = Date.now();
|
|
484
|
+
let transientFailures = 0;
|
|
485
|
+
while (true) {
|
|
486
|
+
const path = syncTask
|
|
487
|
+
? "/gdesign/tool/v1/dify/task/process"
|
|
488
|
+
: "/gdesign/tool/v1/dify/process";
|
|
489
|
+
let response;
|
|
490
|
+
try {
|
|
491
|
+
response = unwrapEnvelope(await this.client.get(path, {
|
|
492
|
+
query: { task_id: taskId },
|
|
493
|
+
}));
|
|
494
|
+
transientFailures = 0;
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
if (!isTransientNetworkError(error) ||
|
|
498
|
+
transientFailures >= 3 ||
|
|
499
|
+
Date.now() - startedAt >= timeoutMs) {
|
|
500
|
+
throw error;
|
|
501
|
+
}
|
|
502
|
+
transientFailures += 1;
|
|
503
|
+
await sleep(1000 * transientFailures);
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
assertDifyOk(response);
|
|
507
|
+
const summary = summarize(response);
|
|
508
|
+
if (summary.text.length > 0 || summary.resources.length > 0)
|
|
509
|
+
return summary;
|
|
510
|
+
if (summary.task_status && FAILURE_STATUSES.has(summary.task_status)) {
|
|
511
|
+
throw new Error(difyErrorMessage(response));
|
|
512
|
+
}
|
|
513
|
+
if (summary.task_status && !RUNNING_STATUSES.has(summary.task_status))
|
|
514
|
+
return summary;
|
|
515
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
516
|
+
throw new Error(`轮询超时,task_id=${taskId}`);
|
|
517
|
+
}
|
|
518
|
+
await sleep(syncTask ? 1000 : 2000);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
async pollSuperResolution(taskId, sceneCode, options) {
|
|
522
|
+
const timeoutMs = (options.timeout ?? 120) * 1000;
|
|
523
|
+
const startedAt = Date.now();
|
|
524
|
+
let transientFailures = 0;
|
|
525
|
+
while (true) {
|
|
526
|
+
let data;
|
|
527
|
+
try {
|
|
528
|
+
data = unwrapEnvelope(await this.client.post("/gdesign/tool/ai/super-resolution/search", { task_id: taskId }));
|
|
529
|
+
transientFailures = 0;
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
if (!isTransientNetworkError(error) ||
|
|
533
|
+
transientFailures >= 3 ||
|
|
534
|
+
Date.now() - startedAt >= timeoutMs) {
|
|
535
|
+
throw error;
|
|
536
|
+
}
|
|
537
|
+
transientFailures += 1;
|
|
538
|
+
await sleep(1000 * transientFailures);
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (data.code !== 0)
|
|
542
|
+
throw new Error(data.message || "查询超分任务失败");
|
|
543
|
+
if (data.status === "SUCCESS" && data.result_url) {
|
|
544
|
+
return completed(taskId, {
|
|
545
|
+
url: data.result_url,
|
|
546
|
+
scene_code: sceneCode,
|
|
547
|
+
mime_type: inferMimeTypeFromUrl(data.result_url),
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
if (data.status && !["PENDING", "RETRY", "STARTED"].includes(data.status)) {
|
|
551
|
+
throw new Error(data.message || `超分任务状态异常: ${data.status}`);
|
|
552
|
+
}
|
|
553
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
554
|
+
throw new Error(`超分轮询超时,task_id=${taskId}`);
|
|
555
|
+
}
|
|
556
|
+
await sleep(2000);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
function assertMattingSupportedLocalImage(uploaded) {
|
|
561
|
+
if (!uploaded)
|
|
562
|
+
return;
|
|
563
|
+
if (uploaded.format === "avif" || uploaded.format === "heic" || uploaded.format === "heif") {
|
|
564
|
+
throw new Error(`当前抠图后端不支持 ${uploaded.format.toUpperCase()} 图片。请先把 ${uploaded.filename} 转成 PNG/JPEG,或换用真实 PNG/JPEG 商品图。`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function assertRequiredReferenceImage(media, params, toolName) {
|
|
568
|
+
const required = booleanParam(params.require_image) ||
|
|
569
|
+
booleanParam(params.reference_image_required) ||
|
|
570
|
+
booleanParam(params.must_use_reference_image);
|
|
571
|
+
if (!required || media)
|
|
572
|
+
return;
|
|
573
|
+
throw new Error([
|
|
574
|
+
`REFERENCE_IMAGE_REQUIRED: ${toolName} 当前被声明为基于参考图执行,但没有拿到真实图片。`,
|
|
575
|
+
"请提供 --image / --image-url / --image-file / --file 指向原始附件、本地图片或可访问 URL。",
|
|
576
|
+
"不要根据图片描述重新生成替代参考图;只有用户明确要求重新生成相似参考图时才允许。",
|
|
577
|
+
].join("\n"));
|
|
578
|
+
}
|
|
579
|
+
function booleanParam(value) {
|
|
580
|
+
return value === true || value === "true" || value === "1" || value === 1;
|
|
581
|
+
}
|
|
582
|
+
function buildToolCallBody(name, args) {
|
|
583
|
+
return {
|
|
584
|
+
jsonrpc: "2.0",
|
|
585
|
+
id: 0,
|
|
586
|
+
method: "tools/call",
|
|
587
|
+
params: {
|
|
588
|
+
name,
|
|
589
|
+
arguments: args,
|
|
590
|
+
},
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
function withOptionalContentId(value, contentId) {
|
|
594
|
+
if (!contentId)
|
|
595
|
+
return value;
|
|
596
|
+
return {
|
|
597
|
+
...value,
|
|
598
|
+
content_id: contentId,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
export function resolveNumericContentId(requested) {
|
|
602
|
+
if (requested && isJavaLongLikeNumber(requested)) {
|
|
603
|
+
return { value: requested, generated: false, requested };
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
value: generateNumericContentId(),
|
|
607
|
+
generated: true,
|
|
608
|
+
requested,
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function isJavaLongLikeNumber(value) {
|
|
612
|
+
return /^\d{1,19}$/.test(value) && BigInt(value) <= 9223372036854775807n;
|
|
613
|
+
}
|
|
614
|
+
function generateNumericContentId() {
|
|
615
|
+
const suffix = Math.floor(Math.random() * 100000)
|
|
616
|
+
.toString()
|
|
617
|
+
.padStart(5, "0");
|
|
618
|
+
return `${Date.now()}${suffix}`;
|
|
619
|
+
}
|
|
620
|
+
function errorMessage(error) {
|
|
621
|
+
if (!error || typeof error !== "object")
|
|
622
|
+
return String(error);
|
|
623
|
+
return "message" in error && typeof error.message === "string" ? error.message : String(error);
|
|
624
|
+
}
|
|
625
|
+
function maybeAttachEntitlementContext(error, context) {
|
|
626
|
+
const entitlement = detectEntitlementIssue({
|
|
627
|
+
message: errorMessage(error),
|
|
628
|
+
detail: error,
|
|
629
|
+
});
|
|
630
|
+
if (!entitlement)
|
|
631
|
+
return error;
|
|
632
|
+
return attachDiagnosticContext(error, context);
|
|
633
|
+
}
|
|
634
|
+
function attachDiagnosticContext(error, context) {
|
|
635
|
+
const target = error instanceof Error ? error : new Error(String(error));
|
|
636
|
+
const existing = diagnosticContextFrom(error);
|
|
637
|
+
const merged = compact({
|
|
638
|
+
...(existing ?? {}),
|
|
639
|
+
...context,
|
|
640
|
+
});
|
|
641
|
+
if (Object.keys(merged).length > 0) {
|
|
642
|
+
target
|
|
643
|
+
.diagnostic_context = merged;
|
|
644
|
+
}
|
|
645
|
+
return target;
|
|
646
|
+
}
|
|
647
|
+
function diagnosticContextFrom(value) {
|
|
648
|
+
if (!value || typeof value !== "object")
|
|
649
|
+
return undefined;
|
|
650
|
+
const direct = value.diagnostic_context;
|
|
651
|
+
if (!direct || typeof direct !== "object" || Array.isArray(direct))
|
|
652
|
+
return undefined;
|
|
653
|
+
return direct;
|
|
654
|
+
}
|
|
655
|
+
function buildEntitlementDiagnosticContext(input) {
|
|
656
|
+
let endpointContext = {};
|
|
657
|
+
try {
|
|
658
|
+
const endpoints = resolveEndpoints();
|
|
659
|
+
endpointContext = {
|
|
660
|
+
environment: endpoints.active_env,
|
|
661
|
+
business_id: endpoints.business_id.value,
|
|
662
|
+
channel_id: endpoints.channel_id.value,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
catch {
|
|
666
|
+
endpointContext = {};
|
|
667
|
+
}
|
|
668
|
+
let credentialContext = {};
|
|
669
|
+
try {
|
|
670
|
+
const creds = new CredentialStoreV2().read();
|
|
671
|
+
credentialContext = {
|
|
672
|
+
org_id: creds?.current_org?.id,
|
|
673
|
+
org_name: creds?.current_org?.name,
|
|
674
|
+
user_id: creds?.user?.id,
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
credentialContext = {};
|
|
679
|
+
}
|
|
680
|
+
return compact({
|
|
681
|
+
...endpointContext,
|
|
682
|
+
...credentialContext,
|
|
683
|
+
api_path: input.apiPath,
|
|
684
|
+
selected_model: input.selectedModel,
|
|
685
|
+
resolved_tool: input.resolvedTool,
|
|
686
|
+
attempted_models: input.attemptedModels,
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
function todayKey() {
|
|
690
|
+
return new Date().toISOString().slice(0, 10);
|
|
691
|
+
}
|
|
692
|
+
function resolveRequestedModelCode(type, value) {
|
|
693
|
+
if (!value || value.trim().toLowerCase() === "auto")
|
|
694
|
+
return undefined;
|
|
695
|
+
return resolveModelEntry(type, value).code;
|
|
696
|
+
}
|
|
697
|
+
function resolvePreferredModelCode(type, explicitModel) {
|
|
698
|
+
if (explicitModel !== undefined) {
|
|
699
|
+
return resolveRequestedModelCode(type, explicitModel);
|
|
700
|
+
}
|
|
701
|
+
return resolveRequestedModelCode(type, getConfiguredModel(type));
|
|
702
|
+
}
|
|
703
|
+
function activePlatformModelCodes(type, models) {
|
|
704
|
+
const knownCodes = new Set(modelEntriesForType(type).map((model) => model.code));
|
|
705
|
+
return uniqueStrings(models
|
|
706
|
+
.map((item) => item.model)
|
|
707
|
+
.filter((model) => Boolean(model?.code))
|
|
708
|
+
.filter((model) => model.status === undefined || model.status === 1)
|
|
709
|
+
.map((model) => model.code)
|
|
710
|
+
.filter((code) => knownCodes.has(code)));
|
|
711
|
+
}
|
|
712
|
+
function orderFromPlatformModelCache(type, platformCodes, preferredModelCode) {
|
|
713
|
+
const platformOrder = activePlatformModelCodes(type, platformCodes.map((code) => ({ model: { code, status: 1 } })));
|
|
714
|
+
if (platformOrder.length === 0) {
|
|
715
|
+
throw new Error(`平台没有返回可用的${modelTypeLabel(type)}模型顺序`);
|
|
716
|
+
}
|
|
717
|
+
return uniqueStrings(preferredModelCode ? [preferredModelCode, ...platformOrder] : platformOrder);
|
|
718
|
+
}
|
|
719
|
+
function orderFromModelCache(baseOrder, cachedCodes) {
|
|
720
|
+
const available = new Set(cachedCodes);
|
|
721
|
+
const filtered = baseOrder.filter((code) => available.has(code));
|
|
722
|
+
return filtered.length > 0 ? filtered : baseOrder;
|
|
723
|
+
}
|
|
724
|
+
function modelTypeLabel(type) {
|
|
725
|
+
return type === "image" ? "图像" : "视频";
|
|
726
|
+
}
|
|
727
|
+
function uniqueStrings(values) {
|
|
728
|
+
return [...new Set(values.filter(Boolean))];
|
|
729
|
+
}
|
|
730
|
+
function shouldRetryModelFallback(error) {
|
|
731
|
+
if (isTransientNetworkError(error))
|
|
732
|
+
return true;
|
|
733
|
+
const message = errorMessage(error);
|
|
734
|
+
if (/没有可用的工具|tool-intent 没有返回|tool intent 没有返回|敏感|安全|审核|not[_\s-]?passed|audit|content|quota|额度|余额|point|credit|权限|permission|401|403|参数|必填|invalid|MEDIA_RESOURCE_UNREACHABLE/i.test(message)) {
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
return /tool-intent|scene_code|模型|model|unsupported|not support|暂不支持|第三方|三方|服务异常|timeout|超时|call_async|call_sync/i.test(message);
|
|
738
|
+
}
|
|
739
|
+
function selectedModelOutput(type, code, meta) {
|
|
740
|
+
return {
|
|
741
|
+
...(publicModelByCode(type, code) ?? { code }),
|
|
742
|
+
source: meta.source,
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
function mattingResultPath() {
|
|
746
|
+
const suffix = Math.random().toString(16).slice(2, 10);
|
|
747
|
+
return `temp/gd-cli-matting-${Date.now()}-${suffix}.png`;
|
|
748
|
+
}
|
|
749
|
+
function shouldComposeMattingMask(params) {
|
|
750
|
+
if (params.raw_mask === true || params.raw_mask === "true")
|
|
751
|
+
return false;
|
|
752
|
+
if (params.compose_mask === false || params.compose_mask === "false")
|
|
753
|
+
return false;
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
756
|
+
function buildMattingAssets(outputs, image) {
|
|
757
|
+
const assets = [];
|
|
758
|
+
if (typeof outputs.url === "string") {
|
|
759
|
+
assets.push({
|
|
760
|
+
role: "result",
|
|
761
|
+
label: outputs.composed ? "抠图后(透明 PNG)" : "抠图后图片",
|
|
762
|
+
type: "image",
|
|
763
|
+
url: outputs.url,
|
|
764
|
+
mime_type: outputs.mime_type,
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
if (typeof outputs.mask_url === "string" && outputs.mask_url !== outputs.url) {
|
|
768
|
+
assets.push({
|
|
769
|
+
role: "mask",
|
|
770
|
+
label: "抠图 mask",
|
|
771
|
+
type: "image",
|
|
772
|
+
url: outputs.mask_url,
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
assets.push({
|
|
776
|
+
role: "source",
|
|
777
|
+
label: "抠图前原图",
|
|
778
|
+
type: "image",
|
|
779
|
+
url: image.url,
|
|
780
|
+
mime_type: image.uploaded?.mime_type,
|
|
781
|
+
filename: image.uploaded?.filename,
|
|
782
|
+
});
|
|
783
|
+
return assets;
|
|
784
|
+
}
|
|
785
|
+
function getContent(response) {
|
|
786
|
+
return response.result?.content ?? [];
|
|
787
|
+
}
|
|
788
|
+
function summarize(response) {
|
|
789
|
+
const content = getContent(response);
|
|
790
|
+
const task = content.find((item) => item.type === "task")?.task;
|
|
791
|
+
return {
|
|
792
|
+
text: content
|
|
793
|
+
.filter((item) => item.type === "text" && item.text)
|
|
794
|
+
.map((item) => item.text),
|
|
795
|
+
resources: content
|
|
796
|
+
.filter((item) => item.type === "resource" && item.resource?.uri)
|
|
797
|
+
.map((item) => ({
|
|
798
|
+
url: item.resource?.uri,
|
|
799
|
+
mime_type: item.resource?.mimeType ?? item.resource?.mineType,
|
|
800
|
+
})),
|
|
801
|
+
task_id: response.task_id,
|
|
802
|
+
dify_task_id: response.dify_task_id,
|
|
803
|
+
task_status: task?.status,
|
|
804
|
+
raw: response,
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
function resultFromSummary(taskIdPrefix, summary, meta = {}) {
|
|
808
|
+
if (summary.resources.length > 0 || summary.text.length > 0) {
|
|
809
|
+
const first = summary.resources[0];
|
|
810
|
+
return completed(summary.task_id ?? taskIdPrefix, {
|
|
811
|
+
...(first ? { url: first.url, mime_type: first.mime_type } : {}),
|
|
812
|
+
...(summary.resources.length > 1 ? { urls: summary.resources.map((r) => r.url) } : {}),
|
|
813
|
+
...(summary.text.length > 0 ? { text: primaryText(summary) } : {}),
|
|
814
|
+
...(summary.dify_task_id ? { dify_task_id: summary.dify_task_id } : {}),
|
|
815
|
+
...compact(meta),
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
if (summary.task_id) {
|
|
819
|
+
return submitted(summary.task_id, "dify", {
|
|
820
|
+
dify_task_id: summary.dify_task_id,
|
|
821
|
+
task_status: summary.task_status,
|
|
822
|
+
...compact(meta),
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
return completed(taskIdPrefix, { ...compact(meta), raw: summary.raw });
|
|
826
|
+
}
|
|
827
|
+
function taskOutputString(result, key, message) {
|
|
828
|
+
const outputs = result.outputs;
|
|
829
|
+
const value = outputs?.[key];
|
|
830
|
+
if (typeof value === "string" && value.trim())
|
|
831
|
+
return value.trim();
|
|
832
|
+
throw new Error(message);
|
|
833
|
+
}
|
|
834
|
+
function completed(taskId, outputs) {
|
|
835
|
+
return {
|
|
836
|
+
task_id: taskId,
|
|
837
|
+
status: "completed",
|
|
838
|
+
outputs: compact(outputs),
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
function submitted(taskId, backend, outputs = {}) {
|
|
842
|
+
return {
|
|
843
|
+
task_id: taskId,
|
|
844
|
+
status: "submitted",
|
|
845
|
+
outputs: compact({
|
|
846
|
+
task_backend: backend,
|
|
847
|
+
...outputs,
|
|
848
|
+
}),
|
|
849
|
+
message: `任务已提交,task_id=${taskId}`,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
function hasFinalPayload(response) {
|
|
853
|
+
return getContent(response).some((item) => (item.type === "text" && item.text) ||
|
|
854
|
+
(item.type === "resource" && item.resource?.uri));
|
|
855
|
+
}
|
|
856
|
+
function assertDifyOk(response) {
|
|
857
|
+
if (response.error) {
|
|
858
|
+
throw new Error(`${response.error.code}: ${response.error.message}`);
|
|
859
|
+
}
|
|
860
|
+
if (response.result?.error || response.result?.isError) {
|
|
861
|
+
throw new Error(difyErrorMessage(response));
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
function difyErrorMessage(response) {
|
|
865
|
+
const content = getContent(response);
|
|
866
|
+
return (response.error?.message ??
|
|
867
|
+
content.find((item) => item.type === "text" && item.text)?.text ??
|
|
868
|
+
content.find((item) => item.type === "task" && item.task?.message)?.task?.message ??
|
|
869
|
+
"工具调用失败");
|
|
870
|
+
}
|
|
871
|
+
function primaryText(summary) {
|
|
872
|
+
return summary.text.join("\n").trim();
|
|
873
|
+
}
|
|
874
|
+
function buildPromptItems(prompt, images, names) {
|
|
875
|
+
const items = [
|
|
876
|
+
{
|
|
877
|
+
type: "text",
|
|
878
|
+
content: prompt.trim(),
|
|
879
|
+
},
|
|
880
|
+
];
|
|
881
|
+
images.forEach((image, index) => {
|
|
882
|
+
items.push({
|
|
883
|
+
type: "media",
|
|
884
|
+
url: image.url,
|
|
885
|
+
mime: image.uploaded?.mime_type ?? inferMimeTypeFromUrl(image.url),
|
|
886
|
+
name: names?.[index] ?? `参考图${index + 1}`,
|
|
887
|
+
});
|
|
888
|
+
});
|
|
889
|
+
return items;
|
|
890
|
+
}
|
|
891
|
+
async function ensureReferenceImageDownloadable(media, target) {
|
|
892
|
+
await probeReferenceImageUrl(media.url);
|
|
893
|
+
if (target === "video" && isRiskyVideoReferenceUrl(media.url)) {
|
|
894
|
+
throw new MediaResourceError("参考图当前 URL 可被本机下载,但视频生成服务无法稳定拉取这类签名或中转链接。请改用可访问的图片结果 URL,或先去掉 --image 使用文生视频。", media.url);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
export async function probeReferenceImageUrl(url) {
|
|
898
|
+
const safeUrl = assertSafeHttpUrl(url, { context: "参考图 URL" });
|
|
899
|
+
let response;
|
|
900
|
+
try {
|
|
901
|
+
response = await fetch(safeUrl, {
|
|
902
|
+
method: "GET",
|
|
903
|
+
headers: {
|
|
904
|
+
"Range": "bytes=0-4095",
|
|
905
|
+
"User-Agent": "gd-cli-reference-check/1.0",
|
|
906
|
+
},
|
|
907
|
+
signal: AbortSignal.timeout(15_000),
|
|
908
|
+
redirect: "follow",
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
catch (error) {
|
|
912
|
+
throw new MediaResourceError(`参考图无法被生成服务下载:${errorMessage(error)}`, safeUrl);
|
|
913
|
+
}
|
|
914
|
+
if (!response.ok && response.status !== 206) {
|
|
915
|
+
throw new MediaResourceError(`参考图无法被生成服务下载:HTTP ${response.status}`, safeUrl);
|
|
916
|
+
}
|
|
917
|
+
const contentType = response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
918
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
919
|
+
if (bytes.byteLength === 0) {
|
|
920
|
+
throw new MediaResourceError("参考图无法被生成服务下载:文件为空", safeUrl);
|
|
921
|
+
}
|
|
922
|
+
if (contentType && /^(text\/html|application\/json|text\/plain|application\/xml|text\/xml)$/.test(contentType)) {
|
|
923
|
+
throw new MediaResourceError(`参考图 URL 返回的不是图片内容:${contentType}`, safeUrl);
|
|
924
|
+
}
|
|
925
|
+
if (!contentType && !hasImageExtension(safeUrl) && !looksLikeImageBytes(bytes)) {
|
|
926
|
+
throw new MediaResourceError("参考图 URL 返回的内容不像图片文件", safeUrl);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
export function isSignedDamReferenceUrl(url) {
|
|
930
|
+
try {
|
|
931
|
+
const parsed = new URL(url);
|
|
932
|
+
return (parsed.searchParams.has("auth_key") &&
|
|
933
|
+
/(^|\.)dancf\.com$/i.test(parsed.hostname) &&
|
|
934
|
+
/gdesign-dam|dam|gd.*dam/i.test(parsed.hostname));
|
|
935
|
+
}
|
|
936
|
+
catch {
|
|
937
|
+
return false;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
export function isRiskyVideoReferenceUrl(url) {
|
|
941
|
+
try {
|
|
942
|
+
const parsed = new URL(url);
|
|
943
|
+
const host = parsed.hostname.toLowerCase();
|
|
944
|
+
if (host.includes("matting-batch.dancf.com"))
|
|
945
|
+
return false;
|
|
946
|
+
if (isSignedDamReferenceUrl(url))
|
|
947
|
+
return true;
|
|
948
|
+
if (host.includes("dify-grsai"))
|
|
949
|
+
return true;
|
|
950
|
+
if (parsed.searchParams.has("auth_key") && host.endsWith("dancf.com"))
|
|
951
|
+
return true;
|
|
952
|
+
return false;
|
|
953
|
+
}
|
|
954
|
+
catch {
|
|
955
|
+
return true;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
function hasImageExtension(url) {
|
|
959
|
+
try {
|
|
960
|
+
return /\.(png|jpe?g|webp|gif|bmp|tiff?|svg)(\?|$)/i.test(new URL(url).pathname);
|
|
961
|
+
}
|
|
962
|
+
catch {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
function looksLikeImageBytes(bytes) {
|
|
967
|
+
if (bytes.length < 4)
|
|
968
|
+
return false;
|
|
969
|
+
if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff)
|
|
970
|
+
return true;
|
|
971
|
+
if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47)
|
|
972
|
+
return true;
|
|
973
|
+
if (bytes.slice(0, 4).toString("ascii") === "RIFF" && bytes.slice(8, 12).toString("ascii") === "WEBP")
|
|
974
|
+
return true;
|
|
975
|
+
if (bytes.slice(0, 3).toString("ascii") === "GIF")
|
|
976
|
+
return true;
|
|
977
|
+
return false;
|
|
978
|
+
}
|
|
979
|
+
function pickStringParams(params, keys) {
|
|
980
|
+
const picked = {};
|
|
981
|
+
for (const key of keys) {
|
|
982
|
+
const value = optionalString(params[key]);
|
|
983
|
+
if (value)
|
|
984
|
+
picked[key] = value;
|
|
985
|
+
}
|
|
986
|
+
return Object.keys(picked).length ? picked : undefined;
|
|
987
|
+
}
|
|
988
|
+
export function normalizeMediaUrlArguments(args) {
|
|
989
|
+
const normalized = Object.fromEntries(Object.entries(args).map(([key, value]) => [key, normalizeMaybeUrlValue(key, value)]));
|
|
990
|
+
return withInferredPreviewUrl(normalized);
|
|
991
|
+
}
|
|
992
|
+
function normalizeMaybeUrlValue(key, value) {
|
|
993
|
+
if (Array.isArray(value)) {
|
|
994
|
+
const normalized = value.map((item) => typeof item === "string" ? normalizeMaybeUrlString(key, item) : item);
|
|
995
|
+
if (shouldStringifyUrlArray(key))
|
|
996
|
+
return JSON.stringify(normalized);
|
|
997
|
+
if (normalized.length === 1 && typeof normalized[0] === "string") {
|
|
998
|
+
return normalized[0];
|
|
999
|
+
}
|
|
1000
|
+
if (shouldKeepUrlArray(key))
|
|
1001
|
+
return normalized;
|
|
1002
|
+
return normalized;
|
|
1003
|
+
}
|
|
1004
|
+
if (typeof value === "string") {
|
|
1005
|
+
const parsedArray = shouldKeepUrlArray(key) ? parseStringArray(value) : null;
|
|
1006
|
+
if (parsedArray)
|
|
1007
|
+
return normalizeMaybeUrlValue(key, parsedArray);
|
|
1008
|
+
return normalizeMaybeUrlString(key, value);
|
|
1009
|
+
}
|
|
1010
|
+
return value;
|
|
1011
|
+
}
|
|
1012
|
+
function normalizeMaybeUrlString(key, value) {
|
|
1013
|
+
const stripped = stripBracketedSingleUrl(value);
|
|
1014
|
+
if (/^https?:\/\//i.test(stripped)) {
|
|
1015
|
+
return assertSafeHttpUrl(stripped, { context: `${key} URL` });
|
|
1016
|
+
}
|
|
1017
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(stripped) && isUrlLikeKey(key)) {
|
|
1018
|
+
throw new Error(`${key} 仅支持 http/https URL`);
|
|
1019
|
+
}
|
|
1020
|
+
return stripped;
|
|
1021
|
+
}
|
|
1022
|
+
function parseStringArray(value) {
|
|
1023
|
+
const trimmed = value.trim();
|
|
1024
|
+
if (!trimmed.startsWith("["))
|
|
1025
|
+
return null;
|
|
1026
|
+
try {
|
|
1027
|
+
const parsed = JSON.parse(trimmed);
|
|
1028
|
+
return Array.isArray(parsed) && parsed.every((item) => typeof item === "string")
|
|
1029
|
+
? parsed
|
|
1030
|
+
: null;
|
|
1031
|
+
}
|
|
1032
|
+
catch {
|
|
1033
|
+
return null;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
function isUrlLikeKey(key) {
|
|
1037
|
+
const normalized = key.toLowerCase();
|
|
1038
|
+
return normalized.includes("url") || normalized.includes("uri") || normalized === "image" || normalized === "video";
|
|
1039
|
+
}
|
|
1040
|
+
function shouldKeepUrlArray(key) {
|
|
1041
|
+
const normalized = key.toLowerCase();
|
|
1042
|
+
return (normalized === "images" ||
|
|
1043
|
+
normalized === "videos" ||
|
|
1044
|
+
normalized === "files" ||
|
|
1045
|
+
normalized.endsWith("_urls") ||
|
|
1046
|
+
normalized.endsWith("urls") ||
|
|
1047
|
+
normalized.endsWith("_images") ||
|
|
1048
|
+
normalized.endsWith("_videos") ||
|
|
1049
|
+
normalized.endsWith("_files"));
|
|
1050
|
+
}
|
|
1051
|
+
function shouldStringifyUrlArray(key) {
|
|
1052
|
+
const normalized = key.toLowerCase();
|
|
1053
|
+
return normalized === "image_urls" || normalized.endsWith("_urls");
|
|
1054
|
+
}
|
|
1055
|
+
function withInferredPreviewUrl(args) {
|
|
1056
|
+
if (typeof args.preview === "string" && /^https?:\/\//.test(args.preview))
|
|
1057
|
+
return args;
|
|
1058
|
+
const firstImageUrl = firstUrlFrom(args.image_urls) ?? firstUrlFrom(args.images);
|
|
1059
|
+
if (!firstImageUrl)
|
|
1060
|
+
return args;
|
|
1061
|
+
return {
|
|
1062
|
+
...args,
|
|
1063
|
+
preview: firstImageUrl,
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
function firstUrlFrom(value) {
|
|
1067
|
+
if (typeof value === "string" && /^https?:\/\//.test(value))
|
|
1068
|
+
return value;
|
|
1069
|
+
if (typeof value === "string" && value.trim().startsWith("[")) {
|
|
1070
|
+
try {
|
|
1071
|
+
const parsed = JSON.parse(value);
|
|
1072
|
+
return firstUrlFrom(parsed);
|
|
1073
|
+
}
|
|
1074
|
+
catch {
|
|
1075
|
+
return undefined;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
if (!Array.isArray(value))
|
|
1079
|
+
return undefined;
|
|
1080
|
+
return value.find((item) => typeof item === "string" && /^https?:\/\//.test(item));
|
|
1081
|
+
}
|
|
1082
|
+
function stripBracketedSingleUrl(value) {
|
|
1083
|
+
const trimmed = value.trim();
|
|
1084
|
+
const bracketed = trimmed.match(/^\[(https?:\/\/[^\]]+)\]$/);
|
|
1085
|
+
return bracketed?.[1] ?? trimmed;
|
|
1086
|
+
}
|
|
1087
|
+
function requiredString(value, message) {
|
|
1088
|
+
const normalized = optionalString(value);
|
|
1089
|
+
if (!normalized)
|
|
1090
|
+
throw new Error(message);
|
|
1091
|
+
return normalized;
|
|
1092
|
+
}
|
|
1093
|
+
function optionalString(value) {
|
|
1094
|
+
if (typeof value !== "string")
|
|
1095
|
+
return undefined;
|
|
1096
|
+
const trimmed = value.trim();
|
|
1097
|
+
return trimmed ? trimmed : undefined;
|
|
1098
|
+
}
|
|
1099
|
+
function safeJsonObject(value) {
|
|
1100
|
+
try {
|
|
1101
|
+
const parsed = JSON.parse(value);
|
|
1102
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
1103
|
+
? parsed
|
|
1104
|
+
: null;
|
|
1105
|
+
}
|
|
1106
|
+
catch {
|
|
1107
|
+
return null;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
function normalizeToolName(name) {
|
|
1111
|
+
const map = {
|
|
1112
|
+
"text.title": "title",
|
|
1113
|
+
"text.prompt": "prompt",
|
|
1114
|
+
"text.prompt-advise": "prompt",
|
|
1115
|
+
"prompt-advise": "prompt",
|
|
1116
|
+
"image.generate": "generate",
|
|
1117
|
+
"image.cutout": "cutout",
|
|
1118
|
+
"image.matting": "cutout",
|
|
1119
|
+
matting: "cutout",
|
|
1120
|
+
"image.svg": "vectorize",
|
|
1121
|
+
svg: "vectorize",
|
|
1122
|
+
"image.super-res": "upscale",
|
|
1123
|
+
"super-res": "upscale",
|
|
1124
|
+
"asset.upload": "upload",
|
|
1125
|
+
"cdn.upload": "upload",
|
|
1126
|
+
"dam.upload": "upload",
|
|
1127
|
+
"video.generate": "video",
|
|
1128
|
+
};
|
|
1129
|
+
return map[name] ?? name;
|
|
1130
|
+
}
|
|
1131
|
+
function allowSensitivePath(params) {
|
|
1132
|
+
return (params.allow_sensitive_path === true ||
|
|
1133
|
+
params.allowSensitivePath === true ||
|
|
1134
|
+
params["allow-sensitive-path"] === true);
|
|
1135
|
+
}
|
|
1136
|
+
function inferMimeTypeFromUrl(url) {
|
|
1137
|
+
const pathname = new URL(url).pathname.toLowerCase();
|
|
1138
|
+
if (pathname.endsWith(".svg"))
|
|
1139
|
+
return "image/svg+xml";
|
|
1140
|
+
if (pathname.endsWith(".png"))
|
|
1141
|
+
return "image/png";
|
|
1142
|
+
if (pathname.endsWith(".webp"))
|
|
1143
|
+
return "image/webp";
|
|
1144
|
+
if (pathname.endsWith(".gif"))
|
|
1145
|
+
return "image/gif";
|
|
1146
|
+
if (/\.(mp4|webm|mov|m4v)$/i.test(pathname))
|
|
1147
|
+
return "video/mp4";
|
|
1148
|
+
return "image/jpeg";
|
|
1149
|
+
}
|
|
1150
|
+
function compact(value) {
|
|
1151
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
1152
|
+
}
|
|
1153
|
+
function sleep(ms) {
|
|
1154
|
+
return new Promise((resolve) => globalThis.setTimeout(resolve, ms));
|
|
1155
|
+
}
|