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.
- package/README.md +3 -2
- package/contracts/operations/agent.send/input.schema.json +4 -0
- package/contracts/operations/model.get/output.schema.json +1 -0
- package/dist/bin/gd-cli.js +3 -1
- package/dist/src/bootstrap/create-cli.js +36 -3
- package/dist/src/bootstrap/create-runtime.js +32 -7
- package/dist/src/cli/action-binding.js +43 -9
- package/dist/src/cli/agent-commands.js +13 -3
- package/dist/src/cli/auth-commands.js +21 -13
- package/dist/src/cli/dam-commands.js +35 -20
- package/dist/src/cli/errors.js +19 -1
- package/dist/src/cli/model-commands.js +41 -11
- package/dist/src/cli/org-commands.js +18 -12
- package/dist/src/cli/presenter.js +11 -1
- package/dist/src/cli/tool-commands.js +19 -7
- package/dist/src/cli/update-command.js +6 -4
- package/dist/src/features/agent/creative-agent-adapter.js +19 -14
- package/dist/src/features/agent/creative-protocol.js +7 -3
- package/dist/src/features/agent/use-cases.js +63 -54
- package/dist/src/features/auth/sso-service.js +17 -2
- package/dist/src/features/auth/use-cases.js +82 -52
- package/dist/src/features/dam/asset-projection.js +22 -14
- package/dist/src/features/dam/dam-api-adapter.js +19 -4
- package/dist/src/features/dam/object-storage.js +2 -0
- package/dist/src/features/dam/registered-uploader.js +58 -35
- package/dist/src/features/dam/use-cases.js +39 -32
- package/dist/src/features/org/use-cases.js +27 -11
- package/dist/src/features/tool/catalog.js +7 -3
- package/dist/src/features/tool/dynamic-schema.js +19 -9
- package/dist/src/features/tool/mns-catalog-adapter.js +7 -4
- package/dist/src/features/tool/tool-api-adapter.js +35 -33
- package/dist/src/features/tool/use-cases.js +68 -36
- package/dist/src/features/update/update-service.js +28 -22
- package/dist/src/platform/json-input.js +3 -0
- package/dist/src/platform/remote-protocol.js +6 -0
- package/dist/src/telemetry/invocation.js +128 -0
- package/dist/src/telemetry/sls-sink.js +31 -0
- package/package.json +1 -1
- package/skills/gd-cli/references/creation.md +4 -2
- 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.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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.
|
|
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({
|
|
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.
|
|
19
|
-
|
|
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", "
|
|
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.
|
|
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.
|
|
63
|
-
|
|
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.
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 {
|
|
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
|
|
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) =>
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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: "
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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({
|
|
@@ -24,11 +24,11 @@ export function createSsoService(options) {
|
|
|
24
24
|
const baseUrl = new URL(options.ssoApi);
|
|
25
25
|
if (!baseUrl.pathname.endsWith("/"))
|
|
26
26
|
baseUrl.pathname += "/";
|
|
27
|
-
async function request(relativeUrl, signal) {
|
|
27
|
+
async function request(relativeUrl, signal, method = "POST") {
|
|
28
28
|
signal.throwIfAborted();
|
|
29
29
|
try {
|
|
30
30
|
return await options.fetch(new URL(relativeUrl, baseUrl), {
|
|
31
|
-
method
|
|
31
|
+
method,
|
|
32
32
|
headers: { Accept: "application/json" },
|
|
33
33
|
signal: AbortSignal.any([
|
|
34
34
|
signal,
|
|
@@ -98,6 +98,21 @@ export function createSsoService(options) {
|
|
|
98
98
|
result.organizationId = organizationId;
|
|
99
99
|
return result;
|
|
100
100
|
},
|
|
101
|
+
async getIdentity(accessKey, signal) {
|
|
102
|
+
const query = new URLSearchParams({ ak: accessKey });
|
|
103
|
+
const response = await request(`oauth/device/identity?${query}`, signal, "GET");
|
|
104
|
+
if (!response.ok)
|
|
105
|
+
throw new SsoRequestError(response.status);
|
|
106
|
+
const body = await responseRecord(response);
|
|
107
|
+
const result = {};
|
|
108
|
+
const userId = identifier(body.userId);
|
|
109
|
+
if (userId)
|
|
110
|
+
result.account = { id: userId, name: userId };
|
|
111
|
+
const organizationId = identifier(body.orgId);
|
|
112
|
+
if (organizationId)
|
|
113
|
+
result.organizationId = organizationId;
|
|
114
|
+
return result;
|
|
115
|
+
},
|
|
101
116
|
async bindOrganization(input, signal) {
|
|
102
117
|
const timestamp = Math.floor(options.now().getTime() / 1000);
|
|
103
118
|
const query = new URLSearchParams({
|
|
@@ -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,73 +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.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
const token = await dependencies.telemetry.stage("wait", async () => {
|
|
73
|
+
while (true) {
|
|
74
|
+
if (dependencies.now().getTime() + interval > deadline) {
|
|
75
|
+
throw new DeviceAuthorizationExpiredError();
|
|
76
|
+
}
|
|
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;
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
64
87
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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 });
|
|
73
104
|
}
|
|
74
|
-
|
|
75
|
-
|
|
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({
|
|
76
116
|
credential: token.credential,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (!
|
|
81
|
-
input.interaction.warn("
|
|
82
|
-
return { status: "logged_in", organizationSelected: false };
|
|
83
|
-
}
|
|
84
|
-
try {
|
|
85
|
-
const organization = await dependencies.organizationBinder.bindAuthorizedOrganization({
|
|
86
|
-
credential: token.credential,
|
|
87
|
-
organizationId: token.organizationId,
|
|
88
|
-
signal: input.signal
|
|
89
|
-
});
|
|
90
|
-
if (!organization) {
|
|
91
|
-
input.interaction.warn("登录成功,但未能选择组织;请执行 gd-cli org switch。");
|
|
92
|
-
return { status: "logged_in", organizationSelected: false };
|
|
93
|
-
}
|
|
94
|
-
await dependencies.store.write({ ...state, organization });
|
|
95
|
-
return { status: "logged_in", organizationSelected: true };
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
if (input.signal.aborted)
|
|
99
|
-
throw input.signal.reason;
|
|
100
|
-
input.interaction.warn("登录成功,但组织绑定失败;请执行 gd-cli org switch。");
|
|
117
|
+
organizationId,
|
|
118
|
+
signal: input.signal
|
|
119
|
+
})));
|
|
120
|
+
if (!organization) {
|
|
121
|
+
input.interaction.warn("登录成功,但未能选择组织;请执行 gd-cli org switch。");
|
|
101
122
|
return { status: "logged_in", organizationSelected: false };
|
|
102
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 };
|
|
103
133
|
}
|
|
104
134
|
}
|
|
105
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
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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 =
|
|
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);
|