gaoding-cli 1.0.0-alpha.12 → 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 (40) 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/sso-service.js +17 -2
  21. package/dist/src/features/auth/use-cases.js +82 -52
  22. package/dist/src/features/dam/asset-projection.js +22 -14
  23. package/dist/src/features/dam/dam-api-adapter.js +19 -4
  24. package/dist/src/features/dam/object-storage.js +2 -0
  25. package/dist/src/features/dam/registered-uploader.js +58 -35
  26. package/dist/src/features/dam/use-cases.js +39 -32
  27. package/dist/src/features/org/use-cases.js +27 -11
  28. package/dist/src/features/tool/catalog.js +7 -3
  29. package/dist/src/features/tool/dynamic-schema.js +19 -9
  30. package/dist/src/features/tool/mns-catalog-adapter.js +7 -4
  31. package/dist/src/features/tool/tool-api-adapter.js +35 -33
  32. package/dist/src/features/tool/use-cases.js +68 -36
  33. package/dist/src/features/update/update-service.js +28 -22
  34. package/dist/src/platform/json-input.js +3 -0
  35. package/dist/src/platform/remote-protocol.js +6 -0
  36. package/dist/src/telemetry/invocation.js +128 -0
  37. package/dist/src/telemetry/sls-sink.js +31 -0
  38. package/package.json +1 -1
  39. package/skills/gd-cli/references/creation.md +4 -2
  40. package/skills/gd-cli/references/errors.md +3 -1
