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
@@ -5,11 +5,14 @@ import { SsoRequestError } from "../features/auth/sso-service.js";
5
5
  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
+ import { ToolTaskFailedError } from "../features/tool/tool-api-adapter.js";
8
9
  import { ObjectStorageUploadError } from "../features/dam/object-storage.js";
10
+ import { DamAssetNotFoundError } from "../features/dam/dam-api-adapter.js";
9
11
  import { DamInputError } from "../features/dam/use-cases.js";
10
12
  import { OrganizationResponseError } from "../features/org/org-service.js";
11
13
  import { OrganizationBindFailedError, OrganizationInvalidError, OrgUsageError } from "../features/org/use-cases.js";
12
14
  import { RemoteRequestError } from "../platform/signed-http-transport.js";
15
+ import { RemoteProtocolError } from "../platform/remote-protocol.js";
13
16
  import { InputContractError, OutputContractError } from "../bootstrap/validators.js";
14
17
  import { JsonInputError } from "../platform/json-input.js";
15
18
  import { LocalFileInputError } from "../platform/safe-upload-file.js";
@@ -59,13 +62,22 @@ export function mapCliError(error, options) {
59
62
  || error instanceof CommanderError) {
60
63
  return failure("USAGE_ERROR", "命令或参数无效,请查看 --help。", 2);
61
64
  }
65
+ if (error instanceof RemoteProtocolError) {
66
+ return failure("REMOTE_PROTOCOL_INCOMPATIBLE", "稿定服务返回了当前 CLI 无法安全处理的结果;为避免重复创作,已停止处理。", 1);
67
+ }
68
+ if (error instanceof ToolTaskFailedError) {
69
+ return failure("TOOL_TASK_FAILED", "稿定工具任务执行失败。", 1);
70
+ }
71
+ if (error instanceof DamAssetNotFoundError) {
72
+ return failure("DAM_ASSET_NOT_FOUND", "未找到指定的 DAM 素材。", 1);
73
+ }
62
74
  if (error instanceof RemoteRequestError
63
75
  || error instanceof SsoRequestError
64
76
  || error instanceof DeviceAuthorizationExpiredError
65
77
  || error instanceof OrganizationResponseError
66
78
  || error instanceof OrganizationBindFailedError
67
79
  || error instanceof ObjectStorageUploadError) {
68
- return failure("REMOTE_REQUEST_FAILED", "稿定服务请求失败,请稍后重试。", 1);
80
+ return failure("REMOTE_REQUEST_FAILED", "稿定服务请求失败。", 1);
69
81
  }
70
82
  if (error instanceof OutputContractError) {
71
83
  return failure("INTERNAL_ERROR", "CLI 内部结果校验失败。", 1);
@@ -77,6 +89,27 @@ export function mapCliError(error, options) {
77
89
  }
78
90
  return failure("INTERNAL_ERROR", "CLI 发生内部错误。", 1);
79
91
  }
