gaoding-cli 1.0.0-alpha.6 → 1.0.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +18 -14
- package/dist/src/api/app-runner.js +1 -2
- package/dist/src/api/creative-client.d.ts +34 -0
- package/dist/src/api/creative-client.js +184 -0
- package/dist/src/api/direct-client.d.ts +9 -2
- package/dist/src/api/direct-client.js +45 -18
- package/dist/src/api/hub-client.d.ts +11 -8
- package/dist/src/api/hub-client.js +17 -18
- package/dist/src/api/usage-client.d.ts +4 -4
- package/dist/src/api/usage-client.js +9 -49
- package/dist/src/auth/credential-store-v2.d.ts +0 -1
- package/dist/src/auth/credential-store-v2.js +0 -6
- package/dist/src/commands/auth.js +13 -13
- package/dist/src/commands/creative.d.ts +8 -0
- package/dist/src/commands/creative.js +186 -0
- package/dist/src/commands/dam.js +27 -22
- package/dist/src/commands/org.js +20 -14
- package/dist/src/commands/skills.js +9 -4
- package/dist/src/commands/update.js +6 -2
- package/dist/src/commands/usage.d.ts +1 -1
- package/dist/src/commands/usage.js +6 -4
- package/dist/src/commands/workflows.js +31 -13
- package/dist/src/core/auth/auth-guard.d.ts +21 -0
- package/dist/src/core/auth/auth-guard.js +121 -0
- package/dist/src/core/auth/org-manager.d.ts +2 -1
- package/dist/src/core/auth/org-manager.js +61 -6
- package/dist/src/creative/contract.d.ts +1112 -0
- package/dist/src/creative/contract.js +743 -0
- package/dist/src/creative/media.d.ts +19 -0
- package/dist/src/creative/media.js +83 -0
- package/dist/src/creative/protocol.d.ts +60 -0
- package/dist/src/creative/protocol.js +337 -0
- package/dist/src/middleware/auth.d.ts +6 -2
- package/dist/src/middleware/auth.js +25 -63
- package/dist/src/middleware/error-handler.d.ts +2 -0
- package/dist/src/middleware/error-handler.js +3 -4
- package/dist/src/program.js +7 -89
- package/dist/src/registry/agent-contracts.d.ts +3 -11
- package/dist/src/registry/agent-contracts.js +5 -243
- package/dist/src/registry/direct-registry.d.ts +0 -5
- package/dist/src/registry/direct-registry.js +0 -19
- package/dist/src/registry/registry-client.d.ts +0 -21
- package/dist/src/registry/registry-client.js +1 -40
- package/dist/src/utils/config.d.ts +2 -2
- package/dist/src/utils/config.js +1 -1
- package/dist/src/utils/help-verify.d.ts +1 -3
- package/dist/src/utils/help-verify.js +1 -25
- package/package.json +3 -3
- package/skills/gd-cli/SKILL.md +10 -9
- package/skills/gd-cli/references/auth.md +6 -0
- package/skills/gd-cli/references/creative.md +47 -0
- package/skills/gd-cli/references/entitlement.md +2 -2
- package/skills/gd-cli/references/errors.md +7 -0
- package/skills/gd-cli/references/sop.md +4 -4
- package/skills/gd-cli/references/workflows.md +1 -1
- package/dist/src/commands/models.d.ts +0 -2
- package/dist/src/commands/models.js +0 -78
- package/dist/src/commands/tools.d.ts +0 -4
- package/dist/src/commands/tools.js +0 -383
- package/skills/gd-cli/references/tools.md +0 -29
|
@@ -0,0 +1,743 @@
|
|
|
1
|
+
import { CliUsageError } from "../core/output/cli-error.js";
|
|
2
|
+
export const CREATIVE_SCHEMA_VERSION = "creative/v1";
|
|
3
|
+
export const CREATIVE_INPUT_SCHEMA = {
|
|
4
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
5
|
+
$id: "https://www.gaoding.com/cli/schemas/creative-input-v1.json",
|
|
6
|
+
title: "Creative Input",
|
|
7
|
+
description: "发起或继续一次稿定 Creative 创作所需的完整输入。",
|
|
8
|
+
type: "object",
|
|
9
|
+
additionalProperties: false,
|
|
10
|
+
properties: {
|
|
11
|
+
prompt: {
|
|
12
|
+
type: "string",
|
|
13
|
+
minLength: 1,
|
|
14
|
+
description: "详细描述期望的最终结果,包括主体、用途、内容、风格、场景、构图或镜头,以及必须保留和避免的事项。比例、分辨率、时长等可结构化表达的要求,建议通过 parameters 传入。",
|
|
15
|
+
},
|
|
16
|
+
media: {
|
|
17
|
+
type: "array",
|
|
18
|
+
minItems: 1,
|
|
19
|
+
description: "本次创作使用的输入素材;媒体不能脱离 prompt 或 answers 单独提交。",
|
|
20
|
+
items: {
|
|
21
|
+
type: "object",
|
|
22
|
+
description: "一份带有明确用途的输入素材。",
|
|
23
|
+
additionalProperties: false,
|
|
24
|
+
required: ["source", "role"],
|
|
25
|
+
properties: {
|
|
26
|
+
source: {
|
|
27
|
+
type: "string",
|
|
28
|
+
minLength: 1,
|
|
29
|
+
description: "本地文件路径或可访问的 HTTP(S) URL。",
|
|
30
|
+
},
|
|
31
|
+
role: {
|
|
32
|
+
type: "string",
|
|
33
|
+
enum: ["reference", "first_frame", "last_frame"],
|
|
34
|
+
description: "素材用途;reference 是参考素材,first_frame 和 last_frame 分别是视频首帧和尾帧。CLI 不根据数组顺序、文件名或 prompt 推断。",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
parameters: {
|
|
40
|
+
type: "object",
|
|
41
|
+
description: "可选的结构化创作约束。GD Agent 会优先遵循本字段中的约束;存在对应参数时,建议使用本字段,而不是仅在 prompt 中通过文本描述。",
|
|
42
|
+
additionalProperties: false,
|
|
43
|
+
properties: {
|
|
44
|
+
ratio: {
|
|
45
|
+
type: "string",
|
|
46
|
+
minLength: 1,
|
|
47
|
+
description: "期望输出媒体的宽高比,例如 1:1、16:9 或 9:16。",
|
|
48
|
+
},
|
|
49
|
+
resolution: {
|
|
50
|
+
type: "string",
|
|
51
|
+
minLength: 1,
|
|
52
|
+
description: "期望输出媒体的分辨率,例如 720p、1080p、2K 或 4K。",
|
|
53
|
+
},
|
|
54
|
+
duration_seconds: {
|
|
55
|
+
type: "integer",
|
|
56
|
+
minimum: 1,
|
|
57
|
+
description: "期望生成视频的时长,单位为秒;仅适用于视频创作。",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
conversation_id: {
|
|
62
|
+
type: "string",
|
|
63
|
+
minLength: 1,
|
|
64
|
+
description: "继续已有创作或回答追问时使用的会话标识;首次请求和独立的新创作不传。",
|
|
65
|
+
},
|
|
66
|
+
last_tool_message_id: {
|
|
67
|
+
type: "string",
|
|
68
|
+
minLength: 1,
|
|
69
|
+
description: "正在回答或跳过的追问消息标识,原样使用上一次 requires_input 输出中的值。",
|
|
70
|
+
},
|
|
71
|
+
answers: {
|
|
72
|
+
type: "array",
|
|
73
|
+
minItems: 1,
|
|
74
|
+
description: "对上一次 requires_input 输出中一个或多个问题的结构化回答。",
|
|
75
|
+
items: {
|
|
76
|
+
type: "object",
|
|
77
|
+
description: "对一条 GD Agent 追问的回答。",
|
|
78
|
+
additionalProperties: false,
|
|
79
|
+
required: ["question", "values"],
|
|
80
|
+
properties: {
|
|
81
|
+
question: {
|
|
82
|
+
type: "string",
|
|
83
|
+
minLength: 1,
|
|
84
|
+
description: "必须与上一次输出中的 question 文本一致。",
|
|
85
|
+
},
|
|
86
|
+
values: {
|
|
87
|
+
type: "array",
|
|
88
|
+
minItems: 1,
|
|
89
|
+
description: "选择项或自定义回答;推荐 options 不是枚举限制。",
|
|
90
|
+
items: {
|
|
91
|
+
type: "string",
|
|
92
|
+
minLength: 1,
|
|
93
|
+
description: "一项非空的选择值或自定义回答。",
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
anyOf: [
|
|
101
|
+
{
|
|
102
|
+
description: "普通新创作或连续编辑必须提供 prompt。",
|
|
103
|
+
required: ["prompt"],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
description: "回答追问时 answers 可以替代 prompt,但必须同时携带两个连续对话标识。",
|
|
107
|
+
required: ["answers", "conversation_id", "last_tool_message_id"],
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
dependentRequired: {
|
|
111
|
+
answers: ["conversation_id", "last_tool_message_id"],
|
|
112
|
+
last_tool_message_id: ["conversation_id"],
|
|
113
|
+
},
|
|
114
|
+
allOf: [
|
|
115
|
+
{
|
|
116
|
+
description: "first_frame 最多出现一次。",
|
|
117
|
+
properties: {
|
|
118
|
+
media: {
|
|
119
|
+
description: "用于检查首帧数量的素材列表。",
|
|
120
|
+
contains: {
|
|
121
|
+
type: "object",
|
|
122
|
+
properties: {
|
|
123
|
+
role: {
|
|
124
|
+
const: "first_frame",
|
|
125
|
+
description: "首帧素材角色。",
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
required: ["role"],
|
|
129
|
+
},
|
|
130
|
+
minContains: 0,
|
|
131
|
+
maxContains: 1,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
description: "last_frame 最多出现一次。",
|
|
137
|
+
properties: {
|
|
138
|
+
media: {
|
|
139
|
+
description: "用于检查尾帧数量的素材列表。",
|
|
140
|
+
contains: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {
|
|
143
|
+
role: {
|
|
144
|
+
const: "last_frame",
|
|
145
|
+
description: "尾帧素材角色。",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
required: ["role"],
|
|
149
|
+
},
|
|
150
|
+
minContains: 0,
|
|
151
|
+
maxContains: 1,
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
export const CREATIVE_OUTPUT_SCHEMA = {
|
|
158
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
159
|
+
$id: "https://www.gaoding.com/cli/schemas/creative-output-v1.json",
|
|
160
|
+
title: "Creative Output",
|
|
161
|
+
description: "creative run 的统一最终输出;中间事件和内部调用过程不属于本 Schema。",
|
|
162
|
+
type: "object",
|
|
163
|
+
additionalProperties: false,
|
|
164
|
+
required: ["status", "data", "error", "meta"],
|
|
165
|
+
properties: {
|
|
166
|
+
status: {
|
|
167
|
+
type: "string",
|
|
168
|
+
enum: ["completed", "failed"],
|
|
169
|
+
description: "CLI 调用是否成功;requires_input 仍属于 completed。",
|
|
170
|
+
},
|
|
171
|
+
data: {
|
|
172
|
+
description: "status 为 completed 时存在的 Creative 最终结果;failed 时为 null。",
|
|
173
|
+
oneOf: [{ $ref: "#/$defs/result" }, { type: "null" }],
|
|
174
|
+
},
|
|
175
|
+
error: {
|
|
176
|
+
description: "status 为 failed 时存在的结构化错误;completed 时为 null。",
|
|
177
|
+
oneOf: [{ $ref: "#/$defs/error" }, { type: "null" }],
|
|
178
|
+
},
|
|
179
|
+
meta: {
|
|
180
|
+
type: "object",
|
|
181
|
+
description: "本次输出对应的命令与公共契约版本。",
|
|
182
|
+
additionalProperties: false,
|
|
183
|
+
required: ["command", "schema_version"],
|
|
184
|
+
properties: {
|
|
185
|
+
command: {
|
|
186
|
+
const: "creative run",
|
|
187
|
+
description: "产生本 Envelope 的 GD CLI 命令。",
|
|
188
|
+
},
|
|
189
|
+
schema_version: {
|
|
190
|
+
const: CREATIVE_SCHEMA_VERSION,
|
|
191
|
+
description: "Creative 公共契约版本。",
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
oneOf: [
|
|
197
|
+
{
|
|
198
|
+
description: "成功完成或需要使用者继续回答的结果。",
|
|
199
|
+
properties: {
|
|
200
|
+
status: { const: "completed", description: "成功状态。" },
|
|
201
|
+
data: { $ref: "#/$defs/result", description: "Creative 结果。" },
|
|
202
|
+
error: { type: "null", description: "成功时没有错误。" },
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
description: "校验、认证、业务或网络失败的结果。",
|
|
207
|
+
properties: {
|
|
208
|
+
status: { const: "failed", description: "失败状态。" },
|
|
209
|
+
data: { type: "null", description: "失败时没有 Creative 结果。" },
|
|
210
|
+
error: { $ref: "#/$defs/error", description: "结构化失败信息。" },
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
$defs: {
|
|
215
|
+
result: {
|
|
216
|
+
type: "object",
|
|
217
|
+
description: "GD Agent 本轮的最终结果。",
|
|
218
|
+
additionalProperties: false,
|
|
219
|
+
required: ["conversation_id", "finish_reason", "message", "outputs"],
|
|
220
|
+
properties: {
|
|
221
|
+
conversation_id: {
|
|
222
|
+
type: "string",
|
|
223
|
+
minLength: 1,
|
|
224
|
+
description: "新请求由 GD Agent 创建;连续请求沿用输入值。",
|
|
225
|
+
},
|
|
226
|
+
finish_reason: {
|
|
227
|
+
type: "string",
|
|
228
|
+
enum: ["completed", "requires_input"],
|
|
229
|
+
description: "completed 表示本轮完成;requires_input 表示需要继续回答 questions。",
|
|
230
|
+
},
|
|
231
|
+
message: {
|
|
232
|
+
type: "string",
|
|
233
|
+
description: "GD Agent 面向使用者的最终说明。",
|
|
234
|
+
},
|
|
235
|
+
outputs: {
|
|
236
|
+
type: "array",
|
|
237
|
+
description: "本轮最终产出的文本、结构化数据或媒体。",
|
|
238
|
+
items: { $ref: "#/$defs/output", description: "一项 Creative 最终产出。" },
|
|
239
|
+
},
|
|
240
|
+
last_tool_message_id: {
|
|
241
|
+
type: "string",
|
|
242
|
+
minLength: 1,
|
|
243
|
+
description: "requires_input 时必填,并在下一次请求中原样回传。",
|
|
244
|
+
},
|
|
245
|
+
questions: {
|
|
246
|
+
$ref: "#/$defs/question_group",
|
|
247
|
+
description: "requires_input 时必填的结构化追问。",
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
allOf: [
|
|
251
|
+
{
|
|
252
|
+
if: {
|
|
253
|
+
properties: {
|
|
254
|
+
finish_reason: {
|
|
255
|
+
const: "requires_input",
|
|
256
|
+
description: "需要使用者回答。",
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
then: { required: ["last_tool_message_id", "questions"] },
|
|
261
|
+
else: {
|
|
262
|
+
not: {
|
|
263
|
+
anyOf: [
|
|
264
|
+
{ required: ["last_tool_message_id"] },
|
|
265
|
+
{ required: ["questions"] },
|
|
266
|
+
],
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
},
|
|
272
|
+
question_group: {
|
|
273
|
+
type: "object",
|
|
274
|
+
description: "GD Agent 在本轮提出的一组问题。",
|
|
275
|
+
additionalProperties: false,
|
|
276
|
+
required: ["title", "questions"],
|
|
277
|
+
properties: {
|
|
278
|
+
title: {
|
|
279
|
+
type: "string",
|
|
280
|
+
description: "本组追问的标题。",
|
|
281
|
+
},
|
|
282
|
+
questions: {
|
|
283
|
+
type: "array",
|
|
284
|
+
minItems: 1,
|
|
285
|
+
description: "需要使用者回答的一个或多个问题。",
|
|
286
|
+
items: {
|
|
287
|
+
type: "object",
|
|
288
|
+
description: "一条 GD Agent 追问。",
|
|
289
|
+
additionalProperties: false,
|
|
290
|
+
required: ["question", "options", "allow_multiple"],
|
|
291
|
+
properties: {
|
|
292
|
+
question: {
|
|
293
|
+
type: "string",
|
|
294
|
+
minLength: 1,
|
|
295
|
+
description: "问题文本;提交 answers 时原样作为 answers[].question。",
|
|
296
|
+
},
|
|
297
|
+
options: {
|
|
298
|
+
type: "array",
|
|
299
|
+
description: "推荐选项,可以为空;label 不是严格枚举。",
|
|
300
|
+
items: {
|
|
301
|
+
type: "object",
|
|
302
|
+
description: "一项推荐回答。",
|
|
303
|
+
additionalProperties: false,
|
|
304
|
+
required: ["label"],
|
|
305
|
+
properties: {
|
|
306
|
+
label: {
|
|
307
|
+
type: "string",
|
|
308
|
+
minLength: 1,
|
|
309
|
+
description: "推荐回答的展示文本。",
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
allow_multiple: {
|
|
315
|
+
type: "boolean",
|
|
316
|
+
description: "是否允许为本问题提交多个回答值。",
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
output: {
|
|
324
|
+
description: "文本、JSON、图片、视频、音频或文件结果。",
|
|
325
|
+
oneOf: [
|
|
326
|
+
{
|
|
327
|
+
type: "object",
|
|
328
|
+
description: "文本结果。",
|
|
329
|
+
additionalProperties: false,
|
|
330
|
+
required: ["type", "text"],
|
|
331
|
+
properties: {
|
|
332
|
+
type: { const: "text", description: "结果类型。" },
|
|
333
|
+
text: { type: "string", description: "文本内容。" },
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
type: "object",
|
|
338
|
+
description: "可机器解析的结构化结果。",
|
|
339
|
+
additionalProperties: false,
|
|
340
|
+
required: ["type", "value"],
|
|
341
|
+
properties: {
|
|
342
|
+
type: { const: "json", description: "结果类型。" },
|
|
343
|
+
value: { description: "任意合法 JSON 值。" },
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
type: "object",
|
|
348
|
+
description: "最终媒体或文件结果。",
|
|
349
|
+
additionalProperties: false,
|
|
350
|
+
required: ["type", "url", "mime_type"],
|
|
351
|
+
properties: {
|
|
352
|
+
type: {
|
|
353
|
+
type: "string",
|
|
354
|
+
enum: ["image", "video", "audio", "file"],
|
|
355
|
+
description: "媒体或文件类型。",
|
|
356
|
+
},
|
|
357
|
+
url: {
|
|
358
|
+
type: "string",
|
|
359
|
+
format: "uri",
|
|
360
|
+
pattern: "^https?://",
|
|
361
|
+
description: "可访问或下载结果的 HTTP(S) URL。",
|
|
362
|
+
},
|
|
363
|
+
mime_type: {
|
|
364
|
+
type: "string",
|
|
365
|
+
minLength: 1,
|
|
366
|
+
description: "结果的 MIME type,例如 image/png 或 video/mp4。",
|
|
367
|
+
},
|
|
368
|
+
name: {
|
|
369
|
+
type: "string",
|
|
370
|
+
minLength: 1,
|
|
371
|
+
description: "建议的展示名或文件名。",
|
|
372
|
+
},
|
|
373
|
+
asset_id: {
|
|
374
|
+
type: "string",
|
|
375
|
+
minLength: 1,
|
|
376
|
+
description: "结果已进入 DAM 时的素材标识。",
|
|
377
|
+
},
|
|
378
|
+
width: {
|
|
379
|
+
type: "number",
|
|
380
|
+
minimum: 0,
|
|
381
|
+
description: "图片或视频的像素宽度。",
|
|
382
|
+
},
|
|
383
|
+
height: {
|
|
384
|
+
type: "number",
|
|
385
|
+
minimum: 0,
|
|
386
|
+
description: "图片或视频的像素高度。",
|
|
387
|
+
},
|
|
388
|
+
duration_seconds: {
|
|
389
|
+
type: "number",
|
|
390
|
+
minimum: 0,
|
|
391
|
+
description: "视频或音频的时长,单位为秒。",
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
],
|
|
396
|
+
},
|
|
397
|
+
error: {
|
|
398
|
+
type: "object",
|
|
399
|
+
description: "Creative 失败时的稳定错误信息。",
|
|
400
|
+
additionalProperties: false,
|
|
401
|
+
required: ["code", "type", "message"],
|
|
402
|
+
properties: {
|
|
403
|
+
code: {
|
|
404
|
+
type: "string",
|
|
405
|
+
minLength: 1,
|
|
406
|
+
description: "稳定、可机器判断的错误码。",
|
|
407
|
+
},
|
|
408
|
+
type: {
|
|
409
|
+
type: "string",
|
|
410
|
+
enum: ["validation", "auth", "business", "network", "unknown"],
|
|
411
|
+
description: "错误所属类别。",
|
|
412
|
+
},
|
|
413
|
+
message: {
|
|
414
|
+
type: "string",
|
|
415
|
+
minLength: 1,
|
|
416
|
+
description: "面向使用者的错误说明。",
|
|
417
|
+
},
|
|
418
|
+
hint: {
|
|
419
|
+
type: "string",
|
|
420
|
+
minLength: 1,
|
|
421
|
+
description: "可执行的处理建议。",
|
|
422
|
+
},
|
|
423
|
+
retryable: {
|
|
424
|
+
type: "boolean",
|
|
425
|
+
description: "使用相同输入稍后重试是否可能成功。",
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
export function getCreativeSchema() {
|
|
432
|
+
return {
|
|
433
|
+
schema_version: CREATIVE_SCHEMA_VERSION,
|
|
434
|
+
input_schema: CREATIVE_INPUT_SCHEMA,
|
|
435
|
+
output_schema: CREATIVE_OUTPUT_SCHEMA,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
export const CREATIVE_EXAMPLES = {
|
|
439
|
+
examples: [
|
|
440
|
+
{
|
|
441
|
+
name: "text_creation",
|
|
442
|
+
description: "纯文本创作",
|
|
443
|
+
input: {
|
|
444
|
+
prompt: "为一款轻量缓震跑鞋写三条电商主图标题,语言简洁有力。",
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
name: "image_with_reference",
|
|
449
|
+
description: "使用参考图创作图片",
|
|
450
|
+
input: {
|
|
451
|
+
prompt: "以产品为主体生成一张户外晨跑场景广告图,保持鞋款外观和配色。",
|
|
452
|
+
media: [
|
|
453
|
+
{
|
|
454
|
+
source: "./product.png",
|
|
455
|
+
role: "reference",
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
parameters: {
|
|
459
|
+
ratio: "1:1",
|
|
460
|
+
resolution: "2K",
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
name: "video_with_first_and_last_frames",
|
|
466
|
+
description: "使用首尾帧创作视频",
|
|
467
|
+
input: {
|
|
468
|
+
prompt: "生成从清晨城市街道过渡到终点冲线的运动广告视频,镜头运动自然。",
|
|
469
|
+
media: [
|
|
470
|
+
{
|
|
471
|
+
source: "./first-frame.png",
|
|
472
|
+
role: "first_frame",
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
source: "./last-frame.png",
|
|
476
|
+
role: "last_frame",
|
|
477
|
+
},
|
|
478
|
+
],
|
|
479
|
+
parameters: {
|
|
480
|
+
ratio: "16:9",
|
|
481
|
+
resolution: "1080p",
|
|
482
|
+
duration_seconds: 5,
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
name: "continue_creation",
|
|
488
|
+
description: "继续修改已有创作",
|
|
489
|
+
input: {
|
|
490
|
+
conversation_id: "conversation_123",
|
|
491
|
+
prompt: "保留主体和构图,把整体风格调整得更克制,并降低背景饱和度。",
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
name: "answer_questions",
|
|
496
|
+
description: "回答 GD Agent 的结构化追问",
|
|
497
|
+
input: {
|
|
498
|
+
conversation_id: "conversation_123",
|
|
499
|
+
last_tool_message_id: "message_456",
|
|
500
|
+
answers: [
|
|
501
|
+
{
|
|
502
|
+
question: "希望采用什么视觉风格?",
|
|
503
|
+
values: ["极简"],
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
question: "主要投放到哪些渠道?",
|
|
507
|
+
values: ["电商详情页", "社交媒体"],
|
|
508
|
+
},
|
|
509
|
+
],
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
],
|
|
513
|
+
};
|
|
514
|
+
export function getCreativeExamples() {
|
|
515
|
+
return CREATIVE_EXAMPLES;
|
|
516
|
+
}
|
|
517
|
+
const INPUT_KEYS = new Set([
|
|
518
|
+
"prompt",
|
|
519
|
+
"media",
|
|
520
|
+
"parameters",
|
|
521
|
+
"conversation_id",
|
|
522
|
+
"last_tool_message_id",
|
|
523
|
+
"answers",
|
|
524
|
+
]);
|
|
525
|
+
const PARAMETER_KEYS = new Set(["ratio", "resolution", "duration_seconds"]);
|
|
526
|
+
const MEDIA_KEYS = new Set(["source", "role"]);
|
|
527
|
+
const ANSWER_KEYS = new Set(["question", "values"]);
|
|
528
|
+
const MEDIA_ROLES = new Set(["reference", "first_frame", "last_frame"]);
|
|
529
|
+
const IMAGE_EXTENSIONS = new Set([
|
|
530
|
+
"avif",
|
|
531
|
+
"bmp",
|
|
532
|
+
"gif",
|
|
533
|
+
"heic",
|
|
534
|
+
"heif",
|
|
535
|
+
"jpeg",
|
|
536
|
+
"jpg",
|
|
537
|
+
"png",
|
|
538
|
+
"svg",
|
|
539
|
+
"webp",
|
|
540
|
+
]);
|
|
541
|
+
const VIDEO_EXTENSIONS = new Set(["avi", "m4v", "mkv", "mov", "mp4", "webm"]);
|
|
542
|
+
const AUDIO_EXTENSIONS = new Set([
|
|
543
|
+
"aac",
|
|
544
|
+
"flac",
|
|
545
|
+
"m4a",
|
|
546
|
+
"mp3",
|
|
547
|
+
"oga",
|
|
548
|
+
"ogg",
|
|
549
|
+
"opus",
|
|
550
|
+
"wav",
|
|
551
|
+
]);
|
|
552
|
+
const KNOWN_NON_MEDIA_EXTENSIONS = new Set([
|
|
553
|
+
"csv",
|
|
554
|
+
"doc",
|
|
555
|
+
"docx",
|
|
556
|
+
"html",
|
|
557
|
+
"json",
|
|
558
|
+
"pdf",
|
|
559
|
+
"ppt",
|
|
560
|
+
"pptx",
|
|
561
|
+
"txt",
|
|
562
|
+
"xls",
|
|
563
|
+
"xlsx",
|
|
564
|
+
"xml",
|
|
565
|
+
"zip",
|
|
566
|
+
]);
|
|
567
|
+
export function validateCreativeInput(value) {
|
|
568
|
+
const input = requireRecord(value, "$", "Creative 输入必须是 JSON object");
|
|
569
|
+
rejectUnknownKeys(input, INPUT_KEYS, "$");
|
|
570
|
+
const hasPrompt = Object.hasOwn(input, "prompt");
|
|
571
|
+
const hasAnswers = Object.hasOwn(input, "answers");
|
|
572
|
+
const prompt = hasPrompt ? requireNonEmptyString(input.prompt, "prompt") : undefined;
|
|
573
|
+
const conversationId = Object.hasOwn(input, "conversation_id")
|
|
574
|
+
? requireNonEmptyString(input.conversation_id, "conversation_id")
|
|
575
|
+
: undefined;
|
|
576
|
+
const lastToolMessageId = Object.hasOwn(input, "last_tool_message_id")
|
|
577
|
+
? requireNonEmptyString(input.last_tool_message_id, "last_tool_message_id")
|
|
578
|
+
: undefined;
|
|
579
|
+
if (!hasPrompt && !hasAnswers) {
|
|
580
|
+
invalid("prompt", "普通创作必须提供非空 prompt;回答追问时可以改为提供 answers");
|
|
581
|
+
}
|
|
582
|
+
if (lastToolMessageId && !conversationId) {
|
|
583
|
+
invalid("last_tool_message_id", "last_tool_message_id 必须与 conversation_id 一起使用");
|
|
584
|
+
}
|
|
585
|
+
if (Object.hasOwn(input, "media"))
|
|
586
|
+
validateMedia(input.media);
|
|
587
|
+
if (Object.hasOwn(input, "parameters"))
|
|
588
|
+
validateParameters(input.parameters);
|
|
589
|
+
if (hasAnswers) {
|
|
590
|
+
validateAnswers(input.answers);
|
|
591
|
+
if (!conversationId || !lastToolMessageId) {
|
|
592
|
+
invalid("answers", "answers 必须同时携带 conversation_id 和 last_tool_message_id");
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (prompt === undefined && !hasAnswers) {
|
|
596
|
+
invalid("prompt", "prompt 不能为空");
|
|
597
|
+
}
|
|
598
|
+
return input;
|
|
599
|
+
}
|
|
600
|
+
function validateMedia(value) {
|
|
601
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
602
|
+
invalid("media", "media 必须是至少包含一项素材的数组");
|
|
603
|
+
}
|
|
604
|
+
let firstFrameCount = 0;
|
|
605
|
+
let lastFrameCount = 0;
|
|
606
|
+
value.forEach((item, index) => {
|
|
607
|
+
const path = `media[${index}]`;
|
|
608
|
+
const media = requireRecord(item, path, `${path} 必须是 object`);
|
|
609
|
+
rejectUnknownKeys(media, MEDIA_KEYS, path);
|
|
610
|
+
const source = requireNonEmptyString(media.source, `${path}.source`);
|
|
611
|
+
const role = requireNonEmptyString(media.role, `${path}.role`);
|
|
612
|
+
if (!MEDIA_ROLES.has(role)) {
|
|
613
|
+
invalid(`${path}.role`, "role 只支持 reference、first_frame 或 last_frame");
|
|
614
|
+
}
|
|
615
|
+
validateMediaSource(source, role, `${path}.source`);
|
|
616
|
+
if (role === "first_frame")
|
|
617
|
+
firstFrameCount += 1;
|
|
618
|
+
if (role === "last_frame")
|
|
619
|
+
lastFrameCount += 1;
|
|
620
|
+
});
|
|
621
|
+
if (firstFrameCount > 1) {
|
|
622
|
+
invalid("media", "first_frame 最多只能提供一份");
|
|
623
|
+
}
|
|
624
|
+
if (lastFrameCount > 1) {
|
|
625
|
+
invalid("media", "last_frame 最多只能提供一份");
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function validateMediaSource(source, role, path) {
|
|
629
|
+
const isWindowsPath = /^[A-Za-z]:[\\/]/.test(source);
|
|
630
|
+
const scheme = !isWindowsPath ? source.match(/^([A-Za-z][A-Za-z\d+.-]*):/)?.[1] : undefined;
|
|
631
|
+
if (scheme && scheme !== "http" && scheme !== "https") {
|
|
632
|
+
invalid(path, "source 只支持本地文件路径或 HTTP(S) URL");
|
|
633
|
+
}
|
|
634
|
+
const remote = /^https?:\/\//i.test(source);
|
|
635
|
+
if (remote) {
|
|
636
|
+
try {
|
|
637
|
+
new URL(source);
|
|
638
|
+
}
|
|
639
|
+
catch {
|
|
640
|
+
invalid(path, "source 必须是合法的 HTTP(S) URL");
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
const extension = mediaExtension(source, remote);
|
|
644
|
+
const kind = mediaKind(extension);
|
|
645
|
+
if (role === "first_frame" || role === "last_frame") {
|
|
646
|
+
if (kind === "video" || kind === "audio" || kind === "unsupported") {
|
|
647
|
+
invalid(path, `${role} 只支持图片素材`);
|
|
648
|
+
}
|
|
649
|
+
if (!remote && kind !== "image") {
|
|
650
|
+
invalid(path, `${role} 的本地文件必须是支持的图片格式`);
|
|
651
|
+
}
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (kind === "unsupported" || (!remote && kind === "unknown")) {
|
|
655
|
+
invalid(path, "reference 只支持图片、视频或音频素材");
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
function validateParameters(value) {
|
|
659
|
+
const parameters = requireRecord(value, "parameters", "parameters 必须是 object");
|
|
660
|
+
rejectUnknownKeys(parameters, PARAMETER_KEYS, "parameters");
|
|
661
|
+
if (Object.hasOwn(parameters, "ratio")) {
|
|
662
|
+
requireNonEmptyString(parameters.ratio, "parameters.ratio");
|
|
663
|
+
}
|
|
664
|
+
if (Object.hasOwn(parameters, "resolution")) {
|
|
665
|
+
requireNonEmptyString(parameters.resolution, "parameters.resolution");
|
|
666
|
+
}
|
|
667
|
+
if (Object.hasOwn(parameters, "duration_seconds")) {
|
|
668
|
+
const duration = parameters.duration_seconds;
|
|
669
|
+
if (typeof duration !== "number" || !Number.isInteger(duration) || duration < 1) {
|
|
670
|
+
invalid("parameters.duration_seconds", "duration_seconds 必须是大于等于 1 的整数");
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
function validateAnswers(value) {
|
|
675
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
676
|
+
invalid("answers", "answers 必须是至少包含一项回答的数组");
|
|
677
|
+
}
|
|
678
|
+
value.forEach((item, index) => {
|
|
679
|
+
const path = `answers[${index}]`;
|
|
680
|
+
const answer = requireRecord(item, path, `${path} 必须是 object`);
|
|
681
|
+
rejectUnknownKeys(answer, ANSWER_KEYS, path);
|
|
682
|
+
requireNonEmptyString(answer.question, `${path}.question`);
|
|
683
|
+
if (!Array.isArray(answer.values) || answer.values.length === 0) {
|
|
684
|
+
invalid(`${path}.values`, "values 必须至少包含一项非空回答");
|
|
685
|
+
}
|
|
686
|
+
answer.values.forEach((itemValue, valueIndex) => {
|
|
687
|
+
requireNonEmptyString(itemValue, `${path}.values[${valueIndex}]`);
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
function requireRecord(value, path, message) {
|
|
692
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
693
|
+
invalid(path, message);
|
|
694
|
+
}
|
|
695
|
+
return value;
|
|
696
|
+
}
|
|
697
|
+
function requireNonEmptyString(value, path) {
|
|
698
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
699
|
+
invalid(path, `${path} 必须是非空字符串`);
|
|
700
|
+
}
|
|
701
|
+
return value;
|
|
702
|
+
}
|
|
703
|
+
function rejectUnknownKeys(value, allowed, path) {
|
|
704
|
+
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
705
|
+
if (unknown.length > 0) {
|
|
706
|
+
invalid(path, `不支持字段: ${unknown.join(", ")}`);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function mediaExtension(source, remote) {
|
|
710
|
+
let pathname = source;
|
|
711
|
+
if (remote) {
|
|
712
|
+
try {
|
|
713
|
+
pathname = new URL(source).pathname;
|
|
714
|
+
}
|
|
715
|
+
catch {
|
|
716
|
+
return "";
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
const filename = pathname.split(/[\\/]/).pop() ?? "";
|
|
720
|
+
const dot = filename.lastIndexOf(".");
|
|
721
|
+
return dot >= 0 ? filename.slice(dot + 1).toLowerCase() : "";
|
|
722
|
+
}
|
|
723
|
+
function mediaKind(extension) {
|
|
724
|
+
if (IMAGE_EXTENSIONS.has(extension))
|
|
725
|
+
return "image";
|
|
726
|
+
if (VIDEO_EXTENSIONS.has(extension))
|
|
727
|
+
return "video";
|
|
728
|
+
if (AUDIO_EXTENSIONS.has(extension))
|
|
729
|
+
return "audio";
|
|
730
|
+
if (KNOWN_NON_MEDIA_EXTENSIONS.has(extension))
|
|
731
|
+
return "unsupported";
|
|
732
|
+
return "unknown";
|
|
733
|
+
}
|
|
734
|
+
function invalid(path, message) {
|
|
735
|
+
throw new CliUsageError({
|
|
736
|
+
code: "PARAM_INVALID",
|
|
737
|
+
type: "validation",
|
|
738
|
+
message,
|
|
739
|
+
hint: "请运行 gd-cli creative schema --format json 查看完整输入结构。",
|
|
740
|
+
retryable: false,
|
|
741
|
+
detail: { path },
|
|
742
|
+
});
|
|
743
|
+
}
|