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

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 (39) hide show
  1. package/README.md +3 -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 +3 -1
  5. package/dist/src/bootstrap/create-cli.js +36 -3
  6. package/dist/src/bootstrap/create-runtime.js +32 -7
  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 +19 -1
  12. package/dist/src/cli/model-commands.js +41 -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 +35 -33
  31. package/dist/src/features/tool/use-cases.js +68 -36
  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-protocol.js +6 -0
  35. package/dist/src/telemetry/invocation.js +128 -0
  36. package/dist/src/telemetry/sls-sink.js +31 -0
  37. package/package.json +1 -1
  38. package/skills/gd-cli/references/creation.md +4 -2
  39. package/skills/gd-cli/references/errors.md +3 -1
package/README.md CHANGED
@@ -28,10 +28,11 @@ gd-cli org switch --org <org-id>
28
28
  gd-cli agent send --schema
29
29
  gd-cli tool list
30
30
  gd-cli model list --tool <tool>
31
- gd-cli tool call <tool> --schema
31
+ gd-cli model get <model>
32
+ gd-cli model get <model> --schema
32
33
  ```
33
34
 
34
- 运行 `gd-cli --help` 查看当前命令;运行任一命令的 `--help` 查看参数。`gd-cli update` 固定检查 npm `latest`,支持更新 npm、pnpm 全局安装并同步 Agent Skill。
35
+ 选定 Model 后,`model get --schema` 给出该 Model 的实际运行时输入;`tool call --schema` 给出整个 Tool 的参数并集。运行 `gd-cli --help` 查看当前命令;运行任一命令的 `--help` 查看参数。`gd-cli update` 固定检查 npm `latest`,支持更新 npm、pnpm 全局安装并同步 Agent Skill。
35
36
 
36
37
  ## License
37
38
 
@@ -123,6 +123,10 @@
123
123
  "properties": {
124
124
  "contextId": {
125
125
  "type": "string"
126
+ },
127
+ "parts": {
128
+ "type": "array",
129
+ "maxItems": 1
126
130
  }
127
131
  },
128
132
  "required": [
@@ -127,6 +127,7 @@
127
127
  "type": "string",
128
128
  "enum": [
129
129
  "string",
130
+ "number",
130
131
  "uri",
131
132
  "uri[]"
132
133
  ],
@@ -14,7 +14,9 @@ try {
14
14
  program,
15
15
  argv: process.argv.slice(2),
16
16
  presenter: runtime.presenter,
17
- signal: controller.signal
17
+ signal: controller.signal,
18
+ telemetry: runtime.telemetry,
19
+ telemetrySink: runtime.telemetrySink
18
20
  });
19
21
  }
20
22
  catch (error) {
@@ -1,4 +1,4 @@
1
- import { Command } from "commander";
1
+ import { Command, CommanderError } from "commander";
2
2
  import { assertAllLeafCommandsBound, configureActionEnvironment } from "../cli/action-binding.js";
3
3
  import { registerAgentCommands } from "../cli/agent-commands.js";
4
4
  import { registerAuthCommands } from "../cli/auth-commands.js";
@@ -10,6 +10,7 @@ import { registerEditorCommands } from "../cli/editor-commands.js";
10
10
  import { registerDamCommands } from "../cli/dam-commands.js";
11
11
  import { registerUpdateCommand } from "../cli/update-command.js";
12
12
  import { redact } from "../platform/redact.js";
13
+ import { RemoteRequestError } from "../platform/signed-http-transport.js";
13
14
  export { assertAllLeafCommandsBound, bindAction } from "../cli/action-binding.js";
14
15
  export function createCli(runtime, options) {
15
16
  const program = new Command()
@@ -27,7 +28,8 @@ export function createCli(runtime, options) {
27
28
  });
28
29
  configureActionEnvironment(program, {
29
30
  policies: runtime.accessPolicies,
30
- signal: options.signal
31
+ signal: options.signal,
32
+ telemetry: runtime.telemetry
31
33
  });
32
34
  registerAuthCommands(program, runtime);
33
35
  registerOrgCommands(program, runtime);
@@ -43,13 +45,44 @@ export function createCli(runtime, options) {
43
45
  export async function executeCli(options) {
44
46
  try {
45
47
  await options.program.parseAsync(options.argv, { from: "user" });
48
+ await sendFinished(options, { outcome: "success" });
46
49
  return 0;
47
50
  }
48
51
  catch (error) {
49
52
  if (isHelpOutcome(error))
50
53
  return 0;
54
+ if (options.telemetry.selected && error instanceof CommanderError) {
55
+ try {
56
+ await options.telemetry.stage("input", () => { throw error; });
57
+ }
58
+ catch {
59
+ // The original Commander error is mapped and presented below.
60
+ }
61
+ }
51
62
  const outcome = redact(mapCliError(error, { aborted: options.signal.aborted }));
52
- options.presenter.error(outcome, { json: options.argv.includes("--json") });
63
+ const invocationId = options.telemetry.selected
64
+ ? options.telemetry.invocationId
65
+ : undefined;
66
+ options.presenter.error(outcome, {
67
+ json: options.argv.includes("--json"),
68
+ ...(invocationId ? { invocationId } : {})
69
+ });
70
+ await sendFinished(options, outcome.kind === "interrupted"
71
+ ? { outcome: "interrupted", error }
72
+ : {
73
+ outcome: "failure",
74
+ error,
75
+ errorCode: outcome.failure.code,
76
+ errorMessage: outcome.failure.message,
77
+ ...(error instanceof RemoteRequestError && error.status !== undefined
78
+ ? { httpStatus: error.status }
79
+ : {})
80
+ });
53
81
  return outcome.exitCode;
54
82
  }
55
83
  }
84
+ async function sendFinished(options, completion) {
85
+ const events = options.telemetry.finish(completion);
86
+ if (events.length > 0)
87
+ await options.telemetrySink.send(events);
88
+ }
@@ -30,6 +30,9 @@ import { createPresenter } from "../cli/presenter.js";
30
30
  import { createContractValidators } from "./validators.js";
31
31
  import { loadBundledSkills } from "../features/skill/bundled-skills.js";
32
32
  import { createUpdateService } from "../features/update/update-service.js";
33
+ import { classifyTelemetryError } from "../cli/errors.js";
34
+ import { createTelemetryInvocation } from "../telemetry/invocation.js";
35
+ import { createSlsTelemetrySink } from "../telemetry/sls-sink.js";
33
36
  const productionEndpoints = {
34
37
  orgApi: new URL("https://gdcli.gaoding.com/api"),
35
38
  agentApi: new URL("https://gdcli.gaoding.com/api"),
@@ -47,6 +50,18 @@ export function createProductionRuntime(options = {}) {
47
50
  const endpoints = options.endpoints ?? productionEndpoints;
48
51
  const fetch = options.fetch ?? globalThis.fetch;
49
52
  const now = options.now ?? (() => new Date());
53
+ const telemetry = createTelemetryInvocation({
54
+ cliVersion: primarySkill.version,
55
+ now: () => now().getTime(),
56
+ idFactory: randomUUID,
57
+ runtime: {
58
+ os: process.platform,
59
+ arch: process.arch,
60
+ nodeMajor: Number.parseInt(process.versions.node, 10)
61
+ },
62
+ classifyError: classifyTelemetryError
63
+ });
64
+ const telemetrySink = createSlsTelemetrySink({ fetch });
50
65
  const rawIo = options.io ?? {
51
66
  input: process.stdin,
52
67
  stdout: process.stdout,
@@ -97,7 +112,8 @@ export function createProductionRuntime(options = {}) {
97
112
  const org = createOrgUseCases({
98
113
  store,
99
114
  org: createOrgService({ transport: orgTransport }),
100
- sso
115
+ sso,
116
+ telemetry
101
117
  });
102
118
  const auth = createAuthUseCases({
103
119
  store,
@@ -105,6 +121,7 @@ export function createProductionRuntime(options = {}) {
105
121
  organizationBinder: org,
106
122
  authPageOrigin: endpoints.authPageOrigin,
107
123
  now,
124
+ telemetry,
108
125
  sleep: async (milliseconds, signal) => {
109
126
  await delay(milliseconds, undefined, { signal });
110
127
  }
@@ -129,7 +146,8 @@ export function createProductionRuntime(options = {}) {
129
146
  api: damApi,
130
147
  storage: storageUpload,
131
148
  warn: (message) => { presenter.warning(message); },
132
- now
149
+ now,
150
+ telemetry
133
151
  }),
134
152
  validate: {
135
153
  list: validators.damListOutput,
@@ -137,20 +155,24 @@ export function createProductionRuntime(options = {}) {
137
155
  get: validators.damGetOutput,
138
156
  upload: validators.damUploadOutput,
139
157
  delete: validators.damDeleteOutput
140
- }
158
+ },
159
+ telemetry
141
160
  });
142
161
  const agent = createAgentUseCases({
143
162
  uploader: transientUploader,
144
163
  completion: createCreativeAgentAdapter({
145
164
  transport: agentTransport,
146
- idFactory: randomUUID
147
- })
165
+ idFactory: randomUUID,
166
+ telemetry
167
+ }),
168
+ telemetry
148
169
  });
149
170
  const tool = createToolUseCases({
150
171
  catalog: createMnsCatalogAdapter({ transport: mnsTransport }),
151
- execution: createToolApiAdapter({ transport: toolTransport }),
172
+ execution: createToolApiAdapter({ transport: toolTransport, telemetry }),
152
173
  uploader: transientUploader,
153
- warn: (message) => { presenter.warning(message); }
174
+ warn: (message) => { presenter.warning(message); },
175
+ telemetry
154
176
  });
155
177
  const editor = createEditorUseCases({
156
178
  session: createEditorSession({
@@ -183,6 +205,7 @@ export function createProductionRuntime(options = {}) {
183
205
  packageRoot,
184
206
  bundledSkills,
185
207
  fetch,
208
+ telemetry,
186
209
  ...(process.argv[1] ? { binaryPath: process.argv[1] } : {}),
187
210
  ...(options.homeDirectory ? { homeDirectory: options.homeDirectory } : {})
188
211
  }).run(signal);
@@ -190,6 +213,8 @@ export function createProductionRuntime(options = {}) {
190
213
  };
191
214
  return {
192
215
  version: primarySkill.version,
216
+ telemetry,
217
+ telemetrySink,
193
218
  accessPolicies: createAccessPolicies({
194
219
  store,
195
220
  now,
@@ -3,34 +3,46 @@ const boundPolicies = new WeakMap();
3
3
  export function configureActionEnvironment(program, environment) {
4
4
  environments.set(program, environment);
5
5
  }
6
- export function bindAction(command, policy, handler) {
6
+ export function bindAction(command, policy, handler, telemetry) {
7
7
  if (boundPolicies.has(command))
8
8
  throw new Error(`Action already bound: ${commandPath(command)}`);
9
9
  boundPolicies.set(command, policy);
10
+ bindTelemetrySelection(command, telemetry);
10
11
  command.action(async () => {
11
12
  const environment = environments.get(rootCommand(command));
12
13
  if (!environment)
13
14
  throw new Error("CLI action environment is missing.");
14
- const context = await environment.policies[policy].authorize(environment.signal);
15
- if (context.policy !== policy)
16
- throw new Error("Access policy returned the wrong context.");
15
+ const context = await environment.telemetry.stage("access", async () => {
16
+ const authorized = await environment.policies[policy].authorize(environment.signal);
17
+ if (authorized.policy !== policy) {
18
+ throw new Error("Access policy returned the wrong context.");
19
+ }
20
+ return authorized;
21
+ });
22
+ annotateAccessContext(environment.telemetry, context);
17
23
  await handler(context, environment.signal);
18
24
  });
19
25
  }
20
- export function bindPreparedAction(command, policy, prepare, run) {
26
+ export function bindPreparedAction(command, policy, prepare, run, telemetry) {
21
27
  if (boundPolicies.has(command))
22
28
  throw new Error(`Action already bound: ${commandPath(command)}`);
23
29
  boundPolicies.set(command, policy);
30
+ bindTelemetrySelection(command, telemetry);
24
31
  command.action(async () => {
25
32
  const environment = environments.get(rootCommand(command));
26
33
  if (!environment)
27
34
  throw new Error("CLI action environment is missing.");
28
- const prepared = await prepare(environment.signal);
35
+ const prepared = await environment.telemetry.stage("input", () => prepare(environment.signal));
29
36
  if (prepared.handled)
30
37
  return;
31
- const context = await environment.policies[policy].authorize(environment.signal);
32
- if (context.policy !== policy)
33
- throw new Error("Access policy returned the wrong context.");
38
+ const context = await environment.telemetry.stage("access", async () => {
39
+ const authorized = await environment.policies[policy].authorize(environment.signal);
40
+ if (authorized.policy !== policy) {
41
+ throw new Error("Access policy returned the wrong context.");
42
+ }
43
+ return authorized;
44
+ });
45
+ annotateAccessContext(environment.telemetry, context);
34
46
  await run(context, prepared.input, environment.signal);
35
47
  });
36
48
  }
@@ -52,6 +64,28 @@ function rootCommand(command) {
52
64
  current = current.parent;
53
65
  return current;
54
66
  }
67
+ function bindTelemetrySelection(command, definition) {
68
+ if (!definition)
69
+ return;
70
+ if (!command.parent)
71
+ throw new Error(`Telemetry command has no parent: ${command.name()}`);
72
+ command.parent.hook("preSubcommand", (parent, selected) => {
73
+ if (selected !== command)
74
+ return;
75
+ const environment = environments.get(rootCommand(parent));
76
+ if (!environment)
77
+ throw new Error("CLI action environment is missing.");
78
+ environment.telemetry.select(definition);
79
+ });
80
+ }
81
+ function annotateAccessContext(telemetry, context) {
82
+ if (context.policy === "none")
83
+ return;
84
+ telemetry.annotate({
85
+ account_id: context.state.account?.id,
86
+ organization_id: context.state.organization?.id
87
+ });
88
+ }
55
89
  function commandPath(command) {
56
90
  const names = [];
57
91
  let current = command;
@@ -18,11 +18,14 @@ export function registerAgentCommands(program, runtime) {
18
18
  if (modes !== 1)
19
19
  throw new CliUsageError();
20
20
  if (options.schema === true) {
21
- runtime.io.writeOut(`${JSON.stringify(agentSendInputSchema)}\n`);
21
+ await runtime.telemetry.stage("output", async () => {
22
+ runtime.io.writeOut(`${JSON.stringify(agentSendInputSchema)}\n`);
23
+ });
22
24
  return { handled: true };
23
25
  }
24
26
  const input = await runtime.jsonInput.read(options.input, signal);
25
27
  runtime.validators.agentSendInput(input);
28
+ runtime.telemetry.annotate({ parameters: { ...input } });
26
29
  return { handled: false, input };
27
30
  }, async (context, input, signal) => {
28
31
  const result = await runtime.agent.send({
@@ -33,7 +36,14 @@ export function registerAgentCommands(program, runtime) {
33
36
  },
34
37
  signal
35
38
  });
36
- runtime.validators.agentSendOutput(result);
37
- runtime.io.writeOut(`${JSON.stringify(result)}\n`);
39
+ await runtime.telemetry.stage("output", async () => {
40
+ runtime.validators.agentSendOutput(result);
41
+ runtime.io.writeOut(`${JSON.stringify(result)}\n`);
42
+ });
43
+ }, {
44
+ operation: "agent.send",
45
+ mode: () => send.opts().schema === true
46
+ ? "schema"
47
+ : "execute"
38
48
  });
39
49
  }
@@ -8,8 +8,10 @@ export function registerAuthCommands(program, runtime) {
8
8
  .description("登录稿定")
9
9
  .option("--no-browser", "不自动打开浏览器");
10
10
  bindAction(login, "none", async (_context, signal) => {
11
+ const noBrowser = login.opts().browser === false;
12
+ runtime.telemetry.annotate({ parameters: { noBrowser } });
11
13
  const result = await runtime.auth.login({
12
- noBrowser: login.opts().browser === false,
14
+ noBrowser,
13
15
  signal,
14
16
  interaction: {
15
17
  showAuthorization: (url) => runtime.presenter.authorization({
@@ -20,27 +22,33 @@ export function registerAuthCommands(program, runtime) {
20
22
  warn: (message) => runtime.presenter.warning(message)
21
23
  }
22
24
  });
23
- runtime.presenter.success(result.status === "already_logged_in" ? "已登录。" : "登录成功。");
24
- if (result.status === "already_logged_in" && !result.organizationSelected) {
25
- runtime.presenter.warning("尚未选择组织,请执行 gd-cli org switch。");
26
- }
27
- });
25
+ await runtime.telemetry.stage("output", async () => {
26
+ runtime.presenter.success(result.status === "already_logged_in" ? "已登录。" : "登录成功。");
27
+ if (result.status === "already_logged_in" && !result.organizationSelected) {
28
+ runtime.presenter.warning("尚未选择组织,请执行 gd-cli org switch。");
29
+ }
30
+ });
31
+ }, { operation: "auth.login" });
28
32
  const status = leaf(auth.command("status"))
29
33
  .description("查看登录状态")
30
34
  .option("--json", "输出 JSON");
31
35
  bindAction(status, "none", async () => {
32
36
  const result = await runtime.auth.status({ now: runtime.now() });
33
- runtime.validators.authStatus(result);
34
- runtime.presenter.result(result, {
35
- json: status.opts().json === true,
36
- view: authStatusView
37
+ await runtime.telemetry.stage("output", async () => {
38
+ runtime.validators.authStatus(result);
39
+ runtime.presenter.result(result, {
40
+ json: status.opts().json === true,
41
+ view: authStatusView
42
+ });
37
43
  });
38
- });
44
+ }, { operation: "auth.status" });
39
45
  const logout = leaf(auth.command("logout")).description("退出登录");
40
46
  bindAction(logout, "none", async () => {
41
47
  await runtime.auth.logout();
42
- runtime.presenter.success("已退出登录。");
43
- });
48
+ await runtime.telemetry.stage("output", async () => {
49
+ runtime.presenter.success("已退出登录。");
50
+ });
51
+ }, { operation: "auth.logout" });
44
52
  }
45
53
  function leaf(command) {
46
54
  return command.allowExcessArguments(false);
@@ -10,6 +10,7 @@ export function registerDamCommands(program, runtime) {
10
10
  signal.throwIfAborted();
11
11
  const input = browseInput(list);
12
12
  runtime.validators.damListInput(input);
13
+ runtime.telemetry.annotate({ parameters: { ...input } });
13
14
  return { handled: false, input };
14
15
  }, async (context, input, signal) => {
15
16
  const result = await runtime.dam.list({
@@ -17,11 +18,13 @@ export function registerDamCommands(program, runtime) {
17
18
  access: organizationAccess(context.state),
18
19
  signal
19
20
  });
20
- runtime.presenter.result(result, {
21
- json: list.opts().json === true,
22
- view: assetListView
21
+ await runtime.telemetry.stage("output", async () => {
22
+ runtime.presenter.result(result, {
23
+ json: list.opts().json === true,
24
+ view: assetListView
25
+ });
23
26
  });
24
- });
27
+ }, { operation: "dam.list" });
25
28
  const search = browseCommand(leaf(dam.command("search")))
26
29
  .description("按关键词搜索素材")
27
30
  .argument("<query>", "搜索关键词");
@@ -32,6 +35,7 @@ export function registerDamCommands(program, runtime) {
32
35
  ...browseInput(search)
33
36
  };
34
37
  runtime.validators.damSearchInput(input);
38
+ runtime.telemetry.annotate({ parameters: { ...input } });
35
39
  return { handled: false, input };
36
40
  }, async (context, input, signal) => {
37
41
  const result = await runtime.dam.search({
@@ -39,11 +43,13 @@ export function registerDamCommands(program, runtime) {
39
43
  access: organizationAccess(context.state),
40
44
  signal
41
45
  });
42
- runtime.presenter.result(result, {
43
- json: search.opts().json === true,
44
- view: assetListView
46
+ await runtime.telemetry.stage("output", async () => {
47
+ runtime.presenter.result(result, {
48
+ json: search.opts().json === true,
49
+ view: assetListView
50
+ });
45
51
  });
46
- });
52
+ }, { operation: "dam.search" });
47
53
  const get = leaf(dam.command("get"))
48
54
  .description("查看素材完整信息")
49
55
  .argument("<asset>", "素材 ID")
@@ -52,6 +58,7 @@ export function registerDamCommands(program, runtime) {
52
58
  signal.throwIfAborted();
53
59
  const input = { asset_id: position(get, 0) };
54
60
  runtime.validators.damGetInput(input);
61
+ runtime.telemetry.annotate({ parameters: { ...input } });
55
62
  return { handled: false, input };
56
63
  }, async (context, input, signal) => {
57
64
  const result = await runtime.dam.get({
@@ -59,11 +66,13 @@ export function registerDamCommands(program, runtime) {
59
66
  access: organizationAccess(context.state),
60
67
  signal
61
68
  });
62
- runtime.presenter.result(result, {
63
- json: get.opts().json === true,
64
- view: assetDetailView
69
+ await runtime.telemetry.stage("output", async () => {
70
+ runtime.presenter.result(result, {
71
+ json: get.opts().json === true,
72
+ view: assetDetailView
73
+ });
65
74
  });
66
- });
75
+ }, { operation: "dam.get" });
67
76
  const upload = leaf(dam.command("upload"))
68
77
  .description("上传本地文件并创建素材")
69
78
  .argument("<file>", "本地文件路径")
@@ -87,6 +96,7 @@ export function registerDamCommands(program, runtime) {
87
96
  ...(options.waitAnalysis === true ? { waitAnalysis: true } : {})
88
97
  };
89
98
  runtime.validators.damUploadInput(input);
99
+ runtime.telemetry.annotate({ parameters: { ...input } });
90
100
  return { handled: false, input };
91
101
  }, async (context, input, signal) => {
92
102
  const result = await runtime.dam.upload({
@@ -94,11 +104,13 @@ export function registerDamCommands(program, runtime) {
94
104
  access: organizationAccess(context.state),
95
105
  signal
96
106
  });
97
- runtime.presenter.result(result, {
98
- json: upload.opts().json === true,
99
- view: assetDetailView
107
+ await runtime.telemetry.stage("output", async () => {
108
+ runtime.presenter.result(result, {
109
+ json: upload.opts().json === true,
110
+ view: assetDetailView
111
+ });
100
112
  });
101
- });
113
+ }, { operation: "dam.upload" });
102
114
  const deleteCommand = leaf(dam.command("delete"))
103
115
  .description("删除素材")
104
116
  .argument("<asset>", "素材 ID")
@@ -113,6 +125,7 @@ export function registerDamCommands(program, runtime) {
113
125
  ...(options.permanent === true ? { permanent: true } : {})
114
126
  };
115
127
  runtime.validators.damDeleteInput(input);
128
+ runtime.telemetry.annotate({ parameters: { ...input } });
116
129
  return { handled: false, input };
117
130
  }, async (context, input, signal) => {
118
131
  await runtime.dam.delete({
@@ -120,10 +133,12 @@ export function registerDamCommands(program, runtime) {
120
133
  access: organizationAccess(context.state),
121
134
  signal
122
135
  });
123
- runtime.presenter.success(input.permanent === true
124
- ? "素材已永久删除。"
125
- : "素材已移入回收站。");
126
- });
136
+ await runtime.telemetry.stage("output", async () => {
137
+ runtime.presenter.success(input.permanent === true
138
+ ? "素材已永久删除。"
139
+ : "素材已移入回收站。");
140
+ });
141
+ }, { operation: "dam.delete" });
127
142
  }
128
143
  function leaf(command) {
129
144
  return command.allowExcessArguments(false);
@@ -6,10 +6,12 @@ import { DeviceAuthorizationExpiredError } from "../features/auth/use-cases.js";
6
6
  import { AgentInputError } from "../features/agent/use-cases.js";
7
7
  import { ToolInputError } from "../features/tool/catalog.js";
8
8
  import { ObjectStorageUploadError } from "../features/dam/object-storage.js";
9
+ import { DamAssetNotFoundError } from "../features/dam/dam-api-adapter.js";
9
10
  import { DamInputError } from "../features/dam/use-cases.js";
10
11
  import { OrganizationResponseError } from "../features/org/org-service.js";
11
12
  import { OrganizationBindFailedError, OrganizationInvalidError, OrgUsageError } from "../features/org/use-cases.js";
12
13
  import { RemoteRequestError } from "../platform/signed-http-transport.js";
14
+ import { RemoteProtocolError } from "../platform/remote-protocol.js";
13
15
  import { InputContractError, OutputContractError } from "../bootstrap/validators.js";
14
16
  import { JsonInputError } from "../platform/json-input.js";
15
17
  import { LocalFileInputError } from "../platform/safe-upload-file.js";
@@ -59,13 +61,19 @@ export function mapCliError(error, options) {
59
61
  || error instanceof CommanderError) {
60
62
  return failure("USAGE_ERROR", "命令或参数无效,请查看 --help。", 2);
61
63
  }
64
+ if (error instanceof RemoteProtocolError) {
65
+ return failure("REMOTE_PROTOCOL_INCOMPATIBLE", "稿定服务返回了当前 CLI 无法安全处理的结果;为避免重复创作,已停止处理。", 1);
66
+ }
67
+ if (error instanceof DamAssetNotFoundError) {
68
+ return failure("DAM_ASSET_NOT_FOUND", "未找到指定的 DAM 素材。", 1);
69
+ }
62
70
  if (error instanceof RemoteRequestError
63
71
  || error instanceof SsoRequestError
64
72
  || error instanceof DeviceAuthorizationExpiredError
65
73
  || error instanceof OrganizationResponseError
66
74
  || error instanceof OrganizationBindFailedError
67
75
  || error instanceof ObjectStorageUploadError) {
68
- return failure("REMOTE_REQUEST_FAILED", "稿定服务请求失败,请稍后重试。", 1);
76
+ return failure("REMOTE_REQUEST_FAILED", "稿定服务请求失败。", 1);
69
77
  }
70
78
  if (error instanceof OutputContractError) {
71
79
  return failure("INTERNAL_ERROR", "CLI 内部结果校验失败。", 1);
@@ -77,6 +85,16 @@ export function mapCliError(error, options) {
77
85
  }
78
86
  return failure("INTERNAL_ERROR", "CLI 发生内部错误。", 1);
79
87
  }
88
+ export function classifyTelemetryError(error) {
89
+ const outcome = mapCliError(error, { aborted: false });
90
+ return {
91
+ outcome: outcome.kind === "interrupted" ? "interrupted" : "failure",
92
+ ...(outcome.kind === "failure" ? { errorCode: outcome.failure.code } : {}),
93
+ ...(error instanceof RemoteRequestError && error.status !== undefined
94
+ ? { httpStatus: error.status }
95
+ : {})
96
+ };
97
+ }
80
98
  function failure(code, message, exitCode, nextSteps) {
81
99
  return {
82
100
  kind: "failure",
@@ -11,7 +11,9 @@ export function registerModelCommands(program, runtime) {
11
11
  bindAction(list, "organization", async (context, signal) => {
12
12
  const selectedTool = list.opts().tool;
13
13
  const input = selectedTool === undefined ? {} : { tool: selectedTool };
14
- runtime.validators.modelListInput(input);
14
+ await runtime.telemetry.stage("input", async () => {
15
+ runtime.validators.modelListInput(input);
16
+ });
15
17
  const result = await runtime.tool.listModels({
16
18
  input,
17
19
  access: {
@@ -20,28 +22,56 @@ export function registerModelCommands(program, runtime) {
20
22
  },
21
23
  signal
22
24
  });
23
- runtime.validators.modelListOutput(result);
24
- runtime.io.writeOut(`${JSON.stringify(result)}\n`);
25
- });
25
+ if (selectedTool !== undefined)
26
+ runtime.telemetry.annotate({ tool_name: selectedTool });
27
+ await runtime.telemetry.stage("output", async () => {
28
+ runtime.validators.modelListOutput(result);
29
+ runtime.io.writeOut(`${JSON.stringify(result)}\n`);
30
+ });
31
+ }, { operation: "model.list" });
26
32
  const get = model.command("get")
27
33
  .description("查看模型完整信息")
28
34
  .argument("<model>", "模型机器标识")
35
+ .option("--schema", "输出所选模型的实际运行时输入 Schema")
29
36
  .allowExcessArguments(false);
30
37
  bindAction(get, "organization", async (context, signal) => {
31
38
  const modelName = get.processedArgs[0];
32
39
  if (typeof modelName !== "string" || modelName.trim() === "")
33
40
  throw new CliUsageError();
34
41
  const input = { model: modelName };
35
- runtime.validators.modelGetInput(input);
42
+ await runtime.telemetry.stage("input", async () => {
43
+ runtime.validators.modelGetInput(input);
44
+ });
45
+ const access = {
46
+ credential: context.state.credential,
47
+ organizationId: context.state.organization.id
48
+ };
49
+ if (get.opts().schema === true) {
50
+ const schema = await runtime.tool.getModelArgumentsSchema({
51
+ model: modelName,
52
+ access,
53
+ signal
54
+ });
55
+ runtime.telemetry.annotate({ model_id: modelName });
56
+ await runtime.telemetry.stage("output", async () => {
57
+ runtime.io.writeOut(`${JSON.stringify(schema)}\n`);
58
+ });
59
+ return;
60
+ }
36
61
  const result = await runtime.tool.getModel({
37
62
  input,
38
- access: {
39
- credential: context.state.credential,
40
- organizationId: context.state.organization.id
41
- },
63
+ access,
42
64
  signal
43
65
  });
44
- runtime.validators.modelGetOutput(result);
45
- runtime.io.writeOut(`${JSON.stringify(result)}\n`);
66
+ runtime.telemetry.annotate({ model_id: modelName });
67
+ await runtime.telemetry.stage("output", async () => {
68
+ runtime.validators.modelGetOutput(result);
69
+ runtime.io.writeOut(`${JSON.stringify(result)}\n`);
70
+ });
71
+ }, {
72
+ operation: "model.get",
73
+ mode: () => get.opts().schema === true
74
+ ? "schema"
75
+ : "detail"
46
76
  });
47
77
  }