92
+ export function classifyTelemetryError(error) {
93
+ const outcome = mapCliError(error, { aborted: false });
94
+ const remoteError = error instanceof RemoteRequestError
95
+ || error instanceof RemoteProtocolError
96
+ || error instanceof ToolTaskFailedError
97
+ ? error
98
+ : undefined;
99
+ return {
100
+ outcome: outcome.kind === "interrupted" ? "interrupted" : "failure",
101
+ ...(outcome.kind === "failure" ? { errorCode: outcome.failure.code } : {}),
102
+ ...(error instanceof RemoteRequestError && error.status !== undefined
103
+ ? { httpStatus: error.status }
104
+ : {}),
105
+ ...(remoteError?.remoteCode === undefined
106
+ ? {}
107
+ : { remoteErrorCode: remoteError.remoteCode }),
108
+ ...(remoteError?.remoteBody === undefined
109
+ ? {}
110
+ : { remoteErrorBody: remoteError.remoteBody })
111
+ };
112
+ }
80
113
  function failure(code, message, exitCode, nextSteps) {
81
114
  return {
82
115
  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,9 +22,13 @@ 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>", "模型机器标识")
@@ -32,16 +38,24 @@ export function registerModelCommands(program, runtime) {
32
38
  if (typeof modelName !== "string" || modelName.trim() === "")
33
39
  throw new CliUsageError();
34
40
  const input = { model: modelName };
35
- runtime.validators.modelGetInput(input);
41
+ await runtime.telemetry.stage("input", async () => {
42
+ runtime.validators.modelGetInput(input);
43
+ });
44
+ const access = {
45
+ credential: context.state.credential,
46
+ organizationId: context.state.organization.id
47
+ };
36
48
  const result = await runtime.tool.getModel({
37
49
  input,
38
- access: {
39
- credential: context.state.credential,
40
- organizationId: context.state.organization.id
41
- },
50
+ access,
42
51
  signal
43
52
  });
44
- runtime.validators.modelGetOutput(result);
45
- runtime.io.writeOut(`${JSON.stringify(result)}\n`);
53
+ runtime.telemetry.annotate({ model_id: modelName });
54
+ await runtime.telemetry.stage("output", async () => {
55
+ runtime.validators.modelGetOutput(result);
56
+ runtime.io.writeOut(`${JSON.stringify(result)}\n`);
57
+ });
58
+ }, {
59
+ operation: "model.get"
46
60
  });
47
61
  }
@@ -10,23 +10,27 @@ export function registerOrgCommands(program, runtime) {
10
10
  .option("--json", "输出 JSON");
11
11
  bindAction(list, "credential", async (context, signal) => {
12
12
  const result = await runtime.org.list({ state: context.state, signal });
13
- runtime.validators.orgList(result);
14
- runtime.presenter.result(result, {
15
- json: list.opts().json === true,
16
- view: orgListView
13
+ await runtime.telemetry.stage("output", async () => {
14
+ runtime.validators.orgList(result);
15
+ runtime.presenter.result(result, {
16
+ json: list.opts().json === true,
17
+ view: orgListView
18
+ });
17
19
  });
18
- });
20
+ }, { operation: "org.list" });
19
21
  const current = leaf(org.command("current"))
20
22
  .description("查看当前组织")
21
23
  .option("--json", "输出 JSON");
22
24
  bindAction(current, "none", async () => {
23
25
  const result = await runtime.org.current();
24
- runtime.validators.orgCurrent(result);
25
- runtime.presenter.result(result, {
26
- json: current.opts().json === true,
27
- view: orgCurrentView
26
+ await runtime.telemetry.stage("output", async () => {
27
+ runtime.validators.orgCurrent(result);
28
+ runtime.presenter.result(result, {
29
+ json: current.opts().json === true,
30
+ view: orgCurrentView
31
+ });
28
32
  });
29
- });
33
+ }, { operation: "org.current" });
30
34
  const switchCommand = leaf(org.command("switch"))
31
35
  .description("切换当前组织")
32
36
  .option("--org <org-id>", "指定组织 ID");
@@ -41,8 +45,10 @@ export function registerOrgCommands(program, runtime) {
41
45
  : { organizationId }),
42
46
  signal
43
47
  });
44
- runtime.presenter.success(`已切换到${record.publicName} (${record.id})。`);
45
- });
48
+ await runtime.telemetry.stage("output", async () => {
49
+ runtime.presenter.success(`已切换到${record.publicName} (${record.id})。`);
50
+ });
51
+ }, { operation: "org.switch" });
46
52
  }
