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
@@ -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.invocationId
34
+ ? { invocation_id: presentation.invocationId }
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.invocationId) {
46
+ options.writeError(`诊断 ID: ${presentation.invocationId}\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({
@@ -7,9 +7,13 @@ export class DeviceAuthorizationExpiredError extends Error {
7
7
  export function createAuthUseCases(dependencies) {
8
8
  return {
9
9
  async status(input) {
10
- const state = await dependencies.store.read();
10
+ const state = await dependencies.telemetry.stage("state", () => dependencies.store.read());
11
11
  if (!state)
12
12
  return { logged_in: false, next_steps: ["gd-cli auth login"] };
13
+ dependencies.telemetry.annotate({
14
+ account_id: state.account?.id,
15
+ organization_id: state.organization?.id
16
+ });
13
17
  const valid = Date.parse(state.credential.expiresAt) > input.now.getTime();
14
18
  return {
15
19
  logged_in: true,
@@ -33,86 +37,99 @@ export function createAuthUseCases(dependencies) {
33
37
  };
34
38
  },
35
39
  async logout() {
36
- await dependencies.store.remove();
40
+ await dependencies.telemetry.stage("state", () => dependencies.store.remove());
37
41
  },
38
42
  async login(input) {
39
43
  input.signal.throwIfAborted();
40
- const existing = await dependencies.store.read();
44
+ const existing = await dependencies.telemetry.stage("state", () => dependencies.store.read());
41
45
  if (existing && Date.parse(existing.credential.expiresAt) > dependencies.now().getTime()) {
46
+ dependencies.telemetry.annotate({
47
+ account_id: existing.account?.id,
48
+ organization_id: existing.organization?.id
49
+ });
42
50
  return {
43
51
  status: "already_logged_in",
44
52
  organizationSelected: existing.organization !== undefined
45
53
  };
46
54
  }
47
- const authorization = await dependencies.sso.startDeviceAuthorization(input.signal);
48
- const deadline = dependencies.now().getTime() + authorization.expiresInSeconds * 1000;
49
- const authorizationUrl = new URL("/cli/device-authorization", dependencies.authPageOrigin);
50
- authorizationUrl.searchParams.set("code", authorization.authorizeCode);
51
- await input.interaction.showAuthorization(authorizationUrl);
52
- if (!input.noBrowser) {
53
- try {
54
- await input.interaction.openBrowser(authorizationUrl);
55
- }
56
- catch {
57
- input.interaction.warn("无法自动打开浏览器,请手动访问授权链接。");
55
+ const authorization = await dependencies.telemetry.stage("authorize", async () => {
56
+ const authorization = await dependencies.sso.startDeviceAuthorization(input.signal);
57
+ const authorizationUrl = new URL("/cli/device-authorization", dependencies.authPageOrigin);
58
+ authorizationUrl.searchParams.set("code", authorization.authorizeCode);
59
+ await input.interaction.showAuthorization(authorizationUrl);
60
+ if (!input.noBrowser) {
61
+ try {
62
+ await input.interaction.openBrowser(authorizationUrl);
63
+ }
64
+ catch {
65
+ input.interaction.warn("无法自动打开浏览器,请手动访问授权链接。");
66
+ }
58
67
  }
59
- }
68
+ return authorization;
69
+ });
70
+ const deadline = dependencies.now().getTime() + authorization.expiresInSeconds * 1000;
60
71
  let interval = authorization.intervalSeconds * 1000;
61
- while (true) {
62
- if (dependencies.now().getTime() + interval > deadline) {
63
- throw new DeviceAuthorizationExpiredError();
64
- }
65
- await dependencies.sleep(interval, input.signal);
66
- input.signal.throwIfAborted();
67
- const token = await dependencies.sso.pollDeviceToken(authorization.deviceCode, input.signal);
68
- if (token.status === "pending")
69
- continue;
70
- if (token.status === "slow_down") {
71
- interval += 5_000;
72
- continue;
73
- }
74
- let state = {
75
- version: 1,
76
- credential: token.credential,
77
- ...(token.account ? { account: token.account } : {})
78
- };
79
- await dependencies.store.write(state);
80
- let organizationId = token.organizationId;
81
- try {
82
- const identity = await dependencies.sso.getIdentity(token.credential.accessKey, input.signal);
83
- organizationId = identity.organizationId ?? organizationId;
84
- if (!state.account && identity.account) {
85
- state = { ...state, account: identity.account };
86
- await dependencies.store.write(state);
72
+ const token = await dependencies.telemetry.stage("wait", async () => {
73
+ while (true) {
74
+ if (dependencies.now().getTime() + interval > deadline) {
75
+ throw new DeviceAuthorizationExpiredError();
87
76
  }
88
- }
89
- catch {
90
- if (input.signal.aborted)
91
- throw input.signal.reason;
92
- }
93
- if (!organizationId) {
94
- input.interaction.warn("登录成功,请执行 gd-cli org switch 选择组织。");
95
- return { status: "logged_in", organizationSelected: false };
96
- }
97
- try {
98
- const organization = await dependencies.organizationBinder.bindAuthorizedOrganization({
99
- credential: token.credential,
100
- organizationId,
101
- signal: input.signal
102
- });
103
- if (!organization) {
104
- input.interaction.warn("登录成功,但未能选择组织;请执行 gd-cli org switch。");
105
- return { status: "logged_in", organizationSelected: false };
77
+ await dependencies.sleep(interval, input.signal);
78
+ input.signal.throwIfAborted();
79
+ const result = await dependencies.sso.pollDeviceToken(authorization.deviceCode, input.signal);
80
+ if (result.status === "pending")
81
+ continue;
82
+ if (result.status === "slow_down") {
83
+ interval += 5_000;
84
+ continue;
106
85
  }
107
- await dependencies.store.write({ ...state, organization });
108
- return { status: "logged_in", organizationSelected: true };
86
+ return result;
87
+ }
88
+ });
89
+ let state = {
90
+ version: 1,
91
+ credential: token.credential,
92
+ ...(token.account ? { account: token.account } : {})
93
+ };
94
+ await dependencies.telemetry.stage("state", () => dependencies.store.write(state));
95
+ dependencies.telemetry.annotate({ account_id: state.account?.id });
96
+ let organizationId = token.organizationId;
97
+ try {
98
+ const identity = await dependencies.telemetry.stage("identity", () => (dependencies.sso.getIdentity(token.credential.accessKey, input.signal)));
99
+ organizationId = identity.organizationId ?? organizationId;
100
+ if (!state.account && identity.account) {
101
+ state = { ...state, account: identity.account };
102
+ await dependencies.telemetry.stage("state", () => dependencies.store.write(state));
103
+ dependencies.telemetry.annotate({ account_id: identity.account.id });
109
104
  }
110
- catch {
111
- if (input.signal.aborted)
112
- throw input.signal.reason;
113
- input.interaction.warn("登录成功,但组织绑定失败;请执行 gd-cli org switch。");
105
+ }
106
+ catch {
107
+ if (input.signal.aborted)
108
+ throw input.signal.reason;
109
+ }
110
+ if (!organizationId) {
111
+ input.interaction.warn("登录成功,请执行 gd-cli org switch 选择组织。");
112
+ return { status: "logged_in", organizationSelected: false };
113
+ }
114
+ try {
115
+ const organization = await dependencies.telemetry.stage("bind", () => (dependencies.organizationBinder.bindAuthorizedOrganization({
116
+ credential: token.credential,
117
+ organizationId,
118
+ signal: input.signal
119
+ })));
120
+ if (!organization) {
121
+ input.interaction.warn("登录成功,但未能选择组织;请执行 gd-cli org switch。");
114
122
  return { status: "logged_in", organizationSelected: false };
115
123
  }
124
+ await dependencies.telemetry.stage("state", () => dependencies.store.write({ ...state, organization }));
125
+ dependencies.telemetry.annotate({ organization_id: organization.id });
126
+ return { status: "logged_in", organizationSelected: true };
127
+ }
128
+ catch {
129
+ if (input.signal.aborted)
130
+ throw input.signal.reason;
131
+ input.interaction.warn("登录成功,但组织绑定失败;请执行 gd-cli org switch。");
132
+ return { status: "logged_in", organizationSelected: false };
116
133
  }
117
134
  }
118
135
  };
@@ -8,6 +8,16 @@ const publicAssetTypes = new Set([
8
8
  "template",
9
9
  "file"
10
10
  ]);
11
+ const internalAssetTypes = new Map([
12
+ ["gdpic", "image"],
13
+ ["gdimage", "image"],
14
+ ["gdvideo", "video"],
15
+ ["gdaudio", "audio"],
16
+ ["gdfont", "font"],
17
+ ["gdtemplate", "template"],
18
+ ["gdfile", "file"],
19
+ ["gdslide", "file"]
20
+ ]);
11
21
  const recognizableAssetKeys = new Set([
12
22
  "asset_id",
13
23
  "id",
@@ -39,6 +49,12 @@ function record(value) {
39
49
  function nonblank(value) {
40
50
  return typeof value === "string" && value.trim() !== "" ? value : undefined;
41
51
  }
52
+ function publicSourceFormat(value) {
53
+ const format = nonblank(value);
54
+ return format !== undefined && !internalAssetTypes.has(format.toLowerCase())
55
+ ? format
56
+ : undefined;
57
+ }
42
58
  function finiteNumber(value) {
43
59
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
44
60
  }
@@ -114,19 +130,11 @@ function assetType(item, format, mediaType) {
114
130
  const explicit = nonblank(item.type)?.toLowerCase();
115
131
  if (explicit !== undefined && publicAssetTypes.has(explicit))
116
132
  return explicit;
117
- const contentFormat = (nonblank(item.content_format) ?? nonblank(item.storage_format))?.toLowerCase();
118
- if (contentFormat === "gdpic" || contentFormat === "gdimage")
119
- return "image";
120
- if (contentFormat === "gdvideo")
121
- return "video";
122
- if (contentFormat === "gdaudio")
123
- return "audio";
124
- if (contentFormat === "gdfont")
125
- return "font";
126
- if (contentFormat === "gdtemplate")
127
- return "template";
128
- if (contentFormat === "gdfile" || contentFormat === "gdslide")
129
- return "file";
133
+ for (const candidate of [item.content_format, item.storage_format, item.format]) {
134
+ const internalType = internalAssetTypes.get(nonblank(candidate)?.toLowerCase() ?? "");
135
+ if (internalType !== undefined)
136
+ return internalType;
137
+ }
130
138
  const mediaCategory = mediaType?.split("/", 1)[0]?.toLowerCase();
131
139
  if (mediaCategory !== undefined && publicAssetTypes.has(mediaCategory))
132
140
  return mediaCategory;
@@ -160,7 +168,7 @@ function normalizeAsset(value, fallback) {
160
168
  ?? record(item.cover);
161
169
  const repository = record(item.repository_info) ?? record(item.repositoryInfo);
162
170
  const folder = record(item.folder_info) ?? record(item.folderInfo);
163
- const format = nonblank(origin?.format) ?? nonblank(item.format);
171
+ const format = publicSourceFormat(origin?.format) ?? publicSourceFormat(item.format);
164
172
  const mediaType = nonblank(origin?.mime_type)
165
173
  ?? nonblank(item.mime_type)
166
174
  ?? nonblank(item.mimeType);
@@ -1,5 +1,11 @@
1
1
  import { RemoteRequestError } from "../../platform/signed-http-transport.js";
2
2
  import { projectDamAssetDetail, projectDamAssetSummary } from "./asset-projection.js";
3
+ export class DamAssetNotFoundError extends Error {
4
+ constructor() {
5
+ super("DAM 素材不存在。");
6
+ this.name = "DamAssetNotFoundError";
7
+ }
8
+ }
3
9
  const contentFormats = {
4
10
  image: ["gdpic", "gdimage"],
5
11
  video: ["gdvideo"],
@@ -152,10 +158,19 @@ export function createDamApiAdapter(dependencies) {
152
158
  return searchResult(response, input);
153
159
  },
154
160
  async getAsset(input) {
155
- const response = await dependencies.transport.getJson({
156
- path: `/dam/asset/${encodeURIComponent(input.assetId)}`,
157
- ...requestContext(input)
158
- });
161
+ let response;
162
+ try {
163
+ response = await dependencies.transport.getJson({
164
+ path: `/dam/asset/${encodeURIComponent(input.assetId)}`,
165
+ ...requestContext(input)
166
+ });
167
+ }
168
+ catch (error) {
169
+ if (error instanceof RemoteRequestError && error.status === 404) {
170
+ throw new DamAssetNotFoundError();
171
+ }
172
+ throw error;
173
+ }
159
174
  const asset = projectDamAssetDetail(response, input.assetId);
160
175
  if (asset === undefined)
161
176
  throw new RemoteRequestError();
@@ -66,6 +66,8 @@ const defaultPutOss = async (input) => {
66
66
  accessKeyId: input.token.accessKeyId,
67
67
  accessKeySecret: input.token.accessKeySecret,
68
68
  stsToken: input.token.securityToken,
69
+ // DAM issues one-shot credentials without a refresh endpoint.
70
+ refreshSTSTokenInterval: Number.MAX_SAFE_INTEGER,
69
71
  endpoint: input.endpoint,
70
72
  region: input.token.region,
71
73
  bucket: input.token.bucketName,