@gaoding/cli 0.0.0 → 1.0.0-alpha.25
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/LICENSE +21 -0
- package/README.md +40 -3
- package/contracts/operations/agent.send/input.schema.json +353 -0
- package/contracts/operations/agent.send/output.schema.json +271 -0
- package/contracts/operations/auth.status/input.schema.json +7 -0
- package/contracts/operations/auth.status/output.schema.json +132 -0
- package/contracts/operations/dam.copy/input.schema.json +19 -0
- package/contracts/operations/dam.copy/output.schema.json +19 -0
- package/contracts/operations/dam.delete/input.schema.json +23 -0
- package/contracts/operations/dam.delete/output.schema.json +6 -0
- package/contracts/operations/dam.download/input.schema.json +14 -0
- package/contracts/operations/dam.download/output.schema.json +25 -0
- package/contracts/operations/dam.folder.list/input.schema.json +15 -0
- package/contracts/operations/dam.folder.list/output.schema.json +37 -0
- package/contracts/operations/dam.get/input.schema.json +16 -0
- package/contracts/operations/dam.get/output.schema.json +46 -0
- package/contracts/operations/dam.list/input.schema.json +44 -0
- package/contracts/operations/dam.list/output.schema.json +71 -0
- package/contracts/operations/dam.move/input.schema.json +19 -0
- package/contracts/operations/dam.move/output.schema.json +18 -0
- package/contracts/operations/dam.recycle.list/input.schema.json +13 -0
- package/contracts/operations/dam.recycle.list/output.schema.json +34 -0
- package/contracts/operations/dam.recycle.restore/input.schema.json +14 -0
- package/contracts/operations/dam.recycle.restore/output.schema.json +6 -0
- package/contracts/operations/dam.rename/input.schema.json +18 -0
- package/contracts/operations/dam.rename/output.schema.json +18 -0
- package/contracts/operations/dam.repository.list/input.schema.json +8 -0
- package/contracts/operations/dam.repository.list/output.schema.json +52 -0
- package/contracts/operations/dam.search/input.schema.json +46 -0
- package/contracts/operations/dam.search/output.schema.json +71 -0
- package/contracts/operations/dam.tag.list/input.schema.json +12 -0
- package/contracts/operations/dam.tag.list/output.schema.json +26 -0
- package/contracts/operations/dam.upload/input.schema.json +31 -0
- package/contracts/operations/dam.upload/output.schema.json +46 -0
- package/contracts/operations/editor.apply/input.schema.json +134 -0
- package/contracts/operations/editor.apply/output.schema.json +15 -0
- package/contracts/operations/editor.connect/input.schema.json +15 -0
- package/contracts/operations/editor.connect/output.schema.json +17 -0
- package/contracts/operations/editor.disconnect/input.schema.json +8 -0
- package/contracts/operations/editor.disconnect/output.schema.json +14 -0
- package/contracts/operations/editor.save/input.schema.json +8 -0
- package/contracts/operations/editor.save/output.schema.json +23 -0
- package/contracts/operations/editor.screenshot/input.schema.json +8 -0
- package/contracts/operations/editor.screenshot/output.schema.json +16 -0
- package/contracts/operations/editor.snapshot/input.schema.json +8 -0
- package/contracts/operations/editor.snapshot/output.schema.json +113 -0
- package/contracts/operations/model.get/input.schema.json +18 -0
- package/contracts/operations/model.get/output.schema.json +191 -0
- package/contracts/operations/model.list/input.schema.json +15 -0
- package/contracts/operations/model.list/output.schema.json +111 -0
- package/contracts/operations/org.current/input.schema.json +7 -0
- package/contracts/operations/org.current/output.schema.json +49 -0
- package/contracts/operations/org.list/input.schema.json +7 -0
- package/contracts/operations/org.list/output.schema.json +71 -0
- package/contracts/operations/tool.call/input.schema.json +35 -0
- package/contracts/operations/tool.call/output.schema.json +129 -0
- package/contracts/operations/tool.list/input.schema.json +7 -0
- package/contracts/operations/tool.list/output.schema.json +45 -0
- package/dist/bin/gd-cli.js +60 -0
- package/dist/bin/postinstall.js +42 -0
- package/dist/src/bootstrap/create-cli.js +112 -0
- package/dist/src/bootstrap/create-runtime.js +290 -0
- package/dist/src/bootstrap/validators.js +157 -0
- package/dist/src/cli/action-binding.js +109 -0
- package/dist/src/cli/agent-commands.js +49 -0
- package/dist/src/cli/auth-commands.js +55 -0
- package/dist/src/cli/dam-commands.js +469 -0
- package/dist/src/cli/editor-commands.js +73 -0
- package/dist/src/cli/errors.js +157 -0
- package/dist/src/cli/model-commands.js +61 -0
- package/dist/src/cli/org-commands.js +58 -0
- package/dist/src/cli/presenter.js +105 -0
- package/dist/src/cli/prompt.js +30 -0
- package/dist/src/cli/tool-commands.js +77 -0
- package/dist/src/cli/update-command.js +14 -0
- package/dist/src/contracts/schema.js +4 -0
- package/dist/src/features/agent/creative-agent-adapter.js +36 -0
- package/dist/src/features/agent/creative-protocol.js +432 -0
- package/dist/src/features/agent/creative-stream.js +127 -0
- package/dist/src/features/agent/use-cases.js +150 -0
- package/dist/src/features/auth/access-policy.js +101 -0
- package/dist/src/features/auth/credential-store.js +62 -0
- package/dist/src/features/auth/sso-service.js +194 -0
- package/dist/src/features/auth/state.js +129 -0
- package/dist/src/features/auth/use-cases.js +136 -0
- package/dist/src/features/dam/asset-projection.js +278 -0
- package/dist/src/features/dam/dam-api-adapter.js +562 -0
- package/dist/src/features/dam/file-downloader.js +242 -0
- package/dist/src/features/dam/object-storage.js +193 -0
- package/dist/src/features/dam/operation-executor.js +72 -0
- package/dist/src/features/dam/registered-uploader.js +247 -0
- package/dist/src/features/dam/repository-catalog.js +127 -0
- package/dist/src/features/dam/storage-upload.js +141 -0
- package/dist/src/features/dam/transient-uploader.js +17 -0
- package/dist/src/features/dam/use-cases.js +470 -0
- package/dist/src/features/editor/bridge-client.js +109 -0
- package/dist/src/features/editor/bridge-process.js +222 -0
- package/dist/src/features/editor/bridge-server.js +337 -0
- package/dist/src/features/editor/protocol.js +1 -0
- package/dist/src/features/editor/public-url.js +36 -0
- package/dist/src/features/editor/session-state.js +78 -0
- package/dist/src/features/editor/session.js +207 -0
- package/dist/src/features/editor/use-cases.js +88 -0
- package/dist/src/features/org/org-service.js +76 -0
- package/dist/src/features/org/use-cases.js +156 -0
- package/dist/src/features/skill/bundled-skills.js +55 -0
- package/dist/src/features/skill/installer.js +271 -0
- package/dist/src/features/tool/catalog.js +83 -0
- package/dist/src/features/tool/dynamic-schema.js +128 -0
- package/dist/src/features/tool/mns-catalog-adapter.js +265 -0
- package/dist/src/features/tool/tool-api-adapter.js +352 -0
- package/dist/src/features/tool/use-cases.js +177 -0
- package/dist/src/features/update/latest-version.js +37 -0
- package/dist/src/features/update/update-notification.js +262 -0
- package/dist/src/features/update/update-service.js +183 -0
- package/dist/src/platform/json-input.js +67 -0
- package/dist/src/platform/local-json-file.js +59 -0
- package/dist/src/platform/open-browser.js +8 -0
- package/dist/src/platform/redact.js +80 -0
- package/dist/src/platform/remote-error-evidence.js +80 -0
- package/dist/src/platform/remote-protocol.js +10 -0
- package/dist/src/platform/safe-upload-file.js +146 -0
- package/dist/src/platform/signature.js +20 -0
- package/dist/src/platform/signed-http-transport.js +143 -0
- package/dist/src/platform/url-safety.js +100 -0
- package/dist/src/telemetry/invocation.js +156 -0
- package/dist/src/telemetry/sls-sink.js +31 -0
- package/package.json +54 -8
- package/skills/gd-cli/SKILL.md +15 -0
- package/skills/gd-cli/references/auth-org.md +9 -0
- package/skills/gd-cli/references/creation.md +115 -0
- package/skills/gd-cli/references/dam.md +52 -0
- package/skills/gd-cli/references/editor.md +254 -0
- package/skills/gd-cli/references/errors.md +13 -0
- package/skills/gd-cli/references/update.md +13 -0
- package/index.js +0 -3
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { RemoteRequestError } from "../../platform/signed-http-transport.js";
|
|
2
|
+
export const ROOT_SCENE_CODES = [
|
|
3
|
+
"image-model-art",
|
|
4
|
+
"AI-image-generation-art",
|
|
5
|
+
"AI-video-generation",
|
|
6
|
+
"AI-text-generation"
|
|
7
|
+
];
|
|
8
|
+
const rootTools = {
|
|
9
|
+
"image-model-art": "image.generate",
|
|
10
|
+
"AI-image-generation-art": "image.generate",
|
|
11
|
+
"AI-video-generation": "video.generate",
|
|
12
|
+
"AI-text-generation": "text.generate"
|
|
13
|
+
};
|
|
14
|
+
const toolOrder = {
|
|
15
|
+
"image.generate": 0,
|
|
16
|
+
"video.generate": 1,
|
|
17
|
+
"text.generate": 2
|
|
18
|
+
};
|
|
19
|
+
const multiUriKeys = new Set([
|
|
20
|
+
"image_urls",
|
|
21
|
+
"reference_images",
|
|
22
|
+
"file_urls",
|
|
23
|
+
"video_urls",
|
|
24
|
+
"audio_urls"
|
|
25
|
+
]);
|
|
26
|
+
function record(value) {
|
|
27
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
28
|
+
? value
|
|
29
|
+
: undefined;
|
|
30
|
+
}
|
|
31
|
+
function text(value) {
|
|
32
|
+
if (typeof value !== "string")
|
|
33
|
+
return undefined;
|
|
34
|
+
const trimmed = value.trim();
|
|
35
|
+
return trimmed || undefined;
|
|
36
|
+
}
|
|
37
|
+
function requiredText(value) {
|
|
38
|
+
const result = text(value);
|
|
39
|
+
if (result === undefined)
|
|
40
|
+
throw new Error("invalid string");
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
function number(value, fallback = 0) {
|
|
44
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
45
|
+
}
|
|
46
|
+
function cleanText(value) {
|
|
47
|
+
const result = text(value);
|
|
48
|
+
if (result === undefined)
|
|
49
|
+
return undefined;
|
|
50
|
+
return text(result.replace(/\{icon:\{[^{}]*\}\}/g, ""));
|
|
51
|
+
}
|
|
52
|
+
function collectScenes(raw) {
|
|
53
|
+
const roots = new Map();
|
|
54
|
+
for (const value of raw) {
|
|
55
|
+
const item = record(value);
|
|
56
|
+
if (item === undefined)
|
|
57
|
+
throw new Error("invalid scene");
|
|
58
|
+
roots.set(requiredText(item.code), item);
|
|
59
|
+
}
|
|
60
|
+
const scenes = [];
|
|
61
|
+
const seen = new Set();
|
|
62
|
+
function visit(value, tool) {
|
|
63
|
+
const code = requiredText(value.code);
|
|
64
|
+
if (!seen.has(code)) {
|
|
65
|
+
seen.add(code);
|
|
66
|
+
scenes.push({ code, tool, sort: number(value.sort) });
|
|
67
|
+
}
|
|
68
|
+
if (value.children === undefined || value.children === null)
|
|
69
|
+
return;
|
|
70
|
+
if (!Array.isArray(value.children))
|
|
71
|
+
throw new Error("invalid children");
|
|
72
|
+
for (const child of value.children) {
|
|
73
|
+
const childRecord = record(child);
|
|
74
|
+
if (childRecord === undefined)
|
|
75
|
+
throw new Error("invalid child");
|
|
76
|
+
visit(childRecord, tool);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const rootCode of ROOT_SCENE_CODES) {
|
|
80
|
+
const root = roots.get(rootCode);
|
|
81
|
+
if (root !== undefined)
|
|
82
|
+
visit(root, rootTools[rootCode]);
|
|
83
|
+
}
|
|
84
|
+
if (scenes.length === 0)
|
|
85
|
+
throw new Error("empty scenes");
|
|
86
|
+
return scenes;
|
|
87
|
+
}
|
|
88
|
+
function parameter(value) {
|
|
89
|
+
const field = record(value);
|
|
90
|
+
if (field === undefined)
|
|
91
|
+
throw new Error("invalid parameter");
|
|
92
|
+
if (field.is_open !== true)
|
|
93
|
+
return undefined;
|
|
94
|
+
const name = requiredText(field.key);
|
|
95
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name))
|
|
96
|
+
return undefined;
|
|
97
|
+
const sourceType = requiredText(field.type);
|
|
98
|
+
const type = multiUriKeys.has(name)
|
|
99
|
+
? "uri[]"
|
|
100
|
+
: name === "start_frame" || name === "end_frame" || sourceType === "IMAGE"
|
|
101
|
+
? "uri"
|
|
102
|
+
: sourceType === "NUMBER"
|
|
103
|
+
? "number"
|
|
104
|
+
: sourceType === "TEXT" || sourceType === "INPUT" || sourceType === "SELECT"
|
|
105
|
+
? "string"
|
|
106
|
+
: undefined;
|
|
107
|
+
if (type === undefined)
|
|
108
|
+
throw new Error("unsupported parameter");
|
|
109
|
+
let options;
|
|
110
|
+
if (sourceType === "SELECT") {
|
|
111
|
+
if (!Array.isArray(field.options))
|
|
112
|
+
throw new Error("invalid options");
|
|
113
|
+
options = field.options.map((value) => {
|
|
114
|
+
const option = record(value);
|
|
115
|
+
if (option === undefined)
|
|
116
|
+
throw new Error("invalid option");
|
|
117
|
+
const label = text(option.label);
|
|
118
|
+
return {
|
|
119
|
+
value: requiredText(option.value),
|
|
120
|
+
...(label === undefined ? {} : { label })
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
if (options.length === 0)
|
|
124
|
+
throw new Error("empty options");
|
|
125
|
+
}
|
|
126
|
+
const description = text(field.description) ?? text(field.label);
|
|
127
|
+
const defaultValue = field.default_value;
|
|
128
|
+
const hasDefault = defaultValue !== undefined
|
|
129
|
+
&& defaultValue !== null
|
|
130
|
+
&& !(typeof defaultValue === "string" && defaultValue.trim() === "");
|
|
131
|
+
return {
|
|
132
|
+
name,
|
|
133
|
+
type,
|
|
134
|
+
required: field.required === true,
|
|
135
|
+
...(description === undefined ? {} : { description }),
|
|
136
|
+
...(hasDefault
|
|
137
|
+
? { default: defaultValue }
|
|
138
|
+
: {}),
|
|
139
|
+
...(options === undefined ? {} : { options })
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function range(values, unit) {
|
|
143
|
+
if (values.length === 0)
|
|
144
|
+
return undefined;
|
|
145
|
+
const bounds = { minimum: Math.min(...values), maximum: Math.max(...values) };
|
|
146
|
+
return unit === "gaodou" ? { unit, ...bounds } : { unit, ...bounds };
|
|
147
|
+
}
|
|
148
|
+
function normalizeModel(value, scenes) {
|
|
149
|
+
const item = record(value);
|
|
150
|
+
const model = record(item?.model);
|
|
151
|
+
if (item === undefined || model === undefined || !Array.isArray(item.skus)) {
|
|
152
|
+
throw new Error("invalid model");
|
|
153
|
+
}
|
|
154
|
+
const activeSkus = item.skus.map(record).filter((sku) => sku !== undefined && sku.status === 1);
|
|
155
|
+
if (activeSkus.length === 0)
|
|
156
|
+
return undefined;
|
|
157
|
+
const code = requiredText(model.code);
|
|
158
|
+
const title = requiredText(model.name);
|
|
159
|
+
if (!Array.isArray(model.scene_categories))
|
|
160
|
+
throw new Error("invalid categories");
|
|
161
|
+
const matches = model.scene_categories
|
|
162
|
+
.map((category) => scenes.get(requiredText(record(category)?.code)))
|
|
163
|
+
.filter((scene) => scene !== undefined)
|
|
164
|
+
.toSorted((left, right) => toolOrder[left.tool] - toolOrder[right.tool]
|
|
165
|
+
|| left.sort - right.sort);
|
|
166
|
+
const scene = matches[0];
|
|
167
|
+
if (scene === undefined)
|
|
168
|
+
throw new Error("unknown scene");
|
|
169
|
+
if (typeof model.input_field_settings !== "string")
|
|
170
|
+
throw new Error("missing settings");
|
|
171
|
+
const settings = JSON.parse(model.input_field_settings);
|
|
172
|
+
if (!Array.isArray(settings))
|
|
173
|
+
throw new Error("invalid settings");
|
|
174
|
+
const parameters = settings
|
|
175
|
+
.map(parameter)
|
|
176
|
+
.filter((item) => item !== undefined);
|
|
177
|
+
const description = cleanText(model.description);
|
|
178
|
+
const usageDescription = cleanText(model.tool_description);
|
|
179
|
+
const outputMediaTypes = Array.isArray(model.output_mime_types)
|
|
180
|
+
? model.output_mime_types.map(text).filter((item) => item !== undefined)
|
|
181
|
+
: [];
|
|
182
|
+
const costs = activeSkus
|
|
183
|
+
.map((sku) => sku.point_cost)
|
|
184
|
+
.filter((value) => typeof value === "number"
|
|
185
|
+
&& Number.isFinite(value) && value >= 0);
|
|
186
|
+
const times = activeSkus
|
|
187
|
+
.map((sku) => sku.estimated_time)
|
|
188
|
+
.filter((value) => typeof value === "number"
|
|
189
|
+
&& Number.isFinite(value) && value >= 0);
|
|
190
|
+
const cost = range(costs, "gaodou");
|
|
191
|
+
const estimatedTime = range(times, "second");
|
|
192
|
+
return {
|
|
193
|
+
tool: scene.tool,
|
|
194
|
+
model: code,
|
|
195
|
+
title,
|
|
196
|
+
...(description === undefined ? {} : { description }),
|
|
197
|
+
...(usageDescription === undefined ? {} : { usageDescription }),
|
|
198
|
+
...(outputMediaTypes.length === 0 ? {} : { outputMediaTypes }),
|
|
199
|
+
parameters,
|
|
200
|
+
...(cost?.unit === "gaodou" ? { cost } : {}),
|
|
201
|
+
...(estimatedTime?.unit === "second" ? { estimatedTime } : {}),
|
|
202
|
+
sceneSort: scene.sort,
|
|
203
|
+
modelSort: number(model.sort)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
export function createMnsCatalogAdapter(dependencies) {
|
|
207
|
+
return {
|
|
208
|
+
async load({ access, signal }) {
|
|
209
|
+
signal.throwIfAborted();
|
|
210
|
+
try {
|
|
211
|
+
const rawScenes = await dependencies.transport.getJson({
|
|
212
|
+
path: "/mns/scene-categories",
|
|
213
|
+
query: { codes: ROOT_SCENE_CODES.join(",") },
|
|
214
|
+
credential: access.credential,
|
|
215
|
+
organizationId: access.organizationId,
|
|
216
|
+
signal
|
|
217
|
+
});
|
|
218
|
+
if (!Array.isArray(rawScenes))
|
|
219
|
+
throw new Error("invalid scene response");
|
|
220
|
+
const scenes = collectScenes(rawScenes);
|
|
221
|
+
const rawModels = await dependencies.transport.getJson({
|
|
222
|
+
path: "/mns/models",
|
|
223
|
+
query: { codes: scenes.map((scene) => scene.code).join(",") },
|
|
224
|
+
credential: access.credential,
|
|
225
|
+
organizationId: access.organizationId,
|
|
226
|
+
signal
|
|
227
|
+
});
|
|
228
|
+
if (!Array.isArray(rawModels))
|
|
229
|
+
throw new Error("invalid model response");
|
|
230
|
+
const sceneMap = new Map(scenes.map((scene) => [scene.code, scene]));
|
|
231
|
+
const models = [];
|
|
232
|
+
const seen = new Set();
|
|
233
|
+
let invalidCount = 0;
|
|
234
|
+
for (const rawModel of rawModels) {
|
|
235
|
+
try {
|
|
236
|
+
const model = normalizeModel(rawModel, sceneMap);
|
|
237
|
+
if (model === undefined || seen.has(model.model))
|
|
238
|
+
continue;
|
|
239
|
+
seen.add(model.model);
|
|
240
|
+
models.push(model);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
invalidCount += 1;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
models.sort((left, right) => toolOrder[left.tool] - toolOrder[right.tool]
|
|
247
|
+
|| left.sceneSort - right.sceneSort
|
|
248
|
+
|| left.modelSort - right.modelSort
|
|
249
|
+
|| (left.model < right.model ? -1 : left.model > right.model ? 1 : 0));
|
|
250
|
+
return {
|
|
251
|
+
models,
|
|
252
|
+
warnings: invalidCount === 0
|
|
253
|
+
? []
|
|
254
|
+
: [`已忽略 ${invalidCount} 个无效模型配置。`]
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
signal.throwIfAborted();
|
|
259
|
+
if (error instanceof RemoteRequestError)
|
|
260
|
+
throw error;
|
|
261
|
+
throw new RemoteRequestError();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { randomInt } from "node:crypto";
|
|
2
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
3
|
+
import { RemoteRequestError } from "../../platform/signed-http-transport.js";
|
|
4
|
+
import { remoteErrorEvidence } from "../../platform/remote-error-evidence.js";
|
|
5
|
+
import { RemoteProtocolError } from "../../platform/remote-protocol.js";
|
|
6
|
+
const MAX_JAVA_LONG = 9223372036854775807n;
|
|
7
|
+
function isRecord(value) {
|
|
8
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function fail() {
|
|
11
|
+
throw new RemoteRequestError();
|
|
12
|
+
}
|
|
13
|
+
function protocolFailure(value) {
|
|
14
|
+
throw new RemoteProtocolError(remoteErrorEvidence(value));
|
|
15
|
+
}
|
|
16
|
+
function nonBlank(value) {
|
|
17
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
18
|
+
return fail();
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
function parseRecord(value) {
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(value);
|
|
24
|
+
if (!isRecord(parsed))
|
|
25
|
+
return fail();
|
|
26
|
+
return parsed;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error instanceof RemoteRequestError)
|
|
30
|
+
throw error;
|
|
31
|
+
return fail();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function contentId(value) {
|
|
35
|
+
if (!/^\d+$/u.test(value))
|
|
36
|
+
return fail();
|
|
37
|
+
const numeric = BigInt(value);
|
|
38
|
+
if (numeric > MAX_JAVA_LONG)
|
|
39
|
+
return fail();
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
function parseIntent(value) {
|
|
43
|
+
if (!isRecord(value))
|
|
44
|
+
return fail();
|
|
45
|
+
return {
|
|
46
|
+
name: nonBlank(value.name),
|
|
47
|
+
arguments: parseRecord(nonBlank(value.arguments))
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function isEmptyOptionalMedia(value) {
|
|
51
|
+
return value === undefined
|
|
52
|
+
|| value === null
|
|
53
|
+
|| value === ""
|
|
54
|
+
|| (Array.isArray(value) && value.length === 0)
|
|
55
|
+
|| (typeof value === "string" && value.trim() === "[]");
|
|
56
|
+
}
|
|
57
|
+
function withoutEmptyOptionalMedia(value, optionalMediaParameters) {
|
|
58
|
+
const normalized = { ...value };
|
|
59
|
+
for (const name of optionalMediaParameters) {
|
|
60
|
+
if (isEmptyOptionalMedia(normalized[name]))
|
|
61
|
+
delete normalized[name];
|
|
62
|
+
}
|
|
63
|
+
return normalized;
|
|
64
|
+
}
|
|
65
|
+
function submittedTask(value) {
|
|
66
|
+
if (!isRecord(value) || !isRecord(value.result))
|
|
67
|
+
return protocolFailure(value);
|
|
68
|
+
if (value.result.isError === true) {
|
|
69
|
+
throw new ToolTaskFailedError(remoteErrorEvidence(value));
|
|
70
|
+
}
|
|
71
|
+
if (value.result.isError !== false)
|
|
72
|
+
return protocolFailure(value);
|
|
73
|
+
if (typeof value.task_id !== "string" || value.task_id.trim() === "") {
|
|
74
|
+
return protocolFailure(value);
|
|
75
|
+
}
|
|
76
|
+
return value.task_id;
|
|
77
|
+
}
|
|
78
|
+
function resourceName(uri) {
|
|
79
|
+
let url;
|
|
80
|
+
try {
|
|
81
|
+
url = new URL(uri);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return protocolFailure(uri);
|
|
85
|
+
}
|
|
86
|
+
const segment = url.pathname.split("/").filter(Boolean).at(-1);
|
|
87
|
+
if (segment === undefined)
|
|
88
|
+
return protocolFailure(uri);
|
|
89
|
+
try {
|
|
90
|
+
const decoded = decodeURIComponent(segment);
|
|
91
|
+
if (decoded.trim() === "")
|
|
92
|
+
return protocolFailure(uri);
|
|
93
|
+
return decoded;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return protocolFailure(uri);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function normalizeContent(value) {
|
|
100
|
+
if (!isRecord(value))
|
|
101
|
+
return protocolFailure(value);
|
|
102
|
+
if (value.type === "text") {
|
|
103
|
+
if (typeof value.text !== "string" || value.text.trim() === "") {
|
|
104
|
+
return protocolFailure(value);
|
|
105
|
+
}
|
|
106
|
+
return { type: "text", text: value.text };
|
|
107
|
+
}
|
|
108
|
+
if (value.type !== "resource" || !isRecord(value.resource)) {
|
|
109
|
+
return protocolFailure(value);
|
|
110
|
+
}
|
|
111
|
+
if ("blob" in value.resource || "text" in value.resource)
|
|
112
|
+
return protocolFailure(value);
|
|
113
|
+
if (typeof value.resource.uri !== "string" || value.resource.uri.trim() === "") {
|
|
114
|
+
return protocolFailure(value);
|
|
115
|
+
}
|
|
116
|
+
const uri = value.resource.uri;
|
|
117
|
+
const mimeType = value.resource.mimeType;
|
|
118
|
+
if (mimeType !== undefined && (typeof mimeType !== "string" || mimeType.trim() === "")) {
|
|
119
|
+
return protocolFailure(value);
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
type: "resource_link",
|
|
123
|
+
uri,
|
|
124
|
+
name: resourceName(uri),
|
|
125
|
+
...(mimeType === undefined ? {} : { mimeType })
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
export class ToolTaskFailedError extends Error {
|
|
129
|
+
remoteCode;
|
|
130
|
+
remoteBody;
|
|
131
|
+
constructor(evidence = {}) {
|
|
132
|
+
super("稿定工具任务执行失败。");
|
|
133
|
+
this.name = "ToolTaskFailedError";
|
|
134
|
+
this.remoteCode = evidence.remoteCode;
|
|
135
|
+
this.remoteBody = evidence.remoteBody;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function failedPoll(error, difyTaskId) {
|
|
139
|
+
return {
|
|
140
|
+
state: "failed",
|
|
141
|
+
error,
|
|
142
|
+
...(difyTaskId === undefined ? {} : { difyTaskId })
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function optionalInnerDifyTaskId(value) {
|
|
146
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
147
|
+
return undefined;
|
|
148
|
+
try {
|
|
149
|
+
const parsed = JSON.parse(value);
|
|
150
|
+
if (!isRecord(parsed))
|
|
151
|
+
return undefined;
|
|
152
|
+
const difyTaskId = parsed.dify_task_id;
|
|
153
|
+
return typeof difyTaskId === "string" && difyTaskId.trim() !== ""
|
|
154
|
+
? difyTaskId
|
|
155
|
+
: undefined;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function isJsonRpcError(value) {
|
|
162
|
+
if (value.jsonrpc !== "2.0"
|
|
163
|
+
|| Object.prototype.hasOwnProperty.call(value, "result")
|
|
164
|
+
|| !isRecord(value.error)) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
return typeof value.error.code === "number"
|
|
168
|
+
&& Number.isInteger(value.error.code)
|
|
169
|
+
&& typeof value.error.message === "string"
|
|
170
|
+
&& value.error.message.trim() !== "";
|
|
171
|
+
}
|
|
172
|
+
function pollResult(value, taskId) {
|
|
173
|
+
if (!Array.isArray(value))
|
|
174
|
+
return protocolFailure(value);
|
|
175
|
+
const item = value.find((candidate) => isRecord(candidate) && candidate.task_id === taskId);
|
|
176
|
+
if (!isRecord(item))
|
|
177
|
+
return { state: "pending" };
|
|
178
|
+
if (typeof item.code !== "string" || item.code.trim() === "") {
|
|
179
|
+
return protocolFailure(item);
|
|
180
|
+
}
|
|
181
|
+
if (item.code !== "200") {
|
|
182
|
+
return failedPoll(new ToolTaskFailedError(remoteErrorEvidence(item)), optionalInnerDifyTaskId(item.result));
|
|
183
|
+
}
|
|
184
|
+
if (typeof item.result !== "string" || item.result.trim() === "") {
|
|
185
|
+
return protocolFailure(item);
|
|
186
|
+
}
|
|
187
|
+
let inner;
|
|
188
|
+
try {
|
|
189
|
+
const parsed = JSON.parse(item.result);
|
|
190
|
+
if (!isRecord(parsed))
|
|
191
|
+
return protocolFailure(item);
|
|
192
|
+
inner = parsed;
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
if (error instanceof RemoteProtocolError)
|
|
196
|
+
throw error;
|
|
197
|
+
return protocolFailure(item);
|
|
198
|
+
}
|
|
199
|
+
const difyTaskId = inner.dify_task_id;
|
|
200
|
+
if (difyTaskId !== undefined
|
|
201
|
+
&& (typeof difyTaskId !== "string" || difyTaskId.trim() === "")) {
|
|
202
|
+
return protocolFailure(inner);
|
|
203
|
+
}
|
|
204
|
+
if (Object.prototype.hasOwnProperty.call(inner, "error")) {
|
|
205
|
+
return isJsonRpcError(inner)
|
|
206
|
+
? failedPoll(new ToolTaskFailedError(remoteErrorEvidence(inner)), difyTaskId)
|
|
207
|
+
: failedPoll(new RemoteProtocolError(remoteErrorEvidence(inner)), difyTaskId);
|
|
208
|
+
}
|
|
209
|
+
if (!isRecord(inner.result)) {
|
|
210
|
+
return failedPoll(new RemoteProtocolError(remoteErrorEvidence(inner)), difyTaskId);
|
|
211
|
+
}
|
|
212
|
+
if (inner.result.isError === true) {
|
|
213
|
+
return failedPoll(new ToolTaskFailedError(remoteErrorEvidence(inner)), difyTaskId);
|
|
214
|
+
}
|
|
215
|
+
if (inner.result.isError !== false) {
|
|
216
|
+
return failedPoll(new RemoteProtocolError(remoteErrorEvidence(inner)), difyTaskId);
|
|
217
|
+
}
|
|
218
|
+
const content = inner.result.content;
|
|
219
|
+
if (content === undefined || (Array.isArray(content) && content.length === 0)) {
|
|
220
|
+
return {
|
|
221
|
+
state: "pending",
|
|
222
|
+
...(difyTaskId === undefined ? {} : { difyTaskId })
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
if (!Array.isArray(content)) {
|
|
226
|
+
return failedPoll(new RemoteProtocolError(remoteErrorEvidence(inner)), difyTaskId);
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
return {
|
|
230
|
+
state: "complete",
|
|
231
|
+
content: content.map(normalizeContent),
|
|
232
|
+
...(difyTaskId === undefined ? {} : { difyTaskId })
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
if (error instanceof RemoteProtocolError)
|
|
237
|
+
return failedPoll(error, difyTaskId);
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async function safePost(transport, request) {
|
|
242
|
+
try {
|
|
243
|
+
return await transport.postJson(request);
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
if (request.signal.aborted)
|
|
247
|
+
throw request.signal.reason;
|
|
248
|
+
if (error instanceof RemoteRequestError || error instanceof RemoteProtocolError)
|
|
249
|
+
throw error;
|
|
250
|
+
throw new RemoteRequestError();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async function defaultSleep(milliseconds, signal) {
|
|
254
|
+
signal.throwIfAborted();
|
|
255
|
+
try {
|
|
256
|
+
await delay(milliseconds, undefined, { signal });
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
if (signal.aborted)
|
|
260
|
+
throw signal.reason;
|
|
261
|
+
throw new RemoteRequestError();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function defaultContentId() {
|
|
265
|
+
return (BigInt(Date.now()) * 100000n + BigInt(randomInt(100_000))).toString();
|
|
266
|
+
}
|
|
267
|
+
export function createToolApiAdapter(dependencies) {
|
|
268
|
+
const createContentId = dependencies.contentIdFactory ?? defaultContentId;
|
|
269
|
+
const sleep = dependencies.sleep ?? defaultSleep;
|
|
270
|
+
return {
|
|
271
|
+
async execute({ invocation, access, signal }) {
|
|
272
|
+
signal.throwIfAborted();
|
|
273
|
+
const managedContentId = contentId(createContentId());
|
|
274
|
+
const resolved = invocation.kind === "direct"
|
|
275
|
+
? { name: invocation.name, arguments: invocation.arguments }
|
|
276
|
+
: await dependencies.telemetry.stage("intent", async () => {
|
|
277
|
+
const intent = parseIntent(await safePost(dependencies.transport, {
|
|
278
|
+
path: "/ai-agent/v1/tool-intent",
|
|
279
|
+
body: {
|
|
280
|
+
prompt: invocation.prompt,
|
|
281
|
+
scene_code: invocation.model,
|
|
282
|
+
parameters: invocation.parameters
|
|
283
|
+
},
|
|
284
|
+
credential: access.credential,
|
|
285
|
+
organizationId: access.organizationId,
|
|
286
|
+
signal
|
|
287
|
+
}));
|
|
288
|
+
return {
|
|
289
|
+
...intent,
|
|
290
|
+
arguments: withoutEmptyOptionalMedia(intent.arguments, invocation.optionalMediaParameters)
|
|
291
|
+
};
|
|
292
|
+
});
|
|
293
|
+
signal.throwIfAborted();
|
|
294
|
+
const taskId = await dependencies.telemetry.stage("submit", async () => {
|
|
295
|
+
dependencies.telemetry.annotate({ content_id: managedContentId });
|
|
296
|
+
return submittedTask(await safePost(dependencies.transport, {
|
|
297
|
+
path: "/gdesign/tool/v1/dify/call_async",
|
|
298
|
+
body: {
|
|
299
|
+
jsonrpc: "2.0",
|
|
300
|
+
id: 0,
|
|
301
|
+
method: "tools/call",
|
|
302
|
+
params: {
|
|
303
|
+
name: resolved.name,
|
|
304
|
+
arguments: { ...resolved.arguments, content_id: managedContentId }
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
credential: access.credential,
|
|
308
|
+
organizationId: access.organizationId,
|
|
309
|
+
signal
|
|
310
|
+
}));
|
|
311
|
+
});
|
|
312
|
+
dependencies.telemetry.annotate({ task_id: taskId });
|
|
313
|
+
return dependencies.telemetry.stage("wait", async () => {
|
|
314
|
+
let transientFailures = 0;
|
|
315
|
+
while (true) {
|
|
316
|
+
signal.throwIfAborted();
|
|
317
|
+
let response;
|
|
318
|
+
try {
|
|
319
|
+
response = await safePost(dependencies.transport, {
|
|
320
|
+
path: "/gdesign/tool/v1/dify/process/batch",
|
|
321
|
+
body: { task_ids: [taskId] },
|
|
322
|
+
credential: access.credential,
|
|
323
|
+
organizationId: access.organizationId,
|
|
324
|
+
signal
|
|
325
|
+
});
|
|
326
|
+
transientFailures = 0;
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
if (signal.aborted)
|
|
330
|
+
throw signal.reason;
|
|
331
|
+
if (!(error instanceof RemoteRequestError) || !error.transient)
|
|
332
|
+
throw error;
|
|
333
|
+
transientFailures += 1;
|
|
334
|
+
if (transientFailures > 3)
|
|
335
|
+
throw error;
|
|
336
|
+
await sleep(transientFailures * 1_000, signal);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const poll = pollResult(response, taskId);
|
|
340
|
+
if (poll.difyTaskId !== undefined) {
|
|
341
|
+
dependencies.telemetry.annotate({ dify_task_id: poll.difyTaskId });
|
|
342
|
+
}
|
|
343
|
+
if (poll.state === "failed")
|
|
344
|
+
throw poll.error;
|
|
345
|
+
if (poll.state === "complete")
|
|
346
|
+
return poll.content;
|
|
347
|
+
await sleep(2_000, signal);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
}
|