47
53
  function leaf(command) {
48
54
  return command.allowExcessArguments(false);
@@ -27,7 +27,14 @@ export function createPresenter(options) {
27
27
  return;
28
28
  }
29
29
  if (presentation.json) {
30
- options.writeError(`${JSON.stringify({ error: outcome.failure })}\n`);
30
+ options.writeError(`${JSON.stringify({
31
+ error: {
32
+ ...outcome.failure,
33
+ ...(presentation.traceId
34
+ ? { trace_id: presentation.traceId }
35
+ : {})
36
+ }
37
+ })}\n`);
31
38
  return;
32
39
  }
33
40
  options.writeError(`${outcome.failure.code}: ${outcome.failure.message}\n`);
@@ -35,6 +42,9 @@ export function createPresenter(options) {
35
42
  if (nextSteps.length > 0) {
36
43
  options.writeError(`下一步: ${nextSteps.join(",")}\n`);
37
44
  }
45
+ if (presentation.traceId) {
46
+ options.writeError(`诊断 ID: ${presentation.traceId}\n`);
47
+ }
38
48
  }
39
49
  };
40
50
  }
@@ -15,13 +15,15 @@ export function registerToolCommands(program, runtime) {
15
15
  },
16
16
  signal
17
17
  });
18
- runtime.validators.toolListOutput(result);
19
- runtime.io.writeOut(`${JSON.stringify(result)}\n`);
20
- });
18
+ await runtime.telemetry.stage("output", async () => {
19
+ runtime.validators.toolListOutput(result);
20
+ runtime.io.writeOut(`${JSON.stringify(result)}\n`);
21
+ });
22
+ }, { operation: "tool.list" });
21
23
  const call = tool.command("call")
22
24
  .description("调用创作工具")
23
25
  .argument("<tool>", "工具机器标识")
24
- .option("--schema", "输出当前工具的输入 Schema")
26
+ .option("--schema", "输出当前工具所有模型参数并集的输入 Schema")
25
27
  .option("--input <file|->", "从文件或标准输入读取参数 JSON")
26
28
  .allowExcessArguments(false);
27
29
  bindPreparedAction(call, "organization", async (signal) => {
@@ -51,7 +53,10 @@ export function registerToolCommands(program, runtime) {
51
53
  access,
52
54
  signal
53
55
  });
54
- runtime.io.writeOut(`${JSON.stringify(schema)}\n`);
56
+ runtime.telemetry.annotate({ tool_name: prepared.name });
57
+ await runtime.telemetry.stage("output", async () => {
58
+ runtime.io.writeOut(`${JSON.stringify(schema)}\n`);
59
+ });
55
60
  return;
56
61
  }
57
62
  const result = await runtime.tool.call({
@@ -59,7 +64,14 @@ export function registerToolCommands(program, runtime) {
59
64
  access,
60
65
  signal
61
66
  });
62
- runtime.validators.toolCallOutput(result);
63
- runtime.io.writeOut(`${JSON.stringify(result)}\n`);
67
+ await runtime.telemetry.stage("output", async () => {
68
+ runtime.validators.toolCallOutput(result);
69
+ runtime.io.writeOut(`${JSON.stringify(result)}\n`);
70
+ });
71
+ }, {
72
+ operation: "tool.call",
73
+ mode: () => call.opts().schema === true
74
+ ? "schema"
75
+ : "execute"
64
76
  });
65
77
  }
@@ -5,8 +5,10 @@ export function registerUpdateCommand(program, runtime) {
5
5
  .allowExcessArguments(false);
6
6
  bindAction(update, "none", async (_context, signal) => {
7
7
  const result = await runtime.update.run(signal);
8
- runtime.presenter.success(result.updated
9
- ? `gd-cli 已更新到 ${result.version},Agent Skill 已同步。`
10
- : `gd-cli ${result.version} 已是最新版本,Agent Skill 已同步。`);
11
- });
8
+ await runtime.telemetry.stage("output", async () => {
9
+ runtime.presenter.success(result.updated
10
+ ? `gd-cli 已更新到 ${result.version}Agent Skill 已同步。`
11
+ : `gd-cli ${result.version} 已是最新版本,Agent Skill 已同步。`);
12
+ });
13
+ }, { operation: "update" });
12
14
  }
@@ -10,22 +10,27 @@ export function createCreativeAgentAdapter(dependencies) {
10
10
  localMessageId: dependencies.idFactory()
11
11
  }
12
12
  : { localMessageId: dependencies.idFactory() };