@@ -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,
@@ -114,24 +114,45 @@ export function createDamRegisteredUploader(dependencies) {
114
114
  return {
115
115
  async upload(input) {
116
116
  try {
117
+ const stage = (name, run) => (dependencies.telemetry.stage(name, async () => {
118
+ try {
119
+ return await run();
120
+ }
121
+ catch (error) {
122
+ if (input.signal.aborted)
123
+ throw input.signal.reason;
124
+ if (error instanceof RemoteRequestError)
125
+ throw error;
126
+ throw new RemoteRequestError();
127
+ }
128
+ }));
117
129
  input.signal.throwIfAborted();
118
- const [digest, metadata] = await Promise.all([
119
- hashes(input.file.filePath, input.signal),
120
- localMetadata(input.file, input.signal)
121
- ]);
122
- input.signal.throwIfAborted();
123
- const contentId = await dependencies.api.createAssetId({
124
- access: input.access,
125
- signal: input.signal
130
+ const prepared = await stage("prepare", async () => {
131
+ const [digest, metadata] = await Promise.all([
132
+ hashes(input.file.filePath, input.signal),
133
+ localMetadata(input.file, input.signal)
134
+ ]);
135
+ input.signal.throwIfAborted();
136
+ const contentId = await dependencies.api.createAssetId({
137
+ access: input.access,
138
+ signal: input.signal
139
+ });
140
+ const format = input.file.format.toLowerCase();
141
+ return {
142
+ digest,
143
+ metadata,
144
+ contentId,
145
+ format,
146
+ damFormat: storageFormat(format),
147
+ title: input.input.title ?? filenameTitle(input.file.filename),
148
+ folderId: input.input.folderId ?? "0"
149
+ };
126
150
  });
127
- const stored = await dependencies.storage.upload({
151
+ const { digest, metadata, contentId, format, damFormat, title, folderId } = prepared;
152
+ const stored = await stage("upload", () => dependencies.storage.upload({
128
153
  file: input.file,
129
154
  contentId
130
- }, input.access, input.signal);
131
- const format = input.file.format.toLowerCase();
132
- const damFormat = storageFormat(format);
133
- const title = input.input.title ?? filenameTitle(input.file.filename);
134
- const folderId = input.input.folderId ?? "0";
155
+ }, input.access, input.signal));
135
156
  const preview = input.file.mediaType.startsWith("image/")
136
157
  ? {
137
158
  url: stored.url,
@@ -182,35 +203,37 @@ export function createDamRegisteredUploader(dependencies) {
182
203
  });
183
204
  if (fallback === undefined)
184
205
  throw new RemoteRequestError();
185
- let latest = await dependencies.api.createMaterial({
206
+ let latest = await stage("persist", () => dependencies.api.createMaterial({
186
207
  body,
187
208
  fallback,
188
209
  access: input.access,
189
210
  signal: input.signal
190
- });
211
+ }));
191
212
  if (input.input.waitAnalysis !== true || terminalAnalysisStatus(latest.analysis_status)) {
192
213
  return latest;
193
214
  }
194
- const deadline = now().getTime() + 180_000;
195
- while (true) {
196
- input.signal.throwIfAborted();
197
- const current = await dependencies.api.getBatchStatus({
198
- assetId: latest.asset_id,
199
- fallback: latest,
200
- access: input.access,
201
- signal: input.signal
202
- });
203
- if (current !== null)
204
- latest = current;
205
- if (terminalAnalysisStatus(latest.analysis_status))
206
- return latest;
207
- const remaining = deadline - now().getTime();
208
- if (remaining <= 0) {
209
- dependencies.warn("素材分析仍在处理中,已返回当前结果。");
210
- return latest;
215
+ return stage("wait", async () => {
216
+ const deadline = now().getTime() + 180_000;
217
+ while (true) {
218
+ input.signal.throwIfAborted();
219
+ const current = await dependencies.api.getBatchStatus({
220
+ assetId: latest.asset_id,
221
+ fallback: latest,
222
+ access: input.access,
223
+ signal: input.signal
224
+ });
225
+ if (current !== null)
226
+ latest = current;
227
+ if (terminalAnalysisStatus(latest.analysis_status))
228
+ return latest;
229
+ const remaining = deadline - now().getTime();
230
+ if (remaining <= 0) {
231
+ dependencies.warn("素材分析仍在处理中,已返回当前结果。");
232
+ return latest;
233
+ }
234
+ await sleep(Math.min(3000, remaining), input.signal);
211
235
  }
212
- await sleep(Math.min(3000, remaining), input.signal);
213
- }
236
+ });
214
237
  }
215
238
  catch (error) {
216
239
  if (input.signal.aborted)
@@ -62,57 +62,63 @@ export function createDamUseCases(dependencies) {
62
62
  async list({ input, access, signal }) {
63
63
  signal.throwIfAborted();
64
64
  assertPageSize(input.pageSize);
65
- const repositories = await resolveRepositories(input, access, signal);
66
- const result = await dependencies.api.search({
67
- ...searchRequest(input, "", repositories.map((repository) => repository.id)),
68
- access,
69
- signal
65
+ const repositories = await dependencies.telemetry.stage("repository", () => (resolveRepositories(input, access, signal)));
66
+ return dependencies.telemetry.stage("request", async () => {
67
+ const result = await dependencies.api.search({
68
+ ...searchRequest(input, "", repositories.map((repository) => repository.id)),
69
+ access,
70
+ signal
71
+ });
72
+ signal.throwIfAborted();
73
+ validate.list(result);
74
+ return result;
70
75
  });
71
- signal.throwIfAborted();
72
- validate.list(result);
73
- return result;
74
76
  },
75
77
  async search({ input, access, signal }) {
76
78
  signal.throwIfAborted();
77
79
  if (!nonblank(input.query))
78
80
  invalid();
79
81
  assertPageSize(input.pageSize);
80
- const repositories = await resolveRepositories(input, access, signal);
81
- const result = await dependencies.api.search({
82
- ...searchRequest(input, input.query, repositories.map((repository) => repository.id)),
83
- access,
84
- signal
82
+ const repositories = await dependencies.telemetry.stage("repository", () => (resolveRepositories(input, access, signal)));
83
+ return dependencies.telemetry.stage("request", async () => {
84
+ const result = await dependencies.api.search({
85
+ ...searchRequest(input, input.query, repositories.map((repository) => repository.id)),
86
+ access,
87
+ signal
88
+ });
89
+ signal.throwIfAborted();
90
+ validate.search(result);
91
+ return result;
85
92
  });
86
- signal.throwIfAborted();
87
- validate.search(result);
88
- return result;
89
93
  },
90
94
  async get({ input, access, signal }) {
91
95
  signal.throwIfAborted();
92
96
  assertAssetId(input.asset_id);
93
- const result = await dependencies.api.getAsset({
94
- assetId: input.asset_id,
95
- access,
96
- signal
97
+ return dependencies.telemetry.stage("request", async () => {
98
+ const result = await dependencies.api.getAsset({
99
+ assetId: input.asset_id,
100
+ access,
101
+ signal
102
+ });
103
+ signal.throwIfAborted();
104
+ validate.get(result);
105
+ return result;
97
106
  });
98
- signal.throwIfAborted();
99
- validate.get(result);
100
- return result;
101
107
  },
102
108
  async upload({ input, access, signal }) {
103
109
  signal.throwIfAborted();
104
110
  if (!nonblank(input.file))
105
111
  invalid();
106
- const file = await dependencies.storage.inspect({
112
+ const file = await dependencies.telemetry.stage("inspect", () => (dependencies.storage.inspect({
107
113
  url: pathToFileURL(resolve(process.cwd(), input.file)).href,
108
114
  ...(input.allowSensitivePath === undefined
109
115
  ? {}
110
116
  : { allowSensitivePath: input.allowSensitivePath })
111
- }, signal);
117
+ }, signal)));
112
118
  signal.throwIfAborted();
113
- const [repository] = await resolveRepositories({
119
+ const [repository] = await dependencies.telemetry.stage("repository", () => (resolveRepositories({
114
120
  ...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
115
- }, access, signal);
121
+ }, access, signal)));
116
122
  if (repository === undefined)
117
123
  throw new RemoteRequestError();
118
124
  const result = await dependencies.uploader.upload({
@@ -129,9 +135,9 @@ export function createDamUseCases(dependencies) {
129
135
  async delete({ input, access, signal }) {
130
136
  signal.throwIfAborted();
131
137
  assertAssetId(input.asset_id);
132
- const [repository] = await resolveRepositories({
138
+ const [repository] = await dependencies.telemetry.stage("repository", () => (resolveRepositories({
133
139
  ...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
134
- }, access, signal);
140
+ }, access, signal)));
135
141
  if (repository === undefined)
136
142
  throw new RemoteRequestError();
137
143
  const request = {
@@ -140,10 +146,11 @@ export function createDamUseCases(dependencies) {
140
146
  access,
141
147
  signal
142
148
  };
143
- await dependencies.api.recycle(request);
149
+ await dependencies.telemetry.stage("recycle", () => dependencies.api.recycle(request));
144
150
  signal.throwIfAborted();
145
- if (input.permanent === true)
146
- await dependencies.api.deleteRecycled(request);
151
+ if (input.permanent === true) {
152
+ await dependencies.telemetry.stage("delete", () => dependencies.api.deleteRecycled(request));
153
+ }
147
154
  signal.throwIfAborted();
148
155
  validate.delete(null);
149
156
  }
@@ -38,7 +38,11 @@ export function createOrgUseCases(dependencies) {
38
38
  }
39
39
  return {
40
40
  async list(input) {
41
- const records = await dependencies.org.list(input.state.credential, input.signal);
41
+ dependencies.telemetry.annotate({
42
+ account_id: input.state.account?.id,
43
+ organization_id: input.state.organization?.id
44
+ });
45
+ const records = await dependencies.telemetry.stage("list", () => dependencies.org.list(input.state.credential, input.signal));
42
46
  return {
43
47
  orgs: records.map((record) => ({
44
48
  id: record.id,
@@ -53,7 +57,13 @@ export function createOrgUseCases(dependencies) {
53
57
  };
54
58
  },
55
59
  async current() {
56
- const state = await dependencies.store.read();
60
+ const state = await dependencies.telemetry.stage("state", () => dependencies.store.read());
61
+ if (state) {
62
+ dependencies.telemetry.annotate({
63
+ account_id: state.account?.id,
64
+ organization_id: state.organization?.id
65
+ });
66
+ }
57
67
  return {
58
68
  organization: state?.organization
59
69
  ? { id: state.organization.id, name: state.organization.name }
@@ -65,15 +75,21 @@ export function createOrgUseCases(dependencies) {
65
75
  },
66
76
  async switchOrganization(input) {
67
77
  input.signal.throwIfAborted();
68
- const records = await dependencies.org.list(input.state.credential, input.signal);
69
- const selectedId = input.organizationId === undefined
70
- ? await selectOrganization(records, input.select)
71
- : requiredSelection(input.organizationId);
72
- const record = records.find((candidate) => candidate.id === selectedId);
73
- if (!record)
74
- throw new OrganizationInvalidError();
75
- const organization = await bind(input.state.credential, record, input.signal);
76
- await dependencies.store.write({ ...input.state, organization });
78
+ dependencies.telemetry.annotate({ account_id: input.state.account?.id });
79
+ const records = await dependencies.telemetry.stage("list", () => dependencies.org.list(input.state.credential, input.signal));
80
+ const { record, selectedId } = await dependencies.telemetry.stage("select", async () => {
81
+ const selectedId = input.organizationId === undefined
82
+ ? await selectOrganization(records, input.select)
83
+ : requiredSelection(input.organizationId);
84
+ const record = records.find((candidate) => candidate.id === selectedId);
85
+ if (!record)
86
+ throw new OrganizationInvalidError();
87
+ return { record, selectedId };
88
+ });
89
+ dependencies.telemetry.annotate({ parameters: { organizationId: selectedId } });
90
+ const organization = await dependencies.telemetry.stage("bind", () => bind(input.state.credential, record, input.signal));
91
+ await dependencies.telemetry.stage("state", () => dependencies.store.write({ ...input.state, organization }));
92
+ dependencies.telemetry.annotate({ organization_id: organization.id });
77
93
  return record;
78
94
  },
79
95
  async bindAuthorizedOrganization(input) {
@@ -56,10 +56,14 @@ export function projectModelList(catalog, tool) {
56
56
  .map(summary)
57
57
  };
58
58
  }
59
- export function projectModelDetail(catalog, model) {
59
+ export function findModel(catalog, model) {
60
60
  const found = catalog.models.find((candidate) => candidate.model === model);
61
61
  if (found === undefined)
62
62
  throw new ToolInputError();
63
+ return found;
64
+ }
65
+ export function projectModelDetail(catalog, model) {
66
+ const found = findModel(catalog, model);
63
67
  return {
64
68
  ...summary(found),
65
69
  ...(found.usageDescription === undefined
@@ -72,8 +76,8 @@ export function projectModelDetail(catalog, model) {
72
76
  };
73
77
  }
74
78
  export function findToolModel(catalog, tool, model) {
75
- const found = catalog.models.find((candidate) => candidate.tool === tool && candidate.model === model);
76
- if (found === undefined)
79
+ const found = findModel(catalog, model);
80
+ if (found.tool !== tool)
77
81
  throw new ToolInputError();
78
82
  return found;
79
83
  }
@@ -11,6 +11,8 @@ function parameterSchema(parameter) {
11
11
  if (parameter.type === "uri[]") {
12
12
  return { type: "array", items: { type: "string", format: "uri" }, ...common };
13
13
  }
14
+ if (parameter.type === "number")
15
+ return { type: "number", ...common };
14
16
  return {
15
17
  type: "string",
16
18
  ...(parameter.options === undefined
@@ -67,29 +69,37 @@ export function buildToolArgumentsSchema(catalog, tool) {
67
69
  additionalProperties: false
68
70
  };
69
71
  }
70
- const ajv = new Ajv2020({ allErrors: true, strict: true });
71
- const addFormats = addFormatsImport;
72
- addFormats(ajv);
73
- function validator(model) {
72
+ export function buildModelArgumentsSchema(model) {
74
73
  const properties = {
75
- model: { type: "string" }
74
+ model: {
75
+ type: "string",
76
+ const: model.model,
77
+ description: "要调用的模型机器标识。"
78
+ }
76
79
  };
77
80
  for (const parameter of model.parameters) {
78
81
  properties[parameter.name] = parameterSchema(parameter);
79
82
  }
80
- return ajv.compile({
83
+ return {
81
84
  type: "object",
82
85
  properties,
83
86
  required: [
84
87
  "model",
85
- ...model.parameters.filter((parameter) => parameter.required).map((parameter) => parameter.name)
88
+ ...model.parameters
89
+ .filter((parameter) => parameter.required)
90
+ .map((parameter) => parameter.name)
86
91
  ],
87
92
  additionalProperties: false
88
- });
93
+ };
94
+ }
95
+ const ajv = new Ajv2020({ allErrors: true, strict: true });
96
+ const addFormats = addFormatsImport;
97
+ addFormats(ajv);
98
+ function validator(model) {
99
+ return ajv.compile(buildModelArgumentsSchema(model));
89
100
  }
90
101
  export function assertModelArguments(model, value) {
91
102
  if (typeof value !== "object" || value === null
92
- || value.model !== model.model
93
103
  || !validator(model)(value)) {
94
104
  throw new ToolInputError();
95
105
  }
@@ -18,6 +18,7 @@ const toolOrder = {
18
18
  };
19
19
  const multiUriKeys = new Set([
20
20
  "image_urls",
21
+ "reference_images",
21
22
  "file_urls",
22
23
  "video_urls",
23
24
  "audio_urls"
@@ -98,9 +99,11 @@ function parameter(value) {
98
99
  ? "uri[]"
99
100
  : name === "start_frame" || name === "end_frame" || sourceType === "IMAGE"
100
101
  ? "uri"
101
- : sourceType === "TEXT" || sourceType === "INPUT" || sourceType === "SELECT"
102
- ? "string"
103
- : undefined;
102
+ : sourceType === "NUMBER"
103
+ ? "number"
104
+ : sourceType === "TEXT" || sourceType === "INPUT" || sourceType === "SELECT"
105
+ ? "string"
106
+ : undefined;
104
107
  if (type === undefined)
105
108
  throw new Error("unsupported parameter");
106
109
  let options;
@@ -120,7 +123,7 @@ function parameter(value) {
120
123
  if (options.length === 0)
121
124
  throw new Error("empty options");
122
125
  }
123
- const description = text(field.label) ?? text(field.description);
126
+ const description = text(field.description) ?? text(field.label);
124
127
  const defaultValue = field.default_value;
125
128
  const hasDefault = defaultValue !== undefined
126
129
  && defaultValue !== null
@@ -138,7 +138,7 @@ export function createToolApiAdapter(dependencies) {
138
138
  async execute({ invocation, access, signal }) {
139
139
  signal.throwIfAborted();
140
140
  const managedContentId = contentId(createContentId());
141
- const intent = parseIntent(await safePost(dependencies.transport, {
141
+ const intent = await dependencies.telemetry.stage("intent", async () => parseIntent(await safePost(dependencies.transport, {
142
142
  path: "/ai-agent/v1/tool-intent",
143
143
  body: {
144
144
  prompt: invocation.prompt,
@@ -148,9 +148,9 @@ export function createToolApiAdapter(dependencies) {
148
148
  credential: access.credential,
149
149
  organizationId: access.organizationId,
150
150
  signal
151
- }));
151
+ })));
152
152
  signal.throwIfAborted();
153
- const taskId = submittedTask(await safePost(dependencies.transport, {
153
+ const taskId = await dependencies.telemetry.stage("submit", async () => submittedTask(await safePost(dependencies.transport, {
154
154
  path: "/gdesign/tool/v1/dify/call_async",
155
155
  body: {
156
156
  jsonrpc: "2.0",
@@ -164,37 +164,39 @@ export function createToolApiAdapter(dependencies) {
164
164
  credential: access.credential,
165
165
  organizationId: access.organizationId,
166
166
  signal
167
- }));
168
- let transientFailures = 0;
169
- while (true) {
170
- signal.throwIfAborted();
171
- let response;
172
- try {
173
- response = await safePost(dependencies.transport, {
174
- path: "/gdesign/tool/v1/dify/process/batch",
175
- body: { task_ids: [taskId] },
176
- credential: access.credential,
177
- organizationId: access.organizationId,
178
- signal
179
- });
180
- transientFailures = 0;
181
- }
182
- catch (error) {
183
- if (signal.aborted)
184
- throw signal.reason;
185
- if (!(error instanceof RemoteRequestError) || !error.transient)
186
- throw error;
187
- transientFailures += 1;
188
- if (transientFailures > 3)
189
- throw error;
190
- await sleep(transientFailures * 1_000, signal);
191
- continue;
167
+ })));
168
+ return dependencies.telemetry.stage("wait", async () => {
169
+ let transientFailures = 0;
170
+ while (true) {
171
+ signal.throwIfAborted();
172
+ let response;
173
+ try {
174
+ response = await safePost(dependencies.transport, {
175
+ path: "/gdesign/tool/v1/dify/process/batch",
176
+ body: { task_ids: [taskId] },
177
+ credential: access.credential,
178
+ organizationId: access.organizationId,
179
+ signal
180
+ });
181
+ transientFailures = 0;
182
+ }
183
+ catch (error) {
184
+ if (signal.aborted)
185
+ throw signal.reason;
186
+ if (!(error instanceof RemoteRequestError) || !error.transient)
187
+ throw error;
188
+ transientFailures += 1;
189
+ if (transientFailures > 3)
190
+ throw error;
191
+ await sleep(transientFailures * 1_000, signal);
192
+ continue;
193
+ }
194
+ const result = pollResult(response, taskId);
195
+ if (result !== undefined)
196
+ return result;
197
+ await sleep(2_000, signal);
192
198
  }
193
- const result = pollResult(response, taskId);
194
- if (result !== undefined)
195
- return result;
196
- await sleep(2_000, signal);
197
- }
199
+ });
198
200
  }
199
201
  };
200
202
  }