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,265 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { cp, lstat, mkdir, mkdtemp, readFile, readlink, rename, rm, symlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
5
|
+
export class SkillSyncError extends Error {
|
|
6
|
+
results;
|
|
7
|
+
constructor(message, results) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.results = results;
|
|
10
|
+
this.name = "SkillSyncError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export async function syncBundledSkills(skills, options = {}) {
|
|
14
|
+
const homeDirectory = options.homeDirectory ?? homedir();
|
|
15
|
+
const platform = options.platform ?? process.platform;
|
|
16
|
+
const results = [];
|
|
17
|
+
for (const skill of skills) {
|
|
18
|
+
assertDescriptor(skill, results);
|
|
19
|
+
const targets = await skillTargets(skill.skill, homeDirectory, options.env ?? {});
|
|
20
|
+
const targetResults = [];
|
|
21
|
+
try {
|
|
22
|
+
const canonical = targets[0];
|
|
23
|
+
let current = await inspectTarget(canonical, skill, canonical.path, platform);
|
|
24
|
+
if (current.status === "conflict" || current.status === "broken") {
|
|
25
|
+
targetResults.push(current);
|
|
26
|
+
throw new Error(current.reason ?? "canonical Skill conflict");
|
|
27
|
+
}
|
|
28
|
+
if (current.status === "missing" || current.status === "outdated") {
|
|
29
|
+
await replaceManagedDirectory(skill, canonical.path, current.status === "outdated");
|
|
30
|
+
current = installed(canonical, "canonical", true);
|
|
31
|
+
}
|
|
32
|
+
targetResults.push(current);
|
|
33
|
+
for (const target of targets.slice(1)) {
|
|
34
|
+
current = await inspectTarget(target, skill, canonical.path, platform);
|
|
35
|
+
if (current.status === "conflict" || current.status === "broken") {
|
|
36
|
+
targetResults.push(current);
|
|
37
|
+
throw new Error(current.reason ?? "Agent host Skill conflict");
|
|
38
|
+
}
|
|
39
|
+
if (current.status === "missing") {
|
|
40
|
+
current = await createLinkOrCopy(skill, target, canonical.path, {
|
|
41
|
+
platform,
|
|
42
|
+
createLink: options.createLink ?? symlink
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else if (current.status === "outdated") {
|
|
46
|
+
await replaceManagedDirectory(skill, target.path, true);
|
|
47
|
+
current = installed(target, "copy", true);
|
|
48
|
+
}
|
|
49
|
+
targetResults.push(current);
|
|
50
|
+
}
|
|
51
|
+
results.push(syncResult(skill, targetResults));
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
const active = targets[targetResults.length];
|
|
55
|
+
if (active && !targetResults.some((target) => target.host === active.host)) {
|
|
56
|
+
targetResults.push({
|
|
57
|
+
host: active.host,
|
|
58
|
+
path: active.path,
|
|
59
|
+
status: "conflict",
|
|
60
|
+
changed: false,
|
|
61
|
+
reason: message(error)
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
results.push(syncResult(skill, targetResults));
|
|
65
|
+
throw new SkillSyncError(`Agent Skill ${skill.skill} 同步失败${active ? ` (${active.path})` : ""}: ${message(error)}`, results);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return results;
|
|
69
|
+
}
|
|
70
|
+
export async function verifyBundledSkills(skills, options = {}) {
|
|
71
|
+
const homeDirectory = options.homeDirectory ?? homedir();
|
|
72
|
+
const platform = options.platform ?? process.platform;
|
|
73
|
+
const results = [];
|
|
74
|
+
for (const skill of skills) {
|
|
75
|
+
assertDescriptor(skill, results);
|
|
76
|
+
const targets = await skillTargets(skill.skill, homeDirectory, options.env ?? {});
|
|
77
|
+
const canonical = targets[0];
|
|
78
|
+
const inspected = await Promise.all(targets.map((target) => inspectTarget(target, skill, canonical.path, platform)));
|
|
79
|
+
results.push(syncResult(skill, inspected));
|
|
80
|
+
}
|
|
81
|
+
const invalid = results.flatMap((result) => result.targets)
|
|
82
|
+
.find((target) => target.status !== "installed");
|
|
83
|
+
if (invalid) {
|
|
84
|
+
throw new SkillSyncError(`Agent Skill 验收失败 (${invalid.path}): ${invalid.reason ?? invalid.status}`, results);
|
|
85
|
+
}
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
async function skillTargets(skill, homeDirectory, env) {
|
|
89
|
+
const roots = [
|
|
90
|
+
{ host: "claude-code", root: env.CLAUDE_HOME ?? join(homeDirectory, ".claude") },
|
|
91
|
+
{ host: "codex", root: env.CODEX_HOME ?? join(homeDirectory, ".codex") },
|
|
92
|
+
{ host: "cursor", root: env.CURSOR_HOME ?? join(homeDirectory, ".cursor") },
|
|
93
|
+
{ host: "opencode", root: env.OPENCODE_HOME ?? join(homeDirectory, ".config", "opencode") },
|
|
94
|
+
{ host: "openclaw", root: env.OPENCLAW_HOME ?? join(homeDirectory, ".openclaw") },
|
|
95
|
+
{ host: "workbuddy", root: join(homeDirectory, ".workbuddy") }
|
|
96
|
+
];
|
|
97
|
+
const detected = await Promise.all(roots.map(async (target) => ({
|
|
98
|
+
...target,
|
|
99
|
+
detected: Boolean(await lstatMaybe(target.root))
|
|
100
|
+
})));
|
|
101
|
+
return [
|
|
102
|
+
{ host: "canonical", path: join(homeDirectory, ".agents", "skills", skill) },
|
|
103
|
+
...detected
|
|
104
|
+
.filter((target) => target.detected)
|
|
105
|
+
.map((target) => ({ host: target.host, path: join(target.root, "skills", skill) }))
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
async function inspectTarget(target, skill, canonical, platform) {
|
|
109
|
+
const stat = await lstatMaybe(target.path);
|
|
110
|
+
if (!stat)
|
|
111
|
+
return targetResult(target, "missing");
|
|
112
|
+
if (target.host === "canonical") {
|
|
113
|
+
if (stat.isSymbolicLink()) {
|
|
114
|
+
return targetResult(target, "conflict", undefined, "canonical 不能是链接");
|
|
115
|
+
}
|
|
116
|
+
if (!stat.isDirectory()) {
|
|
117
|
+
return targetResult(target, "conflict", undefined, "canonical 路径不是目录");
|
|
118
|
+
}
|
|
119
|
+
const marker = await readMarker(target.path);
|
|
120
|
+
if (!marker || marker.skill !== skill.skill) {
|
|
121
|
+
return targetResult(target, "conflict", undefined, "canonical 缺少合法 marker");
|
|
122
|
+
}
|
|
123
|
+
return targetResult(target, marker.version === skill.version ? "installed" : "outdated", "canonical");
|
|
124
|
+
}
|
|
125
|
+
if (stat.isSymbolicLink()) {
|
|
126
|
+
let resolved;
|
|
127
|
+
try {
|
|
128
|
+
resolved = resolve(dirname(target.path), await readlink(target.path));
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
return targetResult(target, "conflict", linkMode(platform), message(error));
|
|
132
|
+
}
|
|
133
|
+
if (!samePath(resolved, canonical, platform)) {
|
|
134
|
+
return targetResult(target, "conflict", linkMode(platform), "链接未指向 canonical Skill");
|
|
135
|
+
}
|
|
136
|
+
if (!await lstatMaybe(canonical)) {
|
|
137
|
+
return targetResult(target, "broken", linkMode(platform), "canonical Skill 不存在");
|
|
138
|
+
}
|
|
139
|
+
return targetResult(target, "installed", linkMode(platform));
|
|
140
|
+
}
|
|
141
|
+
if (!stat.isDirectory()) {
|
|
142
|
+
return targetResult(target, "conflict", undefined, "宿主路径不是目录或链接");
|
|
143
|
+
}
|
|
144
|
+
const marker = await readMarker(target.path);
|
|
145
|
+
if (!marker || marker.skill !== skill.skill) {
|
|
146
|
+
return targetResult(target, "conflict", undefined, "宿主目录缺少合法 marker");
|
|
147
|
+
}
|
|
148
|
+
return targetResult(target, marker.version === skill.version ? "installed" : "outdated", "copy");
|
|
149
|
+
}
|
|
150
|
+
async function createLinkOrCopy(skill, target, canonical, options) {
|
|
151
|
+
await mkdir(dirname(target.path), { recursive: true });
|
|
152
|
+
try {
|
|
153
|
+
if (options.platform === "win32") {
|
|
154
|
+
await options.createLink(canonical, target.path, "junction");
|
|
155
|
+
return installed(target, "junction", true);
|
|
156
|
+
}
|
|
157
|
+
await options.createLink(relative(dirname(target.path), canonical), target.path);
|
|
158
|
+
return installed(target, "symlink", true);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
if (await lstatMaybe(target.path))
|
|
162
|
+
throw error;
|
|
163
|
+
await replaceManagedDirectory(skill, target.path, false);
|
|
164
|
+
return installed(target, "copy", true);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function replaceManagedDirectory(skill, destination, replace) {
|
|
168
|
+
const parent = dirname(destination);
|
|
169
|
+
await mkdir(parent, { recursive: true });
|
|
170
|
+
const stagingRoot = await mkdtemp(join(parent, ".gd-cli-stage-"));
|
|
171
|
+
const staged = join(stagingRoot, skill.skill);
|
|
172
|
+
let backup;
|
|
173
|
+
try {
|
|
174
|
+
await cp(skill.sourceDirectory, staged, { recursive: true });
|
|
175
|
+
await writeFile(join(staged, ".gd-cli.json"), `${JSON.stringify({ skill: skill.skill, version: skill.version })}\n`);
|
|
176
|
+
if (replace) {
|
|
177
|
+
backup = join(parent, `.gd-cli-backup-${randomUUID()}`);
|
|
178
|
+
await rename(destination, backup);
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
await rename(staged, destination);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
if (backup)
|
|
185
|
+
await rename(backup, destination);
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
if (backup)
|
|
189
|
+
await rm(backup, { recursive: true, force: false });
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
await rm(stagingRoot, { recursive: true, force: true });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function readMarker(directory) {
|
|
196
|
+
try {
|
|
197
|
+
const parsed = JSON.parse(await readFile(join(directory, ".gd-cli.json"), "utf8"));
|
|
198
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
199
|
+
return undefined;
|
|
200
|
+
const marker = parsed;
|
|
201
|
+
if (Object.keys(marker).sort().join(",") !== "skill,version")
|
|
202
|
+
return undefined;
|
|
203
|
+
if (typeof marker.skill !== "string" || !marker.skill.trim())
|
|
204
|
+
return undefined;
|
|
205
|
+
if (typeof marker.version !== "string" || !marker.version.trim())
|
|
206
|
+
return undefined;
|
|
207
|
+
return { skill: marker.skill, version: marker.version };
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
const code = error.code;
|
|
211
|
+
if (code === "ENOENT" || error instanceof SyntaxError)
|
|
212
|
+
return undefined;
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function lstatMaybe(path) {
|
|
217
|
+
try {
|
|
218
|
+
return await lstat(path);
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
const code = error.code;
|
|
222
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
223
|
+
return undefined;
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function assertDescriptor(skill, results) {
|
|
228
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(skill.skill) || !skill.version.trim()) {
|
|
229
|
+
throw new SkillSyncError(`Agent Skill descriptor 无效: ${skill.skill}`, results);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function installed(target, mode, changed) {
|
|
233
|
+
return { host: target.host, path: target.path, status: "installed", mode, changed };
|
|
234
|
+
}
|
|
235
|
+
function targetResult(target, status, mode, reason) {
|
|
236
|
+
return {
|
|
237
|
+
host: target.host,
|
|
238
|
+
path: target.path,
|
|
239
|
+
status,
|
|
240
|
+
...(mode ? { mode } : {}),
|
|
241
|
+
changed: false,
|
|
242
|
+
...(reason ? { reason } : {})
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function syncResult(skill, targets) {
|
|
246
|
+
return {
|
|
247
|
+
skill: skill.skill,
|
|
248
|
+
version: skill.version,
|
|
249
|
+
changed: targets.some((target) => target.changed),
|
|
250
|
+
targets
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function linkMode(platform) {
|
|
254
|
+
return platform === "win32" ? "junction" : "symlink";
|
|
255
|
+
}
|
|
256
|
+
function samePath(left, right, platform) {
|
|
257
|
+
const normalizedLeft = resolve(left);
|
|
258
|
+
const normalizedRight = resolve(right);
|
|
259
|
+
return platform === "win32"
|
|
260
|
+
? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
|
|
261
|
+
: normalizedLeft === normalizedRight;
|
|
262
|
+
}
|
|
263
|
+
function message(error) {
|
|
264
|
+
return error instanceof Error ? error.message : String(error);
|
|
265
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export class ToolInputError extends Error {
|
|
2
|
+
constructor() {
|
|
3
|
+
super("Tool 输入不可用。");
|
|
4
|
+
this.name = "ToolInputError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
const tools = [
|
|
8
|
+
{
|
|
9
|
+
name: "image.generate",
|
|
10
|
+
title: "图片生成与编辑",
|
|
11
|
+
description: "使用已发布的图片模型,根据文本和可选参考图片生成或编辑图片。"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: "video.generate",
|
|
15
|
+
title: "视频生成",
|
|
16
|
+
description: "使用已发布的视频模型,根据文本及可选图片、视频或音频生成视频。"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "text.generate",
|
|
20
|
+
title: "文本生成",
|
|
21
|
+
description: "使用已发布的文本模型,根据文本和可选图片生成文本。"
|
|
22
|
+
}
|
|
23
|
+
];
|
|
24
|
+
function toolIndex(tool) {
|
|
25
|
+
return tools.findIndex((candidate) => candidate.name === tool);
|
|
26
|
+
}
|
|
27
|
+
function compareModels(left, right) {
|
|
28
|
+
return toolIndex(left.tool) - toolIndex(right.tool)
|
|
29
|
+
|| left.sceneSort - right.sceneSort
|
|
30
|
+
|| left.modelSort - right.modelSort
|
|
31
|
+
|| (left.model < right.model ? -1 : left.model > right.model ? 1 : 0);
|
|
32
|
+
}
|
|
33
|
+
function summary(model) {
|
|
34
|
+
return {
|
|
35
|
+
tool: model.tool,
|
|
36
|
+
model: model.model,
|
|
37
|
+
title: model.title,
|
|
38
|
+
...(model.description === undefined ? {} : { description: model.description }),
|
|
39
|
+
...(model.cost === undefined ? {} : { cost: model.cost }),
|
|
40
|
+
...(model.estimatedTime === undefined ? {} : { estimatedTime: model.estimatedTime })
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function projectToolList(catalog) {
|
|
44
|
+
return {
|
|
45
|
+
tools: tools.filter((tool) => catalog.models.some((model) => model.tool === tool.name))
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function projectModelList(catalog, tool) {
|
|
49
|
+
if (tool !== undefined && !catalog.models.some((model) => model.tool === tool)) {
|
|
50
|
+
throw new ToolInputError();
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
models: catalog.models
|
|
54
|
+
.filter((model) => tool === undefined || model.tool === tool)
|
|
55
|
+
.toSorted(compareModels)
|
|
56
|
+
.map(summary)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function projectModelDetail(catalog, model) {
|
|
60
|
+
const found = catalog.models.find((candidate) => candidate.model === model);
|
|
61
|
+
if (found === undefined)
|
|
62
|
+
throw new ToolInputError();
|
|
63
|
+
return {
|
|
64
|
+
...summary(found),
|
|
65
|
+
...(found.usageDescription === undefined
|
|
66
|
+
? {}
|
|
67
|
+
: { usageDescription: found.usageDescription }),
|
|
68
|
+
...(found.outputMediaTypes === undefined
|
|
69
|
+
? {}
|
|
70
|
+
: { outputMediaTypes: found.outputMediaTypes }),
|
|
71
|
+
parameters: found.parameters
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function findToolModel(catalog, tool, model) {
|
|
75
|
+
const found = catalog.models.find((candidate) => candidate.tool === tool && candidate.model === model);
|
|
76
|
+
if (found === undefined)
|
|
77
|
+
throw new ToolInputError();
|
|
78
|
+
return found;
|
|
79
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
2
|
+
import addFormatsImport, {} from "ajv-formats";
|
|
3
|
+
import { ToolInputError } from "./catalog.js";
|
|
4
|
+
function parameterSchema(parameter) {
|
|
5
|
+
const common = {
|
|
6
|
+
...(parameter.description === undefined ? {} : { description: parameter.description }),
|
|
7
|
+
...(parameter.default === undefined ? {} : { default: parameter.default })
|
|
8
|
+
};
|
|
9
|
+
if (parameter.type === "uri")
|
|
10
|
+
return { type: "string", format: "uri", ...common };
|
|
11
|
+
if (parameter.type === "uri[]") {
|
|
12
|
+
return { type: "array", items: { type: "string", format: "uri" }, ...common };
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
type: "string",
|
|
16
|
+
...(parameter.options === undefined
|
|
17
|
+
? {}
|
|
18
|
+
: { enum: parameter.options.map((option) => option.value) }),
|
|
19
|
+
...common
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function sameShape(left, right) {
|
|
23
|
+
return left.type === right.type
|
|
24
|
+
&& left.format === right.format
|
|
25
|
+
&& left.items?.format === right.items?.format;
|
|
26
|
+
}
|
|
27
|
+
function mergeSchema(current, next) {
|
|
28
|
+
if (!sameShape(current, next)) {
|
|
29
|
+
const variants = current.anyOf ?? [current];
|
|
30
|
+
return variants.some((variant) => sameShape(variant, next))
|
|
31
|
+
? current
|
|
32
|
+
: { anyOf: [...variants, next] };
|
|
33
|
+
}
|
|
34
|
+
if (current.enum === undefined || next.enum === undefined) {
|
|
35
|
+
if (current.enum === undefined)
|
|
36
|
+
return current;
|
|
37
|
+
const { enum: ignored, ...unrestricted } = current;
|
|
38
|
+
void ignored;
|
|
39
|
+
return unrestricted;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
...current,
|
|
43
|
+
enum: [...new Set([...current.enum, ...next.enum])]
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function buildToolArgumentsSchema(catalog, tool) {
|
|
47
|
+
const models = catalog.models.filter((model) => model.tool === tool);
|
|
48
|
+
if (models.length === 0)
|
|
49
|
+
throw new ToolInputError();
|
|
50
|
+
const properties = {
|
|
51
|
+
model: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "要调用的模型机器标识;取值来自 model list。"
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
for (const model of models) {
|
|
57
|
+
for (const parameter of model.parameters) {
|
|
58
|
+
const next = parameterSchema(parameter);
|
|
59
|
+
const current = properties[parameter.name];
|
|
60
|
+
properties[parameter.name] = current === undefined ? next : mergeSchema(current, next);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties,
|
|
66
|
+
required: ["model"],
|
|
67
|
+
additionalProperties: false
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const ajv = new Ajv2020({ allErrors: true, strict: true });
|
|
71
|
+
const addFormats = addFormatsImport;
|
|
72
|
+
addFormats(ajv);
|
|
73
|
+
function validator(model) {
|
|
74
|
+
const properties = {
|
|
75
|
+
model: { type: "string" }
|
|
76
|
+
};
|
|
77
|
+
for (const parameter of model.parameters) {
|
|
78
|
+
properties[parameter.name] = parameterSchema(parameter);
|
|
79
|
+
}
|
|
80
|
+
return ajv.compile({
|
|
81
|
+
type: "object",
|
|
82
|
+
properties,
|
|
83
|
+
required: [
|
|
84
|
+
"model",
|
|
85
|
+
...model.parameters.filter((parameter) => parameter.required).map((parameter) => parameter.name)
|
|
86
|
+
],
|
|
87
|
+
additionalProperties: false
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export function assertModelArguments(model, value) {
|
|
91
|
+
if (typeof value !== "object" || value === null
|
|
92
|
+
|| value.model !== model.model
|
|
93
|
+
|| !validator(model)(value)) {
|
|
94
|
+
throw new ToolInputError();
|
|
95
|
+
}
|
|
96
|
+
}
|