13
- const stream = await dependencies.transport.postStream({
14
- path: "/ai-agent/v1/thread/completion",
15
- body: buildCreativeRequest(message, ids),
16
- accept: "text/event-stream",
17
- credential: access.credential,
18
- organizationId: access.organizationId,
19
- signal
13
+ const streamed = await dependencies.telemetry.stage("execute", async () => {
14
+ const stream = await dependencies.transport.postStream({
15
+ path: "/ai-agent/v1/thread/completion",
16
+ body: buildCreativeRequest(message, ids),
17
+ accept: "text/event-stream",
18
+ credential: access.credential,
19
+ organizationId: access.organizationId,
20
+ signal
21
+ });
22
+ return parseCreativeStream(stream, signal);
20
23
  });
21
- const contextId = creativeContextId(await parseCreativeStream(stream, signal));
22
- const messages = await dependencies.transport.getJson({
23
- path: `/ai-agent/v1/thread/${encodeURIComponent(contextId)}/messages`,
24
- credential: access.credential,
25
- organizationId: access.organizationId,
26
- signal
24
+ const contextId = creativeContextId(streamed);
25
+ return dependencies.telemetry.stage("result", async () => {
26
+ const messages = await dependencies.transport.getJson({
27
+ path: `/ai-agent/v1/thread/${encodeURIComponent(contextId)}/messages`,
28
+ credential: access.credential,
29
+ organizationId: access.organizationId,
30
+ signal
31
+ });
32
+ return normalizeCreativeMessages(creativeTurnMessages(messages, ids.localMessageId));
27
33
  });
28
- return normalizeCreativeMessages(creativeTurnMessages(messages, ids.localMessageId));
29
34
  }
30
35
  };
31
36
  }
@@ -1,4 +1,4 @@
1
- import { RemoteRequestError } from "../../platform/signed-http-transport.js";
1
+ import { RemoteProtocolError } from "../../platform/remote-protocol.js";
2
2
  export function buildCreativeRequest(message, ids) {
3
3
  const prompt = [];
4
4
  const textParts = [];
@@ -110,7 +110,7 @@ function nonEmptyString(value) {
110
110
  return typeof value === "string" && value.trim() !== "" ? value : undefined;
111
111
  }
112
112
  function fail() {
113
- throw new RemoteRequestError();
113
+ throw new RemoteProtocolError();
114
114
  }
115
115
  export function creativeContextId(messages) {
116
116
  if (messages.some((message) => message.event === "system_error"))
@@ -125,7 +125,11 @@ export function creativeTurnMessages(value, localMessageId) {
125
125
  return fail();
126
126
  }
127
127
  const messages = value;
128
- const start = messages.findIndex((message) => message.role === "user" && message.local_message_id === localMessageId);
128
+ const start = messages.findIndex((message) => {
129
+ if (message.local_message_id !== localMessageId)
130
+ return false;
131
+ return message.role === "user" || contentOf(message)?.type === "function_response";
132
+ });
129
133
  if (start < 0)
130
134
  return fail();
131
135
  return messages.slice(start);
@@ -37,69 +37,78 @@ export function createAgentUseCases(dependencies) {
37
37
  async send({ input, access, signal }) {
38
38
  signal.throwIfAborted();
39
39
  const prepared = [];
40
- for (const part of input.message.parts) {
41
- signal.throwIfAborted();
42
- if ("text" in part) {
43
- prepared.push({ kind: "text", text: part.text });
44
- continue;
45
- }
46
- if ("url" in part) {
47
- const role = part.metadata?.role ?? "reference";
48
- if (part.url.startsWith("file:")) {
49
- const uploadInput = {
50
- url: part.url,
51
- ...(part.mediaType === undefined ? {} : { mediaType: part.mediaType }),
52
- ...(part.filename === undefined ? {} : { filename: part.filename })
53
- };
54
- const asset = await dependencies.uploader.upload(uploadInput, access, signal);
55
- prepared.push({ kind: "asset", asset, role });
40
+ const prepare = async () => {
41
+ for (const part of input.message.parts) {
42
+ signal.throwIfAborted();
43
+ if ("text" in part) {
44
+ prepared.push({ kind: "text", text: part.text });
56
45
  continue;
57
46
  }
58
- let url;
59
- try {
60
- url = parseSafeRemoteAssetUrl(part.url);
47
+ if ("url" in part) {
48
+ const role = part.metadata?.role ?? "reference";
49
+ if (part.url.startsWith("file:")) {
50
+ const uploadInput = {
51
+ url: part.url,
52
+ ...(part.mediaType === undefined ? {} : { mediaType: part.mediaType }),
53
+ ...(part.filename === undefined ? {} : { filename: part.filename })
54
+ };
55
+ const asset = await dependencies.uploader.upload(uploadInput, access, signal);
56
+ prepared.push({ kind: "asset", asset, role });
57
+ continue;
58
+ }
59
+ let url;
60
+ try {
61
+ url = parseSafeRemoteAssetUrl(part.url);
62
+ }
63
+ catch (error) {
64
+ if (error instanceof UrlSafetyError)
65
+ throw new AgentInputError();
66
+ throw error;
67
+ }
68
+ const filename = part.filename ?? pathnameFilename(url);
69
+ prepared.push({
70
+ kind: "asset",
71
+ asset: {
72
+ url: part.url,
73
+ mediaType: part.mediaType ?? inferMediaType(filename),
74
+ filename,
75
+ fromUserUpload: false
76
+ },
77
+ role
78
+ });
79
+ continue;
61
80
  }
62
- catch (error) {
63
- if (error instanceof UrlSafetyError)
64
- throw new AgentInputError();
65
- throw error;
81
+ if ("questionMessageId" in part.data) {
82
+ prepared.push({
83
+ kind: "answers",
84
+ data: {
85
+ questionMessageId: part.data.questionMessageId,
86
+ answers: part.data.answers.map((answer) => ({
87
+ question: answer.question,
88
+ values: [...answer.values]
89
+ }))
90
+ }
91
+ });
92
+ continue;
66
93
  }
67
- const filename = part.filename ?? pathnameFilename(url);
68
94
  prepared.push({
69
- kind: "asset",
70
- asset: {
71
- url: part.url,
72
- mediaType: part.mediaType ?? inferMediaType(filename),
73
- filename,
74
- fromUserUpload: false
75
- },
76
- role
77
- });
78
- continue;
79
- }
80
- if ("questionMessageId" in part.data) {
81
- prepared.push({
82
- kind: "answers",
95
+ kind: "parameters",
83
96
  data: {
84
- questionMessageId: part.data.questionMessageId,
85
- answers: part.data.answers.map((answer) => ({
86
- question: answer.question,
87
- values: [...answer.values]
88
- }))
97
+ ...(part.data.ratio === undefined ? {} : { ratio: part.data.ratio }),
98
+ ...(part.data.resolution === undefined ? {} : { resolution: part.data.resolution }),
99
+ ...(part.data.durationSeconds === undefined
100
+ ? {}
101
+ : { durationSeconds: part.data.durationSeconds })
89
102
  }
90
103
  });
91
- continue;
92
104
  }
93
- prepared.push({
94
- kind: "parameters",
95
- data: {
96
- ...(part.data.ratio === undefined ? {} : { ratio: part.data.ratio }),
97
- ...(part.data.resolution === undefined ? {} : { resolution: part.data.resolution }),
98
- ...(part.data.durationSeconds === undefined
99
- ? {}
100
- : { durationSeconds: part.data.durationSeconds })
101
- }
102
- });
105
+ };
106
+ if (input.message.parts.some((part) => (typeof part === "object" && part !== null
107
+ && "url" in part && typeof part.url === "string" && part.url.startsWith("file:")))) {
108
+ await dependencies.telemetry.stage("upload", prepare);
109
+ }
110
+ else {
111
+ await prepare();
103
112
  }
104
113
  signal.throwIfAborted();
105
114
  return dependencies.completion.complete({