gaoding-cli 1.0.0-alpha.13 → 1.0.0-alpha.15

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 (41) hide show
  1. package/README.md +2 -2
  2. package/contracts/operations/agent.send/input.schema.json +4 -0
  3. package/contracts/operations/model.get/output.schema.json +1 -0
  4. package/dist/bin/gd-cli.js +37 -16
  5. package/dist/src/bootstrap/create-cli.js +43 -4
  6. package/dist/src/bootstrap/create-runtime.js +44 -13
  7. package/dist/src/cli/action-binding.js +43 -9
  8. package/dist/src/cli/agent-commands.js +13 -3
  9. package/dist/src/cli/auth-commands.js +21 -13
  10. package/dist/src/cli/dam-commands.js +35 -20
  11. package/dist/src/cli/errors.js +34 -1
  12. package/dist/src/cli/model-commands.js +25 -11
  13. package/dist/src/cli/org-commands.js +18 -12
  14. package/dist/src/cli/presenter.js +11 -1
  15. package/dist/src/cli/tool-commands.js +19 -7
  16. package/dist/src/cli/update-command.js +6 -4
  17. package/dist/src/features/agent/creative-agent-adapter.js +19 -14
  18. package/dist/src/features/agent/creative-protocol.js +7 -3
  19. package/dist/src/features/agent/use-cases.js +63 -54
  20. package/dist/src/features/auth/use-cases.js +82 -65
  21. package/dist/src/features/dam/asset-projection.js +22 -14
  22. package/dist/src/features/dam/dam-api-adapter.js +19 -4
  23. package/dist/src/features/dam/object-storage.js +2 -0
  24. package/dist/src/features/dam/registered-uploader.js +58 -35
  25. package/dist/src/features/dam/use-cases.js +39 -32
  26. package/dist/src/features/org/use-cases.js +27 -11
  27. package/dist/src/features/tool/catalog.js +7 -3
  28. package/dist/src/features/tool/dynamic-schema.js +19 -9
  29. package/dist/src/features/tool/mns-catalog-adapter.js +7 -4
  30. package/dist/src/features/tool/tool-api-adapter.js +186 -73
  31. package/dist/src/features/tool/use-cases.js +63 -34
  32. package/dist/src/features/update/update-service.js +28 -22
  33. package/dist/src/platform/json-input.js +3 -0
  34. package/dist/src/platform/remote-error-evidence.js +66 -0
  35. package/dist/src/platform/remote-protocol.js +10 -0
  36. package/dist/src/platform/signed-http-transport.js +31 -5
  37. package/dist/src/telemetry/invocation.js +156 -0
  38. package/dist/src/telemetry/sls-sink.js +31 -0
  39. package/package.json +1 -1
  40. package/skills/gd-cli/references/creation.md +15 -4
  41. package/skills/gd-cli/references/errors.md +3 -1
@@ -0,0 +1,66 @@
1
+ import { redact, redactSensitiveText } from "./redact.js";
2
+ const maximumLength = 8_192;
3
+ const truncationMarker = "[TRUNCATED]";
4
+ export function remoteErrorEvidence(value) {
5
+ const normalized = parseJson(value);
6
+ const remoteCode = topLevelCode(normalized);
7
+ const sanitized = sanitize(normalized);
8
+ if (sanitized === undefined) {
9
+ return remoteCode === undefined ? {} : { remoteCode };
10
+ }
11
+ const remoteBody = sanitized.length <= maximumLength
12
+ ? sanitized
13
+ : `${sanitized.slice(0, maximumLength - truncationMarker.length)}${truncationMarker}`;
14
+ return {
15
+ ...(remoteCode === undefined ? {} : { remoteCode }),
16
+ remoteBody
17
+ };
18
+ }
19
+ function parseJson(value) {
20
+ if (typeof value !== "string")
21
+ return value;
22
+ try {
23
+ return JSON.parse(value);
24
+ }
25
+ catch {
26
+ return value;
27
+ }
28
+ }
29
+ function topLevelCode(value) {
30
+ if (value === null || typeof value !== "object" || Array.isArray(value))
31
+ return undefined;
32
+ if (!Object.prototype.hasOwnProperty.call(value, "code"))
33
+ return undefined;
34
+ const code = value.code;
35
+ if (typeof code === "string")
36
+ return redactRemoteText(code);
37
+ return typeof code === "number" ? String(code) : undefined;
38
+ }
39
+ function sanitize(value) {
40
+ if (typeof value === "string")
41
+ return redactRemoteText(value);
42
+ try {
43
+ return JSON.stringify(redactCookieAssignments(redact(value)));
44
+ }
45
+ catch {
46
+ return undefined;
47
+ }
48
+ }
49
+ function redactRemoteText(value) {
50
+ return redactCookieAssignment(redactSensitiveText(value));
51
+ }
52
+ function redactCookieAssignments(value) {
53
+ if (typeof value === "string")
54
+ return redactCookieAssignment(value);
55
+ if (value === null || typeof value !== "object")
56
+ return value;
57
+ if (Array.isArray(value))
58
+ return value.map(redactCookieAssignments);
59
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
60
+ key,
61
+ redactCookieAssignments(item)
62
+ ]));
63
+ }
64
+ function redactCookieAssignment(value) {
65
+ return value.replace(/\b(cookie)\s*=\s*[^\s,;}&]+/giu, "$1=[REDACTED]");
66
+ }
@@ -0,0 +1,10 @@
1
+ export class RemoteProtocolError extends Error {
2
+ remoteCode;
3
+ remoteBody;
4
+ constructor(evidence = {}) {
5
+ super("稿定服务响应与当前 CLI 不兼容。");
6
+ this.name = "RemoteProtocolError";
7
+ this.remoteCode = evidence.remoteCode;
8
+ this.remoteBody = evidence.remoteBody;
9
+ }
10
+ }
@@ -1,12 +1,18 @@
1
+ import { remoteErrorEvidence } from "./remote-error-evidence.js";
2
+ import { RemoteProtocolError } from "./remote-protocol.js";
1
3
  import { canonicalizeQuery, signRequest } from "./signature.js";
2
4
  export class RemoteRequestError extends Error {
3
5
  status;
4
6
  transient;
5
- constructor(status, transient = false) {
7
+ remoteCode;
8
+ remoteBody;
9
+ constructor(status, transient = false, evidence = {}) {
6
10
  super(status === undefined ? "无法连接稿定服务。" : `稿定服务请求失败(HTTP ${status})。`);
7
11
  this.name = "RemoteRequestError";
8
12
  this.status = status;
9
13
  this.transient = transient;
14
+ this.remoteCode = evidence.remoteCode;
15
+ this.remoteBody = evidence.remoteBody;
10
16
  }
11
17
  }
12
18
  const TRANSIENT_NETWORK_CODES = new Set([
@@ -56,6 +62,8 @@ export function createSignedHttpTransport(options) {
56
62
  }),
57
63
  "X-Timestamp": String(timestamp)
58
64
  };
65
+ if (options.traceparent !== undefined)
66
+ headers.traceparent = options.traceparent();
59
67
  if (options.channelId !== undefined)
60
68
  headers["X-Channel-Id"] = options.channelId;
61
69
  if (body !== undefined)
@@ -83,14 +91,22 @@ export function createSignedHttpTransport(options) {
83
91
  }
84
92
  }
85
93
  async function json(response) {
86
- if (!response.ok)
87
- throw new RemoteRequestError(response.status);
94
+ let body;
88
95
  try {
89
- return await response.json();
96
+ body = await response.text();
90
97
  }
91
98
  catch {
92
99
  throw new RemoteRequestError(response.status);
93
100
  }
101
+ const evidence = remoteErrorEvidence(body);
102
+ if (!response.ok)
103
+ throw new RemoteRequestError(response.status, false, evidence);
104
+ try {
105
+ return JSON.parse(body);
106
+ }
107
+ catch {
108
+ throw new RemoteProtocolError(evidence);
109
+ }
94
110
  }
95
111
  return {
96
112
  async getJson(request) {
@@ -101,7 +117,17 @@ export function createSignedHttpTransport(options) {
101
117
  },
102
118
  async postStream(request) {
103
119
  const response = await send("POST", request, request.accept, JSON.stringify(request.body), false);
104
- if (!response.ok || !response.body)
120
+ if (!response.ok) {
121
+ let body;
122
+ try {
123
+ body = await response.text();
124
+ }
125
+ catch {
126
+ throw new RemoteRequestError(response.status);
127
+ }
128
+ throw new RemoteRequestError(response.status, false, remoteErrorEvidence(body));
129
+ }
130
+ if (!response.body)
105
131
  throw new RemoteRequestError(response.status);
106
132
  return response.body;
107
133
  }
@@ -0,0 +1,156 @@
1
+ const detailedStages = {
2
+ "auth.login": new Set(["authorize", "wait", "identity", "bind"]),
3
+ "org.switch": new Set(["list", "bind"]),
4
+ "agent.send:execute": new Set(["upload", "execute", "result"]),
5
+ "tool.call:execute": new Set(["catalog", "upload", "intent", "submit", "wait"]),
6
+ "dam.upload": new Set(["inspect", "repository", "prepare", "upload", "persist", "wait"]),
7
+ update: new Set(["registry", "install", "sync", "verify"])
8
+ };
9
+ export function createTelemetryInvocation(options) {
10
+ const traceId = options.traceIdFactory();
11
+ const startedAt = options.now();
12
+ let definition;
13
+ const context = {};
14
+ const stages = [];
15
+ return {
16
+ traceId,
17
+ get selected() {
18
+ return definition !== undefined;
19
+ },
20
+ traceparent() {
21
+ return `00-${traceId}-${options.spanIdFactory()}-${traceFlags(definition)}`;
22
+ },
23
+ select(nextDefinition) {
24
+ definition = nextDefinition;
25
+ },
26
+ annotate(annotation) {
27
+ if (annotation.account_id !== undefined)
28
+ context.account_id = annotation.account_id;
29
+ if (annotation.organization_id !== undefined) {
30
+ context.organization_id = annotation.organization_id;
31
+ }
32
+ if (annotation.tool_name !== undefined)
33
+ context.tool_name = annotation.tool_name;
34
+ if (annotation.model_id !== undefined)
35
+ context.model_id = annotation.model_id;
36
+ if (annotation.content_id !== undefined)
37
+ context.content_id = annotation.content_id;
38
+ if (annotation.task_id !== undefined)
39
+ context.task_id = annotation.task_id;
40
+ if (annotation.dify_task_id !== undefined) {
41
+ context.dify_task_id = annotation.dify_task_id;
42
+ }
43
+ if (annotation.parameters !== undefined) {
44
+ context.parameters = { ...annotation.parameters };
45
+ }
46
+ },
47
+ async stage(name, run) {
48
+ const stageStartedAt = options.now();
49
+ try {
50
+ const result = await run();
51
+ stages.push({
52
+ name,
53
+ durationMs: elapsed(options.now(), stageStartedAt),
54
+ outcome: "success"
55
+ });
56
+ return result;
57
+ }
58
+ catch (error) {
59
+ const classification = options.classifyError(error);
60
+ stages.push({
61
+ name,
62
+ durationMs: elapsed(options.now(), stageStartedAt),
63
+ outcome: classification.outcome,
64
+ error,
65
+ ...(classification.errorCode === undefined
66
+ ? {}
67
+ : { errorCode: classification.errorCode }),
68
+ ...(classification.httpStatus === undefined
69
+ ? {}
70
+ : { httpStatus: classification.httpStatus })
71
+ });
72
+ throw error;
73
+ }
74
+ },
75
+ finish(completion) {
76
+ if (!definition)
77
+ return [];
78
+ const selectedDefinition = definition;
79
+ const mode = selectedDefinition.mode?.();
80
+ const allowedStages = detailedStages[stageKey(selectedDefinition.operation, mode)];
81
+ const stageEvents = allowedStages
82
+ ? stages
83
+ .filter((stage) => allowedStages.has(stage.name))
84
+ .map((stage) => ({
85
+ schema_version: "2",
86
+ event_name: "stage_finished",
87
+ trace_id: traceId,
88
+ operation: selectedDefinition.operation,
89
+ stage: stage.name,
90
+ outcome: stage.outcome,
91
+ duration_ms: stage.durationMs,
92
+ ...(stage.errorCode === undefined ? {} : { error_code: stage.errorCode }),
93
+ ...(stage.httpStatus === undefined ? {} : { http_status: stage.httpStatus })
94
+ }))
95
+ : [];
96
+ const command = {
97
+ schema_version: "2",
98
+ event_name: "command_finished",
99
+ trace_id: traceId,
100
+ operation: selectedDefinition.operation,
101
+ ...(mode === undefined ? {} : { mode }),
102
+ outcome: completion.outcome,
103
+ duration_ms: elapsed(options.now(), startedAt),
104
+ cli_version: options.cliVersion,
105
+ os: options.runtime.os,
106
+ arch: options.runtime.arch,
107
+ node_major: options.runtime.nodeMajor,
108
+ ...(context.account_id === undefined ? {} : { account_id: context.account_id }),
109
+ ...(context.organization_id === undefined
110
+ ? {}
111
+ : { organization_id: context.organization_id }),
112
+ ...(context.tool_name === undefined ? {} : { tool_name: context.tool_name }),
113
+ ...(context.model_id === undefined ? {} : { model_id: context.model_id }),
114
+ ...(context.content_id === undefined ? {} : { content_id: context.content_id }),
115
+ ...(context.task_id === undefined ? {} : { task_id: context.task_id }),
116
+ ...(context.dify_task_id === undefined
117
+ ? {}
118
+ : { dify_task_id: context.dify_task_id }),
119
+ ...(context.parameters === undefined
120
+ ? {}
121
+ : { parameters: { ...context.parameters } }),
122
+ ...(completion.outcome === "failure"
123
+ ? {
124
+ error_code: completion.errorCode,
125
+ error_message: completion.errorMessage,
126
+ failure_stage: stages.find((stage) => stage.error === completion.error)?.name
127
+ ?? "unknown",
128
+ ...(completion.httpStatus === undefined
129
+ ? {}
130
+ : { http_status: completion.httpStatus }),
131
+ ...(completion.remoteErrorCode === undefined
132
+ ? {}
133
+ : { remote_error_code: completion.remoteErrorCode }),
134
+ ...(completion.remoteErrorBody === undefined
135
+ ? {}
136
+ : { remote_error_body: completion.remoteErrorBody })
137
+ }
138
+ : {})
139
+ };
140
+ return [...stageEvents, command];
141
+ }
142
+ };
143
+ }
144
+ function traceFlags(definition) {
145
+ if (definition === undefined)
146
+ return "00";
147
+ const paidExecution = (definition.operation === "agent.send"
148
+ || definition.operation === "tool.call") && definition.mode?.() === "execute";
149
+ return paidExecution ? "01" : "00";
150
+ }
151
+ function stageKey(operation, mode) {
152
+ return mode === undefined ? operation : `${operation}:${mode}`;
153
+ }
154
+ function elapsed(now, startedAt) {
155
+ return Math.max(0, now - startedAt);
156
+ }
@@ -0,0 +1,31 @@
1
+ const trackingUrl = new URL("https://gaoding-log-ai-gpu.cn-hangzhou.log.aliyuncs.com/logstores/gd-cli/track?APIVersion=0.6.0");
2
+ const TIMEOUT_MS = 2_000;
3
+ export function createSlsTelemetrySink(options) {
4
+ return {
5
+ async send(events) {
6
+ if (events.length === 0)
7
+ return;
8
+ try {
9
+ await options.fetch(trackingUrl, {
10
+ method: "POST",
11
+ headers: { "Content-Type": "application/json" },
12
+ body: JSON.stringify({ __logs__: events.map(wireEvent) }),
13
+ redirect: "error",
14
+ signal: AbortSignal.timeout(TIMEOUT_MS)
15
+ });
16
+ }
17
+ catch {
18
+ // Telemetry must never change command behavior.
19
+ }
20
+ }
21
+ };
22
+ }
23
+ function wireEvent(event) {
24
+ const wire = {};
25
+ for (const [key, value] of Object.entries(event)) {
26
+ if (value === undefined)
27
+ continue;
28
+ wire[key] = key === "parameters" ? JSON.stringify(value) : String(value);
29
+ }
30
+ return wire;
31
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gaoding-cli",
3
- "version": "1.0.0-alpha.13",
3
+ "version": "1.0.0-alpha.15",
4
4
  "description": "Gaoding command-line interface for agents and people.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -3,7 +3,7 @@
3
3
  ## 选择入口
4
4
 
5
5
  - 目标开放、需要稿定 Agent 完成创作时,使用 `gd-cli agent send`。
6
- - 已知要调用具体图片、视频或文本能力时,依次发现 Tool、选择 Model、读取输入 Schema,再调用 Tool。
6
+ - 已知要调用具体图片、视频或文本能力时,依次发现 Tool、选择 Model、读取 Model detail,再调用 Tool。
7
7
  - 不猜 Tool 名、Model 名或参数;以当前命令输出为准。
8
8
 
9
9
  ## Agent 创作
@@ -25,14 +25,25 @@ gd-cli agent send --input request.json
25
25
  gd-cli tool list
26
26
  gd-cli model list --tool <tool>
27
27
  gd-cli model get <model>
28
- gd-cli tool call <tool> --schema
29
28
  gd-cli tool call <tool> --input request.json
30
29
  ```
31
30
 
32
- `tool list` 给出可用 Tool;`model list` 和 `model get` 给出当前可用 Model、费用与预估耗时等信息;`tool call --schema` 给出所选 Tool 的实时输入要求。不要把列表值固化在提示或脚本中。
31
+ `tool list` 给出可用 Tool;`model list` 和 `model get` 给出当前可用 Model、说明、费用、预估耗时和结构化参数。`model get` `parameters` 是所选 Model 的实际调用元数据:遵循 `required`、`type`、`description`,SELECT 参数使用 `options[].value`,required 参数带 `default` 时在用户未指定时显式传入默认值。`usageDescription` 描述跨字段规则,例如视频 mode 对首尾帧或素材字段的要求;这类组合不能只从单个字段推断。不要把列表值固化在提示或脚本中。
32
+
33
+ 按所选 Model detail 构造 JSON:提供全部 required 字段;SELECT 参数使用 `options[].value`;required 字段带 default 且用户未指定时,在输入中显式使用该 default。存在 width、height、resolution 等专用字段时,将用户要求写入对应结构化参数,不要只写在 prompt 中。`tool call <tool> --schema` 返回整个 Tool 下所有 Model 的参数并集,只用于 Tool 级发现;选定 Model 后以 `model get` 的参数和 `usageDescription` 为准。
34
+
35
+ 务必将 `model get <model>` 返回的 `model` 原样放在请求 JSON 的顶层 `model` 字段(即运行时的 `arguments.model`),与所选 Model 的参数字段并列;不要只复制 `parameters`。例如:
36
+
37
+ ```json
38
+ {
39
+ "model": "<model>",
40
+ "prompt": "<required prompt>",
41
+ "<parameter>": "<value>"
42
+ }
43
+ ```
33
44
 
34
45
  成功结果包含 `content` 和 `usage`。`content` 是文本或资源链接;`usage` 包含所选模型及模型目录公布的稿豆价格区间,不表示实际扣费。
35
46
 
36
47
  ## 本地媒体
37
48
 
38
- 按所选 Schema 在输入中提交本地媒体引用。CLI 会在内部通过 DAM 完成临时上传并把公网 URL 传给创作服务;不要自行伪造 URL,也不要读取用户未明确授权的路径。
49
+ 按所选 Model detail 在输入中提交本地媒体引用。CLI 会在内部通过 DAM 完成临时上传并把公网 URL 传给创作服务;不要自行伪造 URL,也不要读取用户未明确授权的路径。
@@ -3,8 +3,10 @@
3
3
  - `stdout` 只承载命令结果;结构化命令的 JSON 可直接解析。
4
4
  - `stderr` 承载警告、授权提示和错误;不要把警告混入结果。
5
5
  - 退出码 `0` 表示成功。
6
- - 退出码 `1` 表示运行、服务或状态失败;读取错误码与“下一步”,处理后再重试。
6
+ - 退出码 `1` 表示运行、服务或状态失败;只执行错误中 `error.details.next_steps` 明确给出的恢复步骤,缺失时停止并报告。
7
7
  - 退出码 `2` 表示命令或参数错误;执行对应命令的 `--help` 后修正调用。
8
8
  - 退出码 `130` 表示用户中断;立即停止,不自动重试。
9
9
 
10
+ Agent 或 Tool 等可能消耗稿豆的请求如果可能已经提交、但完成状态未知,停止并报告,不自动重试原调用。
11
+
10
12
  不要从未知错误中推断内部实现,也不要输出本地凭证、请求签名或带敏感查询参数的 URL。命令失败且没有安全恢复步骤时,保留原始 stderr 的公开错误码并向用户说明。