gaoding-cli 1.0.0-alpha.7 → 1.0.0-alpha.9
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 +38 -0
- package/README.md +47 -60
- package/dist/bin/gd-cli.js +2 -2
- package/dist/src/api/app-runner.d.ts +1 -4
- package/dist/src/api/app-runner.js +11 -44
- package/dist/src/api/creative-client.d.ts +34 -0
- package/dist/src/api/creative-client.js +184 -0
- package/dist/src/api/dam-client.d.ts +0 -7
- package/dist/src/api/dam-client.js +0 -17
- 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 +17 -53
- package/dist/src/commands/creative.d.ts +8 -0
- package/dist/src/commands/creative.js +178 -0
- package/dist/src/commands/dam.js +270 -127
- package/dist/src/commands/org.js +31 -47
- package/dist/src/commands/skills.js +33 -118
- package/dist/src/commands/update.js +11 -82
- package/dist/src/commands/usage.d.ts +1 -1
- package/dist/src/commands/usage.js +10 -21
- package/dist/src/commands/workflows.d.ts +1 -1
- package/dist/src/commands/workflows.js +299 -197
- 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/core/output/envelope.d.ts +1 -0
- package/dist/src/core/output/envelope.js +76 -6
- package/dist/src/creative/contract.d.ts +1060 -0
- package/dist/src/creative/contract.js +722 -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/io.d.ts +0 -2
- package/dist/src/io.js +0 -8
- 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 +8 -0
- package/dist/src/middleware/error-handler.js +61 -57
- package/dist/src/program.d.ts +1 -0
- package/dist/src/program.js +137 -122
- package/dist/src/registry/agent-contracts.d.ts +14 -34
- package/dist/src/registry/agent-contracts.js +209 -297
- 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/capability-presenter.js +6 -29
- 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/dist/src/utils/sensitive-path.js +1 -4
- package/package.json +4 -3
- package/skills/gd-cli/SKILL.md +13 -12
- package/skills/gd-cli/references/auth.md +9 -10
- package/skills/gd-cli/references/creative.md +47 -0
- package/skills/gd-cli/references/entitlement.md +3 -3
- package/skills/gd-cli/references/errors.md +10 -3
- package/skills/gd-cli/references/org.md +3 -3
- package/skills/gd-cli/references/sop.md +8 -8
- package/skills/gd-cli/references/update.md +4 -3
- package/skills/gd-cli/references/workflows.md +7 -6
- 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/dist/src/utils/list-output-format.d.ts +0 -10
- package/dist/src/utils/list-output-format.js +0 -34
- package/skills/gd-cli/references/tools.md +0 -29
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,44 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [1.0.0-alpha.9] - 2026-08-01
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- 根命令仅保留 `--version` 和 `--help`,机器可读的 `--json` 收敛到确有脚本消费价值的叶子命令。
|
|
13
|
+
- `skills install` 与 `skills uninstall` 在未指定目标时默认处理全部 Agent,且不再二次确认;`update` 收敛为无选项动作。
|
|
14
|
+
- Creative 使用语义化的 `question_message_id`,并收紧输入、输出 Schema 与错误响应契约。
|
|
15
|
+
- Workflow 固定内部并发为 2,并由 CLI 选项显式授权敏感路径。
|
|
16
|
+
|
|
17
|
+
### Removed
|
|
18
|
+
|
|
19
|
+
- 删除根级 `--format`、`--json`、`--no-input` 和 `--yes`,以及不再具有明确语义的重复输出选项。
|
|
20
|
+
|
|
21
|
+
### Security
|
|
22
|
+
|
|
23
|
+
- DAM 公开结果改为白名单 DTO,并过滤私网地址、危险协议、userinfo 与敏感查询参数。
|
|
24
|
+
- 公共错误响应改为字段白名单,不再透传后端 `detail` 或内部 Tool/App 路由信息。
|
|
25
|
+
|
|
26
|
+
## [1.0.0-alpha.8] - 2026-08-01
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- 新增统一 `creative` 创作入口及可发现的输入输出 Schema、调用示例和连续创作支持。
|
|
31
|
+
- 新增集中式鉴权前置层,按命令声明凭证或当前组织要求,并在请求前透明刷新当前组织授权。
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- `org credits` 取代顶层 `usage`,组织绑定与刷新有效期不再作为公开概念展示。
|
|
36
|
+
- DAM 素材格式筛选统一使用 `--asset-format`,全局 `--format` 仅控制输出格式。
|
|
37
|
+
|
|
38
|
+
### Removed
|
|
39
|
+
|
|
40
|
+
- 删除公开的 `tools`、`models` 以及顶层 `usage`、`manifest` 和 `examples` 命令。
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- 升级 `brace-expansion` 补丁版本,修复 production 依赖链中的高危拒绝服务漏洞。
|
|
45
|
+
|
|
8
46
|
## [1.0.0-alpha.7] - 2026-07-23
|
|
9
47
|
|
|
10
48
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# gaoding-cli
|
|
2
2
|
|
|
3
|
-
GD CLI 是稿定设计能力的命令行入口,为用户、脚本、CI 和 Agent
|
|
3
|
+
GD CLI 是稿定设计能力的命令行入口,为用户、脚本、CI 和 Agent 提供认证、组织切换、统一 Creative 创作、多步骤 Workflow 和 DAM 素材管理。
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
@@ -9,91 +9,91 @@ GD CLI 是稿定设计能力的命令行入口,为用户、脚本、CI 和 Age
|
|
|
9
9
|
|
|
10
10
|
## Installation
|
|
11
11
|
|
|
12
|
-
默认使用全局安装:
|
|
13
|
-
|
|
14
12
|
```bash
|
|
15
13
|
npm install -g gaoding-cli
|
|
16
14
|
gd-cli skills install
|
|
17
15
|
gd-cli auth login
|
|
18
16
|
```
|
|
19
17
|
|
|
20
|
-
Agent
|
|
18
|
+
`npm install -g gaoding-cli` 只安装 CLI。执行 `gd-cli skills install` 才会安装 Agent Skill;不传参数时安装到全部已知 Agent target。详细规则见[官方 Agent 安装文档](https://www.gaoding.com/cli/install.md)。
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
在 Agent 或 CI 中,可指定一个或多个 target;安装和卸载均为人类可读输出,用 `skills status --json` 检查机器状态:
|
|
23
21
|
|
|
24
22
|
```bash
|
|
25
|
-
gd-cli skills install --agent codex
|
|
26
|
-
gd-cli
|
|
23
|
+
gd-cli skills install --agent codex
|
|
24
|
+
gd-cli skills status --json
|
|
25
|
+
gd-cli auth login --no-browser
|
|
27
26
|
```
|
|
28
27
|
|
|
29
|
-
`npm install -g gaoding-cli` 只安装 CLI,不会修改任何 Agent 目录。只有显式执行 `gd-cli skills install` 才会安装 Agent Skill。
|
|
30
|
-
|
|
31
28
|
## Quick Start
|
|
32
29
|
|
|
33
30
|
```bash
|
|
34
|
-
gd-cli auth status
|
|
35
|
-
|
|
36
|
-
gd-cli org list
|
|
31
|
+
gd-cli auth status --json
|
|
32
|
+
gd-cli org list --json
|
|
37
33
|
gd-cli org switch --org <org-id>
|
|
38
34
|
|
|
39
|
-
gd-cli
|
|
40
|
-
gd-cli
|
|
41
|
-
gd-cli
|
|
35
|
+
gd-cli creative schema --json
|
|
36
|
+
gd-cli creative examples
|
|
37
|
+
gd-cli creative run --prompt "为夏季运动鞋生成一张户外通勤广告图" --json
|
|
42
38
|
|
|
43
|
-
gd-cli workflows list
|
|
39
|
+
gd-cli workflows list --json
|
|
44
40
|
gd-cli workflows run listing-gallery \
|
|
45
41
|
--image ./product.jpg \
|
|
46
|
-
--brief "夏季运动鞋,轻量缓震,适合通勤"
|
|
42
|
+
--brief "夏季运动鞋,轻量缓震,适合通勤" \
|
|
43
|
+
--json
|
|
47
44
|
|
|
48
|
-
gd-cli dam upload ./product.jpg
|
|
45
|
+
gd-cli dam upload ./product.jpg --json
|
|
49
46
|
```
|
|
50
47
|
|
|
51
48
|
## Commands
|
|
52
49
|
|
|
50
|
+
使用 `gd-cli --help` 查看一级命令,再使用 `gd-cli <command> --help` 查看子命令和选项。
|
|
51
|
+
|
|
53
52
|
| Command | Purpose |
|
|
54
53
|
| --- | --- |
|
|
55
|
-
| `auth` |
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
54
|
+
| `auth login [--no-browser]` | 登录;输出面向人 |
|
|
55
|
+
| `auth logout` | 退出登录;输出面向人 |
|
|
56
|
+
| `auth status --json` | 查询认证状态 |
|
|
57
|
+
| `org list/current/credits --json` | 查询组织和稿豆余额 |
|
|
58
|
+
| `org switch [--org]` | 切换组织;输出面向人 |
|
|
59
|
+
| `creative run [--prompt|--input] [--json]` | 发起或继续创作 |
|
|
60
|
+
| `creative schema [--json]` | 查看创作 Schema |
|
|
61
|
+
| `creative examples` | 查看完整可运行示例 |
|
|
62
|
+
| `workflows list [--json]` | 列出 Workflow |
|
|
63
|
+
| `workflows schema <workflow-id> [--json]` | 查看 Workflow Schema |
|
|
64
|
+
| `workflows examples <workflow-id>` | 查看完整可运行示例 |
|
|
65
|
+
| `workflows run <workflow-id> [--json]` | 执行 Workflow |
|
|
66
|
+
| `skills install/uninstall [--agent <id> | --all]` | 管理 Agent Skill |
|
|
67
|
+
| `skills status --json` | 查询 Skill 状态 |
|
|
68
|
+
| `skills update` | 刷新已安装 Skill |
|
|
69
|
+
| `dam upload/list/search/get --json` | 管理和查询素材 |
|
|
70
|
+
| `dam delete` | 删除素材;输出面向人 |
|
|
71
|
+
| `update` | 检查并升级全局 CLI |
|
|
72
|
+
|
|
73
|
+
默认不会因为 stdin/stdout 不是 TTY 而切换为 JSON。只有支持 `--json` 的叶命令在显式传入该选项时才输出机器可读结果。
|
|
64
74
|
|
|
65
75
|
## Agent And Script Usage
|
|
66
76
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
单项能力使用 `tools call`:
|
|
77
|
+
Creative 使用 `--prompt` 或 `--input`。媒体、结构化约束或连续会话输入使用 JSON 文件或 stdin:
|
|
70
78
|
|
|
71
79
|
```bash
|
|
72
|
-
gd-cli
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
--ratio 1:1 \
|
|
76
|
-
--format json
|
|
80
|
+
gd-cli creative run --prompt "为夏季运动鞋生成三条简洁的电商标题" --json
|
|
81
|
+
gd-cli creative run --input creative-request.json --json
|
|
82
|
+
cat creative-request.json | gd-cli creative run --input - --json
|
|
77
83
|
```
|
|
78
84
|
|
|
79
|
-
|
|
85
|
+
遇到 `finish_reason=requires_input` 时,回传结果中的 `conversation_id`、`question_message_id` 和 `answers`。完整字段由 `gd-cli creative schema --json` 定义。
|
|
80
86
|
|
|
81
|
-
|
|
82
|
-
gd-cli workflows run listing-gallery \
|
|
83
|
-
--image ./product.jpg \
|
|
84
|
-
--brief "运动鞋上架素材包" \
|
|
85
|
-
--output-dir ./output \
|
|
86
|
-
--format json
|
|
87
|
-
```
|
|
87
|
+
Workflow 的可用场景可通过 `gd-cli workflows list --json` 查看;特定输入/输出通过 `gd-cli workflows schema <workflow-id> --json` 查看。
|
|
88
88
|
|
|
89
89
|
## Agent Skill
|
|
90
90
|
|
|
91
|
-
`gd-cli skills install` 将静态 Skill 安装到 `~/.agents/skills/gd-cli
|
|
91
|
+
`gd-cli skills install` 将静态 Skill 安装到 `~/.agents/skills/gd-cli`,并为选择的 Agent 创建链接。常用维护命令:
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
|
-
gd-cli skills status --
|
|
94
|
+
gd-cli skills status --json
|
|
95
95
|
gd-cli skills update
|
|
96
|
-
gd-cli skills uninstall --all
|
|
96
|
+
gd-cli skills uninstall --all
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
CLI 全局升级后,只在 canonical Skill 已安装时自动刷新它;本地项目安装、`npm link` 和 `npx` 运行不会修改全局 CLI。
|
|
@@ -105,23 +105,10 @@ npm install
|
|
|
105
105
|
npm run build
|
|
106
106
|
npm test
|
|
107
107
|
npm run start -- --help
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
本地开发使用独立的 `gd-cli-dev`:
|
|
111
|
-
|
|
112
|
-
```bash
|
|
113
|
-
npm run dev:setup
|
|
114
|
-
npm run dev
|
|
115
|
-
gd-cli-dev --help
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
默认 smoke test 不登录、不调用付费生成能力:
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
108
|
npm run smoke:cli
|
|
122
109
|
```
|
|
123
110
|
|
|
124
|
-
|
|
111
|
+
真实接口 smoke 会使用本机登录凭证,并可能消耗额度:
|
|
125
112
|
|
|
126
113
|
```bash
|
|
127
114
|
gd-cli auth login
|
package/dist/bin/gd-cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createProgram } from "../src/program.js";
|
|
2
|
+
import { createProgram, parseProgram } from "../src/program.js";
|
|
3
3
|
async function main() {
|
|
4
4
|
const program = await createProgram();
|
|
5
|
-
await program
|
|
5
|
+
await parseProgram(program, process.argv);
|
|
6
6
|
}
|
|
7
7
|
main().catch((err) => {
|
|
8
8
|
console.error(err.message);
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { HubClient } from "./hub-client.js";
|
|
2
2
|
import type { TaskResult } from "./task-poller.js";
|
|
3
3
|
interface RunOptions {
|
|
4
|
-
|
|
5
|
-
timeout?: number;
|
|
6
|
-
outputDir?: string;
|
|
7
|
-
concurrency?: number;
|
|
4
|
+
allowSensitivePath?: boolean;
|
|
8
5
|
onStepStart?: (step: AppStepPublicRecord) => void;
|
|
9
6
|
onStepComplete?: (step: AppStepPublicRecord, result?: TaskResult) => void;
|
|
10
7
|
}
|
|
@@ -1,23 +1,5 @@
|
|
|
1
1
|
import { DirectToolInvoker } from "./direct-tools.js";
|
|
2
2
|
import { DirectApiError } from "./direct-client.js";
|
|
3
|
-
/** 多步 App 含多次生图/对话,默认至少等待 10 分钟 */
|
|
4
|
-
const APP_RUN_MIN_WAIT_SEC = 600;
|
|
5
|
-
function resolveAppWaitTimeout(explicit, estimatedSeconds) {
|
|
6
|
-
if (explicit !== undefined && Number.isFinite(explicit) && explicit > 0) {
|
|
7
|
-
return Math.floor(explicit);
|
|
8
|
-
}
|
|
9
|
-
const est = Number(estimatedSeconds);
|
|
10
|
-
if (Number.isFinite(est) && est > APP_RUN_MIN_WAIT_SEC) {
|
|
11
|
-
return Math.ceil(est);
|
|
12
|
-
}
|
|
13
|
-
return APP_RUN_MIN_WAIT_SEC;
|
|
14
|
-
}
|
|
15
|
-
function resolveConcurrency(value) {
|
|
16
|
-
const numeric = Number(value);
|
|
17
|
-
if (!Number.isFinite(numeric) || numeric <= 0)
|
|
18
|
-
return 2;
|
|
19
|
-
return Math.max(1, Math.min(4, Math.floor(numeric)));
|
|
20
|
-
}
|
|
21
3
|
async function mapWithConcurrency(items, concurrency, worker) {
|
|
22
4
|
let next = 0;
|
|
23
5
|
const runners = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
|
@@ -170,15 +152,6 @@ export class AppRunner {
|
|
|
170
152
|
void client;
|
|
171
153
|
}
|
|
172
154
|
async run(appId, params, options = {}) {
|
|
173
|
-
if (options.async) {
|
|
174
|
-
return {
|
|
175
|
-
task_id: `app-${appId}-${Date.now()}`,
|
|
176
|
-
status: "failed",
|
|
177
|
-
message: "本地 App workflow 暂不支持 --async,请去掉 --async 直接运行。",
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
void resolveAppWaitTimeout(options.timeout, undefined);
|
|
181
|
-
void options.outputDir;
|
|
182
155
|
try {
|
|
183
156
|
if (appId === "listing-gallery")
|
|
184
157
|
return await this.runListingGallery(params, options);
|
|
@@ -208,7 +181,7 @@ export class AppRunner {
|
|
|
208
181
|
detail_image: "4:5",
|
|
209
182
|
lifestyle_image: "4:5",
|
|
210
183
|
});
|
|
211
|
-
const prepared = await this.prepareProduct(ctx, image, allowSensitivePath
|
|
184
|
+
const prepared = await this.prepareProduct(ctx, image, options.allowSensitivePath === true);
|
|
212
185
|
const title = await this.generateTitle(ctx, brief);
|
|
213
186
|
const titleText = outputString(title, "text", false) || brief;
|
|
214
187
|
const promptPlan = await this.optimizePrompt(ctx, "prompt_plan", "生成图库素材规划", `为电商 Listing 图库生成素材规划:${brief}。品类判断:${profile.label}。需要覆盖白底主图、单卖点证明图、材质/结构细节图、生活方式图;${copyPlanningGuidance(copyPlan)}每张图只回答一个购买问题,不生成假认证和不可验证参数。`, prepared.cutoutUrl, `Listing 图库素材规划:主图、卖点图、细节图、生活方式图。品类:${profile.label}。商品卖点:${brief}`);
|
|
@@ -310,7 +283,7 @@ export class AppRunner {
|
|
|
310
283
|
overlayCopy: overlayCopy.lifestyle_image,
|
|
311
284
|
}),
|
|
312
285
|
},
|
|
313
|
-
]
|
|
286
|
+
]);
|
|
314
287
|
return ctx.completed();
|
|
315
288
|
}
|
|
316
289
|
async runPdpAPlus(params, options) {
|
|
@@ -326,7 +299,7 @@ export class AppRunner {
|
|
|
326
299
|
usage_module: "4:5",
|
|
327
300
|
comparison_module: "1:1",
|
|
328
301
|
});
|
|
329
|
-
const prepared = await this.prepareProduct(ctx, image, allowSensitivePath
|
|
302
|
+
const prepared = await this.prepareProduct(ctx, image, options.allowSensitivePath === true);
|
|
330
303
|
const title = await this.generateTitle(ctx, brief);
|
|
331
304
|
const titleText = outputString(title, "text", false) || brief;
|
|
332
305
|
const promptPlan = await this.optimizePrompt(ctx, "prompt_plan", "生成详情页模块规划", `为电商详情页/A+模块生成购买说服顺序:${brief}。品类判断:${profile.label}。模块包含首屏、痛点解决、结构/细节证明、真实使用场景、对比规格;${copyPlanningGuidance(copyPlan)}不编造参数和认证。`, prepared.cutoutUrl, `详情页模块规划:首屏、痛点、结构细节、使用场景、对比规格。品类:${profile.label}。商品信息:${brief}`);
|
|
@@ -451,7 +424,7 @@ export class AppRunner {
|
|
|
451
424
|
overlayCopy: overlayCopy.comparison_module,
|
|
452
425
|
}),
|
|
453
426
|
},
|
|
454
|
-
]
|
|
427
|
+
]);
|
|
455
428
|
return ctx.completed();
|
|
456
429
|
}
|
|
457
430
|
async runSocialSeedingPack(params, options) {
|
|
@@ -464,7 +437,7 @@ export class AppRunner {
|
|
|
464
437
|
cover_image: "3:4",
|
|
465
438
|
scene_image: "9:16",
|
|
466
439
|
});
|
|
467
|
-
const prepared = await this.prepareProduct(ctx, image, allowSensitivePath
|
|
440
|
+
const prepared = await this.prepareProduct(ctx, image, options.allowSensitivePath === true);
|
|
468
441
|
const title = await this.generateTitle(ctx, brief);
|
|
469
442
|
const titleText = outputString(title, "text", false) || brief;
|
|
470
443
|
const xhsCaption = buildXhsCaption(titleText, brief);
|
|
@@ -534,7 +507,7 @@ export class AppRunner {
|
|
|
534
507
|
overlayCopy: overlayCopy.scene_image,
|
|
535
508
|
}),
|
|
536
509
|
},
|
|
537
|
-
]
|
|
510
|
+
]);
|
|
538
511
|
return ctx.completed();
|
|
539
512
|
}
|
|
540
513
|
async runCampaignKvPack(params, options) {
|
|
@@ -544,7 +517,7 @@ export class AppRunner {
|
|
|
544
517
|
const ratios = parseRatioList(params.ratios ?? params.ratio, ["16:9", "1:1", "4:5"]);
|
|
545
518
|
const profile = inferProductCategoryProfile(brief);
|
|
546
519
|
const copyPlan = resolveCopyPlan(params, brief);
|
|
547
|
-
const prepared = await this.prepareProduct(ctx, image, allowSensitivePath
|
|
520
|
+
const prepared = await this.prepareProduct(ctx, image, options.allowSensitivePath === true);
|
|
548
521
|
const title = await this.generateTitle(ctx, brief);
|
|
549
522
|
const titleText = outputString(title, "text", false) || brief;
|
|
550
523
|
const promptPlan = await this.optimizePrompt(ctx, "prompt_plan", "生成 Campaign KV 主视觉提示词", `为新品 Campaign KV 生成${copyPlan.mode === "no-text" ? "无字主视觉" : "可直接投放主视觉"}提示词:${brief}。品类判断:${profile.label}。要求一个清晰记忆符号,可裁切到官网、社媒、电商活动和投放;${copyPlanningGuidance(copyPlan)}不改 logo、不生成假促销标。`, prepared.cutoutUrl, copyPlan.mode === "no-text"
|
|
@@ -595,11 +568,11 @@ export class AppRunner {
|
|
|
595
568
|
overlayCopy: String(overlayCopy[key] ?? ""),
|
|
596
569
|
}),
|
|
597
570
|
};
|
|
598
|
-
})
|
|
571
|
+
}));
|
|
599
572
|
return ctx.completed();
|
|
600
573
|
}
|
|
601
|
-
async generateImageJobs(ctx, jobs
|
|
602
|
-
await mapWithConcurrency(jobs,
|
|
574
|
+
async generateImageJobs(ctx, jobs) {
|
|
575
|
+
await mapWithConcurrency(jobs, 2, async (job) => {
|
|
603
576
|
const result = await this.generateImage(ctx, job.key, job.stepName, job.prompt, job.image, job.ratio, job.model);
|
|
604
577
|
ctx.outputs[job.key] = result.outputs;
|
|
605
578
|
ctx.addAsset(job.label, job.key, result);
|
|
@@ -752,9 +725,8 @@ function normalizeAppStepError(error, step) {
|
|
|
752
725
|
: "business",
|
|
753
726
|
message: `${step.step_name} 执行失败:${friendlyErrorMessage(rawMessage)}`,
|
|
754
727
|
raw_message: rawMessage,
|
|
755
|
-
hint: "
|
|
728
|
+
hint: "请调整图片或 brief 后重试当前 Workflow;内部步骤由 Workflow 自动编排。",
|
|
756
729
|
next_steps: [
|
|
757
|
-
`单独验证该步骤:gd-cli tools call ${step.tool}`,
|
|
758
730
|
"调整输入后重试原 workflows run 命令。",
|
|
759
731
|
],
|
|
760
732
|
};
|
|
@@ -1160,11 +1132,6 @@ function optionalString(value) {
|
|
|
1160
1132
|
const normalized = value.trim();
|
|
1161
1133
|
return normalized ? normalized : undefined;
|
|
1162
1134
|
}
|
|
1163
|
-
function allowSensitivePath(params) {
|
|
1164
|
-
return (params.allow_sensitive_path === true ||
|
|
1165
|
-
params.allowSensitivePath === true ||
|
|
1166
|
-
params["allow-sensitive-path"] === true);
|
|
1167
|
-
}
|
|
1168
1135
|
function outputString(result, key, required = true) {
|
|
1169
1136
|
const outputs = result.outputs;
|
|
1170
1137
|
const value = outputs?.[key];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { DamClient, type DamUploadOptions } from "./dam-client.js";
|
|
2
|
+
import { type DirectRequestOptions } from "./direct-client.js";
|
|
3
|
+
import { type CreativeAgentMessage, type CreativeAgentRequest } from "../creative/protocol.js";
|
|
4
|
+
import { type CreativeInput, type CreativeResult } from "../creative/contract.js";
|
|
5
|
+
export declare const CREATIVE_COMPLETION_PATH: "/ai-agent/v1/thread/completion";
|
|
6
|
+
export interface CreativeStreamClient {
|
|
7
|
+
postStream(path: string, body?: unknown, options?: DirectRequestOptions): Promise<Response>;
|
|
8
|
+
}
|
|
9
|
+
export interface CreativeUploadClient {
|
|
10
|
+
uploadLocalAsset(file: string, options?: DamUploadOptions): Promise<Awaited<ReturnType<DamClient["uploadLocalAsset"]>>>;
|
|
11
|
+
}
|
|
12
|
+
export interface CreativeClientDeps {
|
|
13
|
+
api?: CreativeStreamClient;
|
|
14
|
+
dam?: CreativeUploadClient;
|
|
15
|
+
idFactory?: () => string;
|
|
16
|
+
}
|
|
17
|
+
export declare class CreativeClient {
|
|
18
|
+
private readonly api;
|
|
19
|
+
private readonly dam;
|
|
20
|
+
private readonly idFactory;
|
|
21
|
+
constructor(deps?: CreativeClientDeps);
|
|
22
|
+
run(value: unknown): Promise<CreativeResult>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Consume the Editor completion body without exposing transport events to the
|
|
26
|
+
* CLI caller. The Editor currently emits JSON arrays, sometimes as adjacent
|
|
27
|
+
* JSON values and sometimes wrapped in `data:` SSE records, so both forms are
|
|
28
|
+
* accepted here.
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseCreativeSse(stream: ReadableStream<Uint8Array>): Promise<CreativeAgentMessage[]>;
|
|
31
|
+
export declare function buildCreativeRequestForTest(input: CreativeInput, request: CreativeAgentRequest): {
|
|
32
|
+
input: CreativeInput;
|
|
33
|
+
request: CreativeAgentRequest;
|
|
34
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { DamClient } from "./dam-client.js";
|
|
3
|
+
import { DirectApiClient } from "./direct-client.js";
|
|
4
|
+
import { buildCreativeAgentRequest, normalizeCreativeMessages, } from "../creative/protocol.js";
|
|
5
|
+
import { validateCreativeInput } from "../creative/contract.js";
|
|
6
|
+
import { prepareCreativeMedia } from "../creative/media.js";
|
|
7
|
+
export const CREATIVE_COMPLETION_PATH = "/ai-agent/v1/thread/completion";
|
|
8
|
+
export class CreativeClient {
|
|
9
|
+
api;
|
|
10
|
+
dam;
|
|
11
|
+
idFactory;
|
|
12
|
+
constructor(deps = {}) {
|
|
13
|
+
this.api = deps.api ?? new DirectApiClient();
|
|
14
|
+
this.dam = deps.dam ?? new DamClient();
|
|
15
|
+
this.idFactory = deps.idFactory ?? randomUUID;
|
|
16
|
+
}
|
|
17
|
+
async run(value) {
|
|
18
|
+
const input = validateCreativeInput(value);
|
|
19
|
+
const uploader = (file, options) => this.dam.uploadLocalAsset(file, options);
|
|
20
|
+
const media = await prepareCreativeMedia(input.media ?? [], uploader);
|
|
21
|
+
const request = buildCreativeAgentRequest(input, media, {
|
|
22
|
+
local_thread_id: this.idFactory(),
|
|
23
|
+
local_message_id: this.idFactory(),
|
|
24
|
+
});
|
|
25
|
+
const response = await this.api.postStream(CREATIVE_COMPLETION_PATH, request, {
|
|
26
|
+
headers: { Accept: "text/event-stream" },
|
|
27
|
+
});
|
|
28
|
+
if (!response.body) {
|
|
29
|
+
throw new Error("Creative completion 没有返回流式响应");
|
|
30
|
+
}
|
|
31
|
+
const messages = await parseCreativeSse(response.body);
|
|
32
|
+
return normalizeCreativeMessages(messages, input.conversation_id ?? "");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Consume the Editor completion body without exposing transport events to the
|
|
37
|
+
* CLI caller. The Editor currently emits JSON arrays, sometimes as adjacent
|
|
38
|
+
* JSON values and sometimes wrapped in `data:` SSE records, so both forms are
|
|
39
|
+
* accepted here.
|
|
40
|
+
*/
|
|
41
|
+
export async function parseCreativeSse(stream) {
|
|
42
|
+
const reader = stream.getReader();
|
|
43
|
+
const decoder = new TextDecoder();
|
|
44
|
+
let raw = "";
|
|
45
|
+
try {
|
|
46
|
+
while (true) {
|
|
47
|
+
const result = await reader.read();
|
|
48
|
+
if (result.done)
|
|
49
|
+
break;
|
|
50
|
+
raw += decoder.decode(result.value, { stream: true });
|
|
51
|
+
}
|
|
52
|
+
raw += decoder.decode();
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
reader.releaseLock();
|
|
56
|
+
}
|
|
57
|
+
const payloads = extractSsePayloads(raw);
|
|
58
|
+
const messages = [];
|
|
59
|
+
for (const payload of payloads) {
|
|
60
|
+
for (const value of parseJsonSequence(payload)) {
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
for (const item of value) {
|
|
63
|
+
if (isMessage(item))
|
|
64
|
+
messages.push(item);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else if (isMessage(value)) {
|
|
68
|
+
messages.push(value);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return messages;
|
|
73
|
+
}
|
|
74
|
+
function extractSsePayloads(raw) {
|
|
75
|
+
const normalized = raw.replace(/\r\n?/g, "\n");
|
|
76
|
+
const lines = normalized.split("\n");
|
|
77
|
+
const payloads = [];
|
|
78
|
+
let dataLines = [];
|
|
79
|
+
let foundDataLine = false;
|
|
80
|
+
const flush = () => {
|
|
81
|
+
if (dataLines.length > 0) {
|
|
82
|
+
payloads.push(dataLines.join("\n"));
|
|
83
|
+
dataLines = [];
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
for (const line of lines) {
|
|
87
|
+
if (line.startsWith("data:")) {
|
|
88
|
+
foundDataLine = true;
|
|
89
|
+
dataLines.push(line.slice(5).replace(/^ /, ""));
|
|
90
|
+
}
|
|
91
|
+
else if (line === "") {
|
|
92
|
+
flush();
|
|
93
|
+
}
|
|
94
|
+
else if (line.startsWith(":") || line.startsWith("event:") || line.startsWith("id:")) {
|
|
95
|
+
// SSE metadata/heartbeats do not contain completion messages.
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
else if (!foundDataLine) {
|
|
99
|
+
// The current backend may send raw JSON arrays without an SSE prefix.
|
|
100
|
+
dataLines.push(line);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// A continuation line belongs to the current data record according to
|
|
104
|
+
// the SSE grammar.
|
|
105
|
+
dataLines.push(line);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
flush();
|
|
109
|
+
return payloads;
|
|
110
|
+
}
|
|
111
|
+
function parseJsonSequence(payload) {
|
|
112
|
+
const values = [];
|
|
113
|
+
let index = 0;
|
|
114
|
+
while (index < payload.length) {
|
|
115
|
+
while (index < payload.length && /[\s,]/.test(payload[index] ?? ""))
|
|
116
|
+
index += 1;
|
|
117
|
+
if (index >= payload.length)
|
|
118
|
+
break;
|
|
119
|
+
const remaining = payload.slice(index).trim();
|
|
120
|
+
if (remaining === "[DONE]")
|
|
121
|
+
break;
|
|
122
|
+
const start = findJsonStart(payload, index);
|
|
123
|
+
if (start < 0)
|
|
124
|
+
break;
|
|
125
|
+
const end = findJsonEnd(payload, start);
|
|
126
|
+
if (end < 0)
|
|
127
|
+
break;
|
|
128
|
+
const text = payload.slice(start, end + 1);
|
|
129
|
+
try {
|
|
130
|
+
values.push(JSON.parse(text));
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// A malformed event is ignored here; valid later events can still be
|
|
134
|
+
// delivered and the final normalizer will report an empty completion.
|
|
135
|
+
}
|
|
136
|
+
index = end + 1;
|
|
137
|
+
}
|
|
138
|
+
return values;
|
|
139
|
+
}
|
|
140
|
+
function findJsonStart(value, from) {
|
|
141
|
+
for (let index = from; index < value.length; index += 1) {
|
|
142
|
+
if (value[index] === "{" || value[index] === "[")
|
|
143
|
+
return index;
|
|
144
|
+
}
|
|
145
|
+
return -1;
|
|
146
|
+
}
|
|
147
|
+
function findJsonEnd(value, start) {
|
|
148
|
+
const stack = [];
|
|
149
|
+
let inString = false;
|
|
150
|
+
let escaped = false;
|
|
151
|
+
for (let index = start; index < value.length; index += 1) {
|
|
152
|
+
const char = value[index];
|
|
153
|
+
if (inString) {
|
|
154
|
+
if (escaped)
|
|
155
|
+
escaped = false;
|
|
156
|
+
else if (char === "\\")
|
|
157
|
+
escaped = true;
|
|
158
|
+
else if (char === '"')
|
|
159
|
+
inString = false;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (char === '"') {
|
|
163
|
+
inString = true;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (char === "{" || char === "[")
|
|
167
|
+
stack.push(char);
|
|
168
|
+
else if (char === "}" || char === "]") {
|
|
169
|
+
const opening = stack.pop();
|
|
170
|
+
if ((char === "}" && opening !== "{") || (char === "]" && opening !== "[")) {
|
|
171
|
+
return -1;
|
|
172
|
+
}
|
|
173
|
+
if (stack.length === 0)
|
|
174
|
+
return index;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return -1;
|
|
178
|
+
}
|
|
179
|
+
function isMessage(value) {
|
|
180
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
181
|
+
}
|
|
182
|
+
export function buildCreativeRequestForTest(input, request) {
|
|
183
|
+
return { input, request };
|
|
184
|
+
}
|
|
@@ -42,7 +42,6 @@ export interface DamSearchOptions {
|
|
|
42
42
|
pageSize?: number;
|
|
43
43
|
page?: number;
|
|
44
44
|
cursor?: string;
|
|
45
|
-
raw?: boolean;
|
|
46
45
|
allowEmptyKeyword?: boolean;
|
|
47
46
|
}
|
|
48
47
|
export interface DamSearchAsset {
|
|
@@ -74,12 +73,6 @@ export interface DamSearchResult {
|
|
|
74
73
|
finish: boolean;
|
|
75
74
|
next_cursor?: string;
|
|
76
75
|
assets: DamSearchAsset[];
|
|
77
|
-
debug?: {
|
|
78
|
-
source: {
|
|
79
|
-
endpoint: string;
|
|
80
|
-
raw?: unknown;
|
|
81
|
-
};
|
|
82
|
-
};
|
|
83
76
|
}
|
|
84
77
|
interface DamCover {
|
|
85
78
|
url?: string;
|
|
@@ -4,7 +4,6 @@ import { basename, extname, resolve } from "node:path";
|
|
|
4
4
|
import OSS from "ali-oss";
|
|
5
5
|
import sharp from "sharp";
|
|
6
6
|
import { DirectApiClient, DirectApiError, unwrapEnvelope } from "./direct-client.js";
|
|
7
|
-
import { isDebug, isVerbose } from "../utils/config.js";
|
|
8
7
|
import { assertSafeUploadPath } from "../utils/sensitive-path.js";
|
|
9
8
|
import { assertSafeHttpUrl } from "../utils/url-safety.js";
|
|
10
9
|
import { redactSensitiveText } from "../utils/redact.js";
|
|
@@ -140,12 +139,9 @@ export class DamClient {
|
|
|
140
139
|
try {
|
|
141
140
|
const response = await this.client.post("/dam/asset/v2/search/simple", payload);
|
|
142
141
|
return normalizeSearchResponse(response, {
|
|
143
|
-
endpoint: "/dam/asset/v2/search/simple",
|
|
144
142
|
query,
|
|
145
143
|
repositoryIds,
|
|
146
144
|
payload,
|
|
147
|
-
includeDebug: options.raw === true || isVerbose() || isDebug(),
|
|
148
|
-
includeRaw: options.raw === true,
|
|
149
145
|
});
|
|
150
146
|
}
|
|
151
147
|
catch (error) {
|
|
@@ -153,12 +149,9 @@ export class DamClient {
|
|
|
153
149
|
throw error;
|
|
154
150
|
const response = await this.client.post("/dam/asset/search", payload);
|
|
155
151
|
return normalizeSearchResponse(response, {
|
|
156
|
-
endpoint: "/dam/asset/search",
|
|
157
152
|
query,
|
|
158
153
|
repositoryIds,
|
|
159
154
|
payload,
|
|
160
|
-
includeDebug: options.raw === true || isVerbose() || isDebug(),
|
|
161
|
-
includeRaw: options.raw === true,
|
|
162
155
|
});
|
|
163
156
|
}
|
|
164
157
|
}
|
|
@@ -307,16 +300,6 @@ function normalizeSearchResponse(response, context) {
|
|
|
307
300
|
finish,
|
|
308
301
|
next_cursor: nextCursor,
|
|
309
302
|
assets: rawItems.map(normalizeSearchAsset).filter((item) => item.asset_id),
|
|
310
|
-
...(context.includeDebug
|
|
311
|
-
? {
|
|
312
|
-
debug: {
|
|
313
|
-
source: {
|
|
314
|
-
endpoint: context.endpoint,
|
|
315
|
-
...(context.includeRaw ? { raw: response } : {}),
|
|
316
|
-
},
|
|
317
|
-
},
|
|
318
|
-
}
|
|
319
|
-
: {}),
|
|
320
303
|
};
|
|
321
304
|
}
|
|
322
305
|
function extractSearchContainer(value) {
|