gaoding-cli 1.0.0-alpha.10
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 +38 -0
- package/contracts/operations/agent.send/input.schema.json +354 -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.delete/input.schema.json +23 -0
- package/contracts/operations/dam.delete/output.schema.json +6 -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.search/input.schema.json +46 -0
- package/contracts/operations/dam.search/output.schema.json +71 -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 +190 -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 +38 -0
- package/dist/bin/postinstall.js +42 -0
- package/dist/src/bootstrap/create-cli.js +55 -0
- package/dist/src/bootstrap/create-runtime.js +222 -0
- package/dist/src/bootstrap/validators.js +118 -0
- package/dist/src/cli/action-binding.js +63 -0
- package/dist/src/cli/agent-commands.js +39 -0
- package/dist/src/cli/auth-commands.js +47 -0
- package/dist/src/cli/dam-commands.js +207 -0
- package/dist/src/cli/editor-commands.js +73 -0
- package/dist/src/cli/errors.js +90 -0
- package/dist/src/cli/model-commands.js +47 -0
- package/dist/src/cli/org-commands.js +52 -0
- package/dist/src/cli/presenter.js +80 -0
- package/dist/src/cli/prompt.js +30 -0
- package/dist/src/cli/tool-commands.js +65 -0
- package/dist/src/cli/update-command.js +12 -0
- package/dist/src/contracts/schema.js +4 -0
- package/dist/src/features/agent/creative-agent-adapter.js +31 -0
- package/dist/src/features/agent/creative-protocol.js +324 -0
- package/dist/src/features/agent/creative-stream.js +127 -0
- package/dist/src/features/agent/use-cases.js +113 -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 +179 -0
- package/dist/src/features/auth/state.js +129 -0
- package/dist/src/features/auth/use-cases.js +106 -0
- package/dist/src/features/dam/asset-projection.js +270 -0
- package/dist/src/features/dam/dam-api-adapter.js +206 -0
- package/dist/src/features/dam/object-storage.js +191 -0
- package/dist/src/features/dam/registered-uploader.js +224 -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 +151 -0
- package/dist/src/features/editor/bridge-client.js +96 -0
- package/dist/src/features/editor/bridge-process.js +222 -0
- package/dist/src/features/editor/bridge-server.js +311 -0
- package/dist/src/features/editor/protocol.js +1 -0
- package/dist/src/features/editor/session-state.js +78 -0
- package/dist/src/features/editor/session.js +193 -0
- package/dist/src/features/editor/use-cases.js +61 -0
- package/dist/src/features/org/org-service.js +76 -0
- package/dist/src/features/org/use-cases.js +140 -0
- package/dist/src/features/skill/bundled-skills.js +55 -0
- package/dist/src/features/skill/installer.js +265 -0
- package/dist/src/features/tool/catalog.js +79 -0
- package/dist/src/features/tool/dynamic-schema.js +96 -0
- package/dist/src/features/tool/mns-catalog-adapter.js +262 -0
- package/dist/src/features/tool/tool-api-adapter.js +200 -0
- package/dist/src/features/tool/use-cases.js +126 -0
- package/dist/src/features/update/update-service.js +194 -0
- package/dist/src/platform/json-input.js +64 -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/safe-upload-file.js +146 -0
- package/dist/src/platform/signature.js +19 -0
- package/dist/src/platform/signed-http-transport.js +109 -0
- package/dist/src/platform/url-safety.js +100 -0
- package/package.json +56 -0
- 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 +38 -0
- package/skills/gd-cli/references/dam.md +20 -0
- package/skills/gd-cli/references/editor.md +12 -0
- package/skills/gd-cli/references/errors.md +10 -0
- package/skills/gd-cli/references/update.md +11 -0
|
@@ -0,0 +1,262 @@
|
|
|
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
|
+
"file_urls",
|
|
22
|
+
"video_urls",
|
|
23
|
+
"audio_urls"
|
|
24
|
+
]);
|
|
25
|
+
function record(value) {
|
|
26
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
27
|
+
? value
|
|
28
|
+
: undefined;
|
|
29
|
+
}
|
|
30
|
+
function text(value) {
|
|
31
|
+
if (typeof value !== "string")
|
|
32
|
+
return undefined;
|
|
33
|
+
const trimmed = value.trim();
|
|
34
|
+
return trimmed || undefined;
|
|
35
|
+
}
|
|
36
|
+
function requiredText(value) {
|
|
37
|
+
const result = text(value);
|
|
38
|
+
if (result === undefined)
|
|
39
|
+
throw new Error("invalid string");
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
function number(value, fallback = 0) {
|
|
43
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
44
|
+
}
|
|
45
|
+
function cleanText(value) {
|
|
46
|
+
const result = text(value);
|
|
47
|
+
if (result === undefined)
|
|
48
|
+
return undefined;
|
|
49
|
+
return text(result.replace(/\{icon:\{[^{}]*\}\}/g, ""));
|
|
50
|
+
}
|
|
51
|
+
function collectScenes(raw) {
|
|
52
|
+
const roots = new Map();
|
|
53
|
+
for (const value of raw) {
|
|
54
|
+
const item = record(value);
|
|
55
|
+
if (item === undefined)
|
|
56
|
+
throw new Error("invalid scene");
|
|
57
|
+
roots.set(requiredText(item.code), item);
|
|
58
|
+
}
|
|
59
|
+
const scenes = [];
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
function visit(value, tool) {
|
|
62
|
+
const code = requiredText(value.code);
|
|
63
|
+
if (!seen.has(code)) {
|
|
64
|
+
seen.add(code);
|
|
65
|
+
scenes.push({ code, tool, sort: number(value.sort) });
|
|
66
|
+
}
|
|
67
|
+
if (value.children === undefined || value.children === null)
|
|
68
|
+
return;
|
|
69
|
+
if (!Array.isArray(value.children))
|
|
70
|
+
throw new Error("invalid children");
|
|
71
|
+
for (const child of value.children) {
|
|
72
|
+
const childRecord = record(child);
|
|
73
|
+
if (childRecord === undefined)
|
|
74
|
+
throw new Error("invalid child");
|
|
75
|
+
visit(childRecord, tool);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const rootCode of ROOT_SCENE_CODES) {
|
|
79
|
+
const root = roots.get(rootCode);
|
|
80
|
+
if (root !== undefined)
|
|
81
|
+
visit(root, rootTools[rootCode]);
|
|
82
|
+
}
|
|
83
|
+
if (scenes.length === 0)
|
|
84
|
+
throw new Error("empty scenes");
|
|
85
|
+
return scenes;
|
|
86
|
+
}
|
|
87
|
+
function parameter(value) {
|
|
88
|
+
const field = record(value);
|
|
89
|
+
if (field === undefined)
|
|
90
|
+
throw new Error("invalid parameter");
|
|
91
|
+
if (field.is_open !== true)
|
|
92
|
+
return undefined;
|
|
93
|
+
const name = requiredText(field.key);
|
|
94
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name))
|
|
95
|
+
return undefined;
|
|
96
|
+
const sourceType = requiredText(field.type);
|
|
97
|
+
const type = multiUriKeys.has(name)
|
|
98
|
+
? "uri[]"
|
|
99
|
+
: name === "start_frame" || name === "end_frame" || sourceType === "IMAGE"
|
|
100
|
+
? "uri"
|
|
101
|
+
: sourceType === "TEXT" || sourceType === "INPUT" || sourceType === "SELECT"
|
|
102
|
+
? "string"
|
|
103
|
+
: undefined;
|
|
104
|
+
if (type === undefined)
|
|
105
|
+
throw new Error("unsupported parameter");
|
|
106
|
+
let options;
|
|
107
|
+
if (sourceType === "SELECT") {
|
|
108
|
+
if (!Array.isArray(field.options))
|
|
109
|
+
throw new Error("invalid options");
|
|
110
|
+
options = field.options.map((value) => {
|
|
111
|
+
const option = record(value);
|
|
112
|
+
if (option === undefined)
|
|
113
|
+
throw new Error("invalid option");
|
|
114
|
+
const label = text(option.label);
|
|
115
|
+
return {
|
|
116
|
+
value: requiredText(option.value),
|
|
117
|
+
...(label === undefined ? {} : { label })
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
if (options.length === 0)
|
|
121
|
+
throw new Error("empty options");
|
|
122
|
+
}
|
|
123
|
+
const description = text(field.label) ?? text(field.description);
|
|
124
|
+
const defaultValue = field.default_value;
|
|
125
|
+
const hasDefault = defaultValue !== undefined
|
|
126
|
+
&& defaultValue !== null
|
|
127
|
+
&& !(typeof defaultValue === "string" && defaultValue.trim() === "");
|
|
128
|
+
return {
|
|
129
|
+
name,
|
|
130
|
+
type,
|
|
131
|
+
required: field.required === true,
|
|
132
|
+
...(description === undefined ? {} : { description }),
|
|
133
|
+
...(hasDefault
|
|
134
|
+
? { default: defaultValue }
|
|
135
|
+
: {}),
|
|
136
|
+
...(options === undefined ? {} : { options })
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function range(values, unit) {
|
|
140
|
+
if (values.length === 0)
|
|
141
|
+
return undefined;
|
|
142
|
+
const bounds = { minimum: Math.min(...values), maximum: Math.max(...values) };
|
|
143
|
+
return unit === "gaodou" ? { unit, ...bounds } : { unit, ...bounds };
|
|
144
|
+
}
|
|
145
|
+
function normalizeModel(value, scenes) {
|
|
146
|
+
const item = record(value);
|
|
147
|
+
const model = record(item?.model);
|
|
148
|
+
if (item === undefined || model === undefined || !Array.isArray(item.skus)) {
|
|
149
|
+
throw new Error("invalid model");
|
|
150
|
+
}
|
|
151
|
+
const activeSkus = item.skus.map(record).filter((sku) => sku !== undefined && sku.status === 1);
|
|
152
|
+
if (activeSkus.length === 0)
|
|
153
|
+
return undefined;
|
|
154
|
+
const code = requiredText(model.code);
|
|
155
|
+
const title = requiredText(model.name);
|
|
156
|
+
if (!Array.isArray(model.scene_categories))
|
|
157
|
+
throw new Error("invalid categories");
|
|
158
|
+
const matches = model.scene_categories
|
|
159
|
+
.map((category) => scenes.get(requiredText(record(category)?.code)))
|
|
160
|
+
.filter((scene) => scene !== undefined)
|
|
161
|
+
.toSorted((left, right) => toolOrder[left.tool] - toolOrder[right.tool]
|
|
162
|
+
|| left.sort - right.sort);
|
|
163
|
+
const scene = matches[0];
|
|
164
|
+
if (scene === undefined)
|
|
165
|
+
throw new Error("unknown scene");
|
|
166
|
+
if (typeof model.input_field_settings !== "string")
|
|
167
|
+
throw new Error("missing settings");
|
|
168
|
+
const settings = JSON.parse(model.input_field_settings);
|
|
169
|
+
if (!Array.isArray(settings))
|
|
170
|
+
throw new Error("invalid settings");
|
|
171
|
+
const parameters = settings
|
|
172
|
+
.map(parameter)
|
|
173
|
+
.filter((item) => item !== undefined);
|
|
174
|
+
const description = cleanText(model.description);
|
|
175
|
+
const usageDescription = cleanText(model.tool_description);
|
|
176
|
+
const outputMediaTypes = Array.isArray(model.output_mime_types)
|
|
177
|
+
? model.output_mime_types.map(text).filter((item) => item !== undefined)
|
|
178
|
+
: [];
|
|
179
|
+
const costs = activeSkus
|
|
180
|
+
.map((sku) => sku.point_cost)
|
|
181
|
+
.filter((value) => typeof value === "number"
|
|
182
|
+
&& Number.isFinite(value) && value >= 0);
|
|
183
|
+
const times = activeSkus
|
|
184
|
+
.map((sku) => sku.estimated_time)
|
|
185
|
+
.filter((value) => typeof value === "number"
|
|
186
|
+
&& Number.isFinite(value) && value >= 0);
|
|
187
|
+
const cost = range(costs, "gaodou");
|
|
188
|
+
const estimatedTime = range(times, "second");
|
|
189
|
+
return {
|
|
190
|
+
tool: scene.tool,
|
|
191
|
+
model: code,
|
|
192
|
+
title,
|
|
193
|
+
...(description === undefined ? {} : { description }),
|
|
194
|
+
...(usageDescription === undefined ? {} : { usageDescription }),
|
|
195
|
+
...(outputMediaTypes.length === 0 ? {} : { outputMediaTypes }),
|
|
196
|
+
parameters,
|
|
197
|
+
...(cost?.unit === "gaodou" ? { cost } : {}),
|
|
198
|
+
...(estimatedTime?.unit === "second" ? { estimatedTime } : {}),
|
|
199
|
+
sceneSort: scene.sort,
|
|
200
|
+
modelSort: number(model.sort)
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
export function createMnsCatalogAdapter(dependencies) {
|
|
204
|
+
return {
|
|
205
|
+
async load({ access, signal }) {
|
|
206
|
+
signal.throwIfAborted();
|
|
207
|
+
try {
|
|
208
|
+
const rawScenes = await dependencies.transport.getJson({
|
|
209
|
+
path: "/mns/scene-categories",
|
|
210
|
+
query: { codes: ROOT_SCENE_CODES.join(",") },
|
|
211
|
+
credential: access.credential,
|
|
212
|
+
organizationId: access.organizationId,
|
|
213
|
+
signal
|
|
214
|
+
});
|
|
215
|
+
if (!Array.isArray(rawScenes))
|
|
216
|
+
throw new Error("invalid scene response");
|
|
217
|
+
const scenes = collectScenes(rawScenes);
|
|
218
|
+
const rawModels = await dependencies.transport.getJson({
|
|
219
|
+
path: "/mns/models",
|
|
220
|
+
query: { codes: scenes.map((scene) => scene.code).join(",") },
|
|
221
|
+
credential: access.credential,
|
|
222
|
+
organizationId: access.organizationId,
|
|
223
|
+
signal
|
|
224
|
+
});
|
|
225
|
+
if (!Array.isArray(rawModels))
|
|
226
|
+
throw new Error("invalid model response");
|
|
227
|
+
const sceneMap = new Map(scenes.map((scene) => [scene.code, scene]));
|
|
228
|
+
const models = [];
|
|
229
|
+
const seen = new Set();
|
|
230
|
+
let invalidCount = 0;
|
|
231
|
+
for (const rawModel of rawModels) {
|
|
232
|
+
try {
|
|
233
|
+
const model = normalizeModel(rawModel, sceneMap);
|
|
234
|
+
if (model === undefined || seen.has(model.model))
|
|
235
|
+
continue;
|
|
236
|
+
seen.add(model.model);
|
|
237
|
+
models.push(model);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
invalidCount += 1;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
models.sort((left, right) => toolOrder[left.tool] - toolOrder[right.tool]
|
|
244
|
+
|| left.sceneSort - right.sceneSort
|
|
245
|
+
|| left.modelSort - right.modelSort
|
|
246
|
+
|| (left.model < right.model ? -1 : left.model > right.model ? 1 : 0));
|
|
247
|
+
return {
|
|
248
|
+
models,
|
|
249
|
+
warnings: invalidCount === 0
|
|
250
|
+
? []
|
|
251
|
+
: [`已忽略 ${invalidCount} 个无效模型配置。`]
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
signal.throwIfAborted();
|
|
256
|
+
if (error instanceof RemoteRequestError)
|
|
257
|
+
throw error;
|
|
258
|
+
throw new RemoteRequestError();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
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
|
+
const MAX_JAVA_LONG = 9223372036854775807n;
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function fail() {
|
|
9
|
+
throw new RemoteRequestError();
|
|
10
|
+
}
|
|
11
|
+
function nonBlank(value) {
|
|
12
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
13
|
+
return fail();
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function parseRecord(value) {
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(value);
|
|
19
|
+
if (!isRecord(parsed))
|
|
20
|
+
return fail();
|
|
21
|
+
return parsed;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (error instanceof RemoteRequestError)
|
|
25
|
+
throw error;
|
|
26
|
+
return fail();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function contentId(value) {
|
|
30
|
+
if (!/^\d+$/u.test(value))
|
|
31
|
+
return fail();
|
|
32
|
+
const numeric = BigInt(value);
|
|
33
|
+
if (numeric > MAX_JAVA_LONG)
|
|
34
|
+
return fail();
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
function parseIntent(value) {
|
|
38
|
+
if (!isRecord(value))
|
|
39
|
+
return fail();
|
|
40
|
+
return {
|
|
41
|
+
name: nonBlank(value.name),
|
|
42
|
+
arguments: parseRecord(nonBlank(value.arguments))
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function submittedTask(value) {
|
|
46
|
+
if (!isRecord(value) || !isRecord(value.result) || value.result.isError !== false)
|
|
47
|
+
return fail();
|
|
48
|
+
return nonBlank(value.task_id);
|
|
49
|
+
}
|
|
50
|
+
function resourceName(uri) {
|
|
51
|
+
let url;
|
|
52
|
+
try {
|
|
53
|
+
url = new URL(uri);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return fail();
|
|
57
|
+
}
|
|
58
|
+
const segment = url.pathname.split("/").filter(Boolean).at(-1);
|
|
59
|
+
if (segment === undefined)
|
|
60
|
+
return fail();
|
|
61
|
+
try {
|
|
62
|
+
return nonBlank(decodeURIComponent(segment));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return fail();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function normalizeContent(value) {
|
|
69
|
+
if (!isRecord(value))
|
|
70
|
+
return fail();
|
|
71
|
+
if (value.type === "text") {
|
|
72
|
+
return { type: "text", text: nonBlank(value.text) };
|
|
73
|
+
}
|
|
74
|
+
if (value.type !== "resource" || !isRecord(value.resource))
|
|
75
|
+
return fail();
|
|
76
|
+
if ("blob" in value.resource || "text" in value.resource)
|
|
77
|
+
return fail();
|
|
78
|
+
const uri = nonBlank(value.resource.uri);
|
|
79
|
+
const mimeType = value.resource.mimeType;
|
|
80
|
+
if (mimeType !== undefined && (typeof mimeType !== "string" || mimeType.trim() === "")) {
|
|
81
|
+
return fail();
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
type: "resource_link",
|
|
85
|
+
uri,
|
|
86
|
+
name: resourceName(uri),
|
|
87
|
+
...(mimeType === undefined ? {} : { mimeType })
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function pollResult(value, taskId) {
|
|
91
|
+
if (!Array.isArray(value))
|
|
92
|
+
return fail();
|
|
93
|
+
const item = value.find((candidate) => isRecord(candidate) && candidate.task_id === taskId);
|
|
94
|
+
if (!isRecord(item) || item.code !== "200")
|
|
95
|
+
return fail();
|
|
96
|
+
const inner = parseRecord(nonBlank(item.result));
|
|
97
|
+
if (!isRecord(inner.result) || inner.result.isError !== false)
|
|
98
|
+
return fail();
|
|
99
|
+
const content = inner.result.content;
|
|
100
|
+
if (content === undefined)
|
|
101
|
+
return undefined;
|
|
102
|
+
if (!Array.isArray(content))
|
|
103
|
+
return fail();
|
|
104
|
+
if (content.length === 0)
|
|
105
|
+
return undefined;
|
|
106
|
+
return content.map(normalizeContent);
|
|
107
|
+
}
|
|
108
|
+
async function safePost(transport, request) {
|
|
109
|
+
try {
|
|
110
|
+
return await transport.postJson(request);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (request.signal.aborted)
|
|
114
|
+
throw request.signal.reason;
|
|
115
|
+
if (error instanceof RemoteRequestError)
|
|
116
|
+
throw error;
|
|
117
|
+
throw new RemoteRequestError();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function defaultSleep(milliseconds, signal) {
|
|
121
|
+
signal.throwIfAborted();
|
|
122
|
+
try {
|
|
123
|
+
await delay(milliseconds, undefined, { signal });
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
if (signal.aborted)
|
|
127
|
+
throw signal.reason;
|
|
128
|
+
throw new RemoteRequestError();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function defaultContentId() {
|
|
132
|
+
return (BigInt(Date.now()) * 100000n + BigInt(randomInt(100_000))).toString();
|
|
133
|
+
}
|
|
134
|
+
export function createToolApiAdapter(dependencies) {
|
|
135
|
+
const createContentId = dependencies.contentIdFactory ?? defaultContentId;
|
|
136
|
+
const sleep = dependencies.sleep ?? defaultSleep;
|
|
137
|
+
return {
|
|
138
|
+
async execute({ invocation, access, signal }) {
|
|
139
|
+
signal.throwIfAborted();
|
|
140
|
+
const managedContentId = contentId(createContentId());
|
|
141
|
+
const intent = parseIntent(await safePost(dependencies.transport, {
|
|
142
|
+
path: "/ai-agent/v1/tool-intent",
|
|
143
|
+
body: {
|
|
144
|
+
prompt: invocation.prompt,
|
|
145
|
+
scene_code: invocation.model,
|
|
146
|
+
parameters: invocation.parameters
|
|
147
|
+
},
|
|
148
|
+
credential: access.credential,
|
|
149
|
+
organizationId: access.organizationId,
|
|
150
|
+
signal
|
|
151
|
+
}));
|
|
152
|
+
signal.throwIfAborted();
|
|
153
|
+
const taskId = submittedTask(await safePost(dependencies.transport, {
|
|
154
|
+
path: "/gdesign/tool/v1/dify/call_async",
|
|
155
|
+
body: {
|
|
156
|
+
jsonrpc: "2.0",
|
|
157
|
+
id: 0,
|
|
158
|
+
method: "tools/call",
|
|
159
|
+
params: {
|
|
160
|
+
name: intent.name,
|
|
161
|
+
arguments: { ...intent.arguments, content_id: managedContentId }
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
credential: access.credential,
|
|
165
|
+
organizationId: access.organizationId,
|
|
166
|
+
signal
|
|
167
|
+
}));
|
|
168
|
+
let transientFailures = 0;
|
|
169
|
+
while (true) {
|
|
170
|
+
signal.throwIfAborted();
|
|
171
|
+
let response;
|
|
172
|
+
try {
|
|
173
|
+
response = await safePost(dependencies.transport, {
|
|
174
|
+
path: "/gdesign/tool/v1/dify/process/batch",
|
|
175
|
+
body: { task_ids: [taskId] },
|
|
176
|
+
credential: access.credential,
|
|
177
|
+
organizationId: access.organizationId,
|
|
178
|
+
signal
|
|
179
|
+
});
|
|
180
|
+
transientFailures = 0;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
if (signal.aborted)
|
|
184
|
+
throw signal.reason;
|
|
185
|
+
if (!(error instanceof RemoteRequestError) || !error.transient)
|
|
186
|
+
throw error;
|
|
187
|
+
transientFailures += 1;
|
|
188
|
+
if (transientFailures > 3)
|
|
189
|
+
throw error;
|
|
190
|
+
await sleep(transientFailures * 1_000, signal);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const result = pollResult(response, taskId);
|
|
194
|
+
if (result !== undefined)
|
|
195
|
+
return result;
|
|
196
|
+
await sleep(2_000, signal);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { parseSafeRemoteAssetUrl, UrlSafetyError } from "../../platform/url-safety.js";
|
|
2
|
+
import { findToolModel, projectModelDetail, projectModelList, projectToolList, ToolInputError } from "./catalog.js";
|
|
3
|
+
import { assertModelArguments, buildToolArgumentsSchema } from "./dynamic-schema.js";
|
|
4
|
+
const mediaTypes = {
|
|
5
|
+
avif: "image/avif",
|
|
6
|
+
gif: "image/gif",
|
|
7
|
+
heic: "image/heic",
|
|
8
|
+
jpeg: "image/jpeg",
|
|
9
|
+
jpg: "image/jpeg",
|
|
10
|
+
mov: "video/quicktime",
|
|
11
|
+
mp3: "audio/mpeg",
|
|
12
|
+
mp4: "video/mp4",
|
|
13
|
+
png: "image/png",
|
|
14
|
+
wav: "audio/wav",
|
|
15
|
+
webm: "video/webm",
|
|
16
|
+
webp: "image/webp"
|
|
17
|
+
};
|
|
18
|
+
function inferRemoteMediaType(url) {
|
|
19
|
+
const segment = url.pathname.split("/").filter(Boolean).at(-1) ?? "";
|
|
20
|
+
let filename = segment;
|
|
21
|
+
try {
|
|
22
|
+
filename = decodeURIComponent(segment);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// The extension remains usable when the path contains malformed escapes.
|
|
26
|
+
}
|
|
27
|
+
const extension = filename.split(".").at(-1)?.toLowerCase();
|
|
28
|
+
return extension === undefined || extension === filename.toLowerCase()
|
|
29
|
+
? "application/octet-stream"
|
|
30
|
+
: mediaTypes[extension] ?? "application/octet-stream";
|
|
31
|
+
}
|
|
32
|
+
function remoteMedia(value, name) {
|
|
33
|
+
try {
|
|
34
|
+
const url = parseSafeRemoteAssetUrl(value);
|
|
35
|
+
return {
|
|
36
|
+
type: "media",
|
|
37
|
+
url: value,
|
|
38
|
+
mime: inferRemoteMediaType(url),
|
|
39
|
+
name
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error instanceof UrlSafetyError)
|
|
44
|
+
throw new ToolInputError();
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function createToolUseCases(dependencies) {
|
|
49
|
+
async function load(access, signal) {
|
|
50
|
+
signal.throwIfAborted();
|
|
51
|
+
const catalog = await dependencies.catalog.load({ access, signal });
|
|
52
|
+
signal.throwIfAborted();
|
|
53
|
+
for (const warning of catalog.warnings)
|
|
54
|
+
dependencies.warn(warning);
|
|
55
|
+
return catalog;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
async listTools({ access, signal }) {
|
|
59
|
+
return projectToolList(await load(access, signal));
|
|
60
|
+
},
|
|
61
|
+
async listModels({ input, access, signal }) {
|
|
62
|
+
return projectModelList(await load(access, signal), input.tool);
|
|
63
|
+
},
|
|
64
|
+
async getModel({ input, access, signal }) {
|
|
65
|
+
return projectModelDetail(await load(access, signal), input.model);
|
|
66
|
+
},
|
|
67
|
+
async getArgumentsSchema({ name, access, signal }) {
|
|
68
|
+
return buildToolArgumentsSchema(await load(access, signal), name);
|
|
69
|
+
},
|
|
70
|
+
async call({ input, access, signal }) {
|
|
71
|
+
const catalog = await load(access, signal);
|
|
72
|
+
const modelName = input.arguments.model;
|
|
73
|
+
if (typeof modelName !== "string")
|
|
74
|
+
throw new ToolInputError();
|
|
75
|
+
const model = findToolModel(catalog, input.name, modelName);
|
|
76
|
+
assertModelArguments(model, input.arguments);
|
|
77
|
+
const prompt = [];
|
|
78
|
+
const parameters = {};
|
|
79
|
+
for (const parameter of model.parameters) {
|
|
80
|
+
signal.throwIfAborted();
|
|
81
|
+
const value = input.arguments[parameter.name];
|
|
82
|
+
if (value === undefined)
|
|
83
|
+
continue;
|
|
84
|
+
if (parameter.type === "string") {
|
|
85
|
+
if (parameter.name === "prompt") {
|
|
86
|
+
prompt.push({ type: "text", content: value });
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
parameters[parameter.name] = value;
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const values = parameter.type === "uri[]" ? value : [value];
|
|
94
|
+
for (const mediaUrl of values) {
|
|
95
|
+
signal.throwIfAborted();
|
|
96
|
+
if (mediaUrl.startsWith("file:")) {
|
|
97
|
+
const uploaded = await dependencies.uploader.upload({ url: mediaUrl }, access, signal);
|
|
98
|
+
signal.throwIfAborted();
|
|
99
|
+
prompt.push({
|
|
100
|
+
type: "media",
|
|
101
|
+
url: uploaded.url,
|
|
102
|
+
mime: uploaded.mediaType,
|
|
103
|
+
name: parameter.name
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
prompt.push(remoteMedia(mediaUrl, parameter.name));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
signal.throwIfAborted();
|
|
112
|
+
const content = await dependencies.execution.execute({
|
|
113
|
+
invocation: { model: model.model, prompt, parameters },
|
|
114
|
+
access,
|
|
115
|
+
signal
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
content,
|
|
119
|
+
usage: {
|
|
120
|
+
model: model.model,
|
|
121
|
+
...(model.cost === undefined ? {} : { cost: model.cost })
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|