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.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/contracts/operations/agent.send/input.schema.json +354 -0
  4. package/contracts/operations/agent.send/output.schema.json +271 -0
  5. package/contracts/operations/auth.status/input.schema.json +7 -0
  6. package/contracts/operations/auth.status/output.schema.json +132 -0
  7. package/contracts/operations/dam.delete/input.schema.json +23 -0
  8. package/contracts/operations/dam.delete/output.schema.json +6 -0
  9. package/contracts/operations/dam.get/input.schema.json +16 -0
  10. package/contracts/operations/dam.get/output.schema.json +46 -0
  11. package/contracts/operations/dam.list/input.schema.json +44 -0
  12. package/contracts/operations/dam.list/output.schema.json +71 -0
  13. package/contracts/operations/dam.search/input.schema.json +46 -0
  14. package/contracts/operations/dam.search/output.schema.json +71 -0
  15. package/contracts/operations/dam.upload/input.schema.json +31 -0
  16. package/contracts/operations/dam.upload/output.schema.json +46 -0
  17. package/contracts/operations/editor.apply/input.schema.json +134 -0
  18. package/contracts/operations/editor.apply/output.schema.json +15 -0
  19. package/contracts/operations/editor.connect/input.schema.json +15 -0
  20. package/contracts/operations/editor.connect/output.schema.json +17 -0
  21. package/contracts/operations/editor.disconnect/input.schema.json +8 -0
  22. package/contracts/operations/editor.disconnect/output.schema.json +14 -0
  23. package/contracts/operations/editor.save/input.schema.json +8 -0
  24. package/contracts/operations/editor.save/output.schema.json +23 -0
  25. package/contracts/operations/editor.screenshot/input.schema.json +8 -0
  26. package/contracts/operations/editor.screenshot/output.schema.json +16 -0
  27. package/contracts/operations/editor.snapshot/input.schema.json +8 -0
  28. package/contracts/operations/editor.snapshot/output.schema.json +113 -0
  29. package/contracts/operations/model.get/input.schema.json +18 -0
  30. package/contracts/operations/model.get/output.schema.json +190 -0
  31. package/contracts/operations/model.list/input.schema.json +15 -0
  32. package/contracts/operations/model.list/output.schema.json +111 -0
  33. package/contracts/operations/org.current/input.schema.json +7 -0
  34. package/contracts/operations/org.current/output.schema.json +49 -0
  35. package/contracts/operations/org.list/input.schema.json +7 -0
  36. package/contracts/operations/org.list/output.schema.json +71 -0
  37. package/contracts/operations/tool.call/input.schema.json +35 -0
  38. package/contracts/operations/tool.call/output.schema.json +129 -0
  39. package/contracts/operations/tool.list/input.schema.json +7 -0
  40. package/contracts/operations/tool.list/output.schema.json +45 -0
  41. package/dist/bin/gd-cli.js +38 -0
  42. package/dist/bin/postinstall.js +42 -0
  43. package/dist/src/bootstrap/create-cli.js +55 -0
  44. package/dist/src/bootstrap/create-runtime.js +222 -0
  45. package/dist/src/bootstrap/validators.js +118 -0
  46. package/dist/src/cli/action-binding.js +63 -0
  47. package/dist/src/cli/agent-commands.js +39 -0
  48. package/dist/src/cli/auth-commands.js +47 -0
  49. package/dist/src/cli/dam-commands.js +207 -0
  50. package/dist/src/cli/editor-commands.js +73 -0
  51. package/dist/src/cli/errors.js +90 -0
  52. package/dist/src/cli/model-commands.js +47 -0
  53. package/dist/src/cli/org-commands.js +52 -0
  54. package/dist/src/cli/presenter.js +80 -0
  55. package/dist/src/cli/prompt.js +30 -0
  56. package/dist/src/cli/tool-commands.js +65 -0
  57. package/dist/src/cli/update-command.js +12 -0
  58. package/dist/src/contracts/schema.js +4 -0
  59. package/dist/src/features/agent/creative-agent-adapter.js +31 -0
  60. package/dist/src/features/agent/creative-protocol.js +324 -0
  61. package/dist/src/features/agent/creative-stream.js +127 -0
  62. package/dist/src/features/agent/use-cases.js +113 -0
  63. package/dist/src/features/auth/access-policy.js +101 -0
  64. package/dist/src/features/auth/credential-store.js +62 -0
  65. package/dist/src/features/auth/sso-service.js +179 -0
  66. package/dist/src/features/auth/state.js +129 -0
  67. package/dist/src/features/auth/use-cases.js +106 -0
  68. package/dist/src/features/dam/asset-projection.js +270 -0
  69. package/dist/src/features/dam/dam-api-adapter.js +206 -0
  70. package/dist/src/features/dam/object-storage.js +191 -0
  71. package/dist/src/features/dam/registered-uploader.js +224 -0
  72. package/dist/src/features/dam/storage-upload.js +141 -0
  73. package/dist/src/features/dam/transient-uploader.js +17 -0
  74. package/dist/src/features/dam/use-cases.js +151 -0
  75. package/dist/src/features/editor/bridge-client.js +96 -0
  76. package/dist/src/features/editor/bridge-process.js +222 -0
  77. package/dist/src/features/editor/bridge-server.js +311 -0
  78. package/dist/src/features/editor/protocol.js +1 -0
  79. package/dist/src/features/editor/session-state.js +78 -0
  80. package/dist/src/features/editor/session.js +193 -0
  81. package/dist/src/features/editor/use-cases.js +61 -0
  82. package/dist/src/features/org/org-service.js +76 -0
  83. package/dist/src/features/org/use-cases.js +140 -0
  84. package/dist/src/features/skill/bundled-skills.js +55 -0
  85. package/dist/src/features/skill/installer.js +265 -0
  86. package/dist/src/features/tool/catalog.js +79 -0
  87. package/dist/src/features/tool/dynamic-schema.js +96 -0
  88. package/dist/src/features/tool/mns-catalog-adapter.js +262 -0
  89. package/dist/src/features/tool/tool-api-adapter.js +200 -0
  90. package/dist/src/features/tool/use-cases.js +126 -0
  91. package/dist/src/features/update/update-service.js +194 -0
  92. package/dist/src/platform/json-input.js +64 -0
  93. package/dist/src/platform/local-json-file.js +59 -0
  94. package/dist/src/platform/open-browser.js +8 -0
  95. package/dist/src/platform/redact.js +80 -0
  96. package/dist/src/platform/safe-upload-file.js +146 -0
  97. package/dist/src/platform/signature.js +19 -0
  98. package/dist/src/platform/signed-http-transport.js +109 -0
  99. package/dist/src/platform/url-safety.js +100 -0
  100. package/package.json +56 -0
  101. package/skills/gd-cli/SKILL.md +15 -0
  102. package/skills/gd-cli/references/auth-org.md +9 -0
  103. package/skills/gd-cli/references/creation.md +38 -0
  104. package/skills/gd-cli/references/dam.md +20 -0
  105. package/skills/gd-cli/references/editor.md +12 -0
  106. package/skills/gd-cli/references/errors.md +10 -0
  107. 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
+ }