gaoding-cli 1.0.0-alpha.6 → 1.0.0-alpha.8
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/CHANGELOG.md +26 -0
- package/README.md +18 -14
- package/dist/src/api/app-runner.js +1 -2
- package/dist/src/api/creative-client.d.ts +34 -0
- package/dist/src/api/creative-client.js +184 -0
- package/dist/src/api/direct-client.d.ts +9 -2
- package/dist/src/api/direct-client.js +45 -18
- package/dist/src/api/hub-client.d.ts +11 -8
- package/dist/src/api/hub-client.js +17 -18
- package/dist/src/api/usage-client.d.ts +4 -4
- package/dist/src/api/usage-client.js +9 -49
- package/dist/src/auth/credential-store-v2.d.ts +0 -1
- package/dist/src/auth/credential-store-v2.js +0 -6
- package/dist/src/commands/auth.js +13 -13
- package/dist/src/commands/creative.d.ts +8 -0
- package/dist/src/commands/creative.js +186 -0
- package/dist/src/commands/dam.js +27 -22
- package/dist/src/commands/org.js +20 -14
- package/dist/src/commands/skills.js +9 -4
- package/dist/src/commands/update.js +6 -2
- package/dist/src/commands/usage.d.ts +1 -1
- package/dist/src/commands/usage.js +6 -4
- package/dist/src/commands/workflows.js +31 -13
- package/dist/src/core/auth/auth-guard.d.ts +21 -0
- package/dist/src/core/auth/auth-guard.js +121 -0
- package/dist/src/core/auth/org-manager.d.ts +2 -1
- package/dist/src/core/auth/org-manager.js +61 -6
- package/dist/src/creative/contract.d.ts +1112 -0
- package/dist/src/creative/contract.js +743 -0
- package/dist/src/creative/media.d.ts +19 -0
- package/dist/src/creative/media.js +83 -0
- package/dist/src/creative/protocol.d.ts +60 -0
- package/dist/src/creative/protocol.js +337 -0
- package/dist/src/middleware/auth.d.ts +6 -2
- package/dist/src/middleware/auth.js +25 -63
- package/dist/src/middleware/error-handler.d.ts +2 -0
- package/dist/src/middleware/error-handler.js +3 -4
- package/dist/src/program.js +7 -89
- package/dist/src/registry/agent-contracts.d.ts +3 -11
- package/dist/src/registry/agent-contracts.js +5 -243
- package/dist/src/registry/direct-registry.d.ts +0 -5
- package/dist/src/registry/direct-registry.js +0 -19
- package/dist/src/registry/registry-client.d.ts +0 -21
- package/dist/src/registry/registry-client.js +1 -40
- package/dist/src/utils/config.d.ts +2 -2
- package/dist/src/utils/config.js +1 -1
- package/dist/src/utils/help-verify.d.ts +1 -3
- package/dist/src/utils/help-verify.js +1 -25
- package/package.json +3 -3
- package/skills/gd-cli/SKILL.md +10 -9
- package/skills/gd-cli/references/auth.md +6 -0
- package/skills/gd-cli/references/creative.md +47 -0
- package/skills/gd-cli/references/entitlement.md +2 -2
- package/skills/gd-cli/references/errors.md +7 -0
- package/skills/gd-cli/references/sop.md +4 -4
- package/skills/gd-cli/references/workflows.md +1 -1
- package/dist/src/commands/models.d.ts +0 -2
- package/dist/src/commands/models.js +0 -78
- package/dist/src/commands/tools.d.ts +0 -4
- package/dist/src/commands/tools.js +0 -383
- package/skills/gd-cli/references/tools.md +0 -29
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { UploadedAsset } from "../api/dam-client.js";
|
|
2
|
+
import type { CreativeMedia } from "./contract.js";
|
|
3
|
+
export interface PreparedCreativeMedia {
|
|
4
|
+
source: string;
|
|
5
|
+
url: string;
|
|
6
|
+
role: CreativeMedia["role"];
|
|
7
|
+
mime_type: string;
|
|
8
|
+
name: string;
|
|
9
|
+
from_user_upload: boolean;
|
|
10
|
+
}
|
|
11
|
+
export type CreativeMediaUploader = (file: string, options: {
|
|
12
|
+
register?: boolean;
|
|
13
|
+
}) => Promise<UploadedAsset>;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve public media references into the URL-shaped payload expected by the
|
|
16
|
+
* Editor chat endpoint. Local files are uploaded as transient storage only;
|
|
17
|
+
* they are deliberately not registered as DAM assets.
|
|
18
|
+
*/
|
|
19
|
+
export declare function prepareCreativeMedia(media: CreativeMedia[] | undefined, upload: CreativeMediaUploader): Promise<PreparedCreativeMedia[]>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { basename, extname } from "node:path";
|
|
2
|
+
import { assertSafeHttpUrl } from "../utils/url-safety.js";
|
|
3
|
+
/**
|
|
4
|
+
* Resolve public media references into the URL-shaped payload expected by the
|
|
5
|
+
* Editor chat endpoint. Local files are uploaded as transient storage only;
|
|
6
|
+
* they are deliberately not registered as DAM assets.
|
|
7
|
+
*/
|
|
8
|
+
export async function prepareCreativeMedia(media = [], upload) {
|
|
9
|
+
const prepared = [];
|
|
10
|
+
for (const item of media) {
|
|
11
|
+
if (isRemoteSource(item.source)) {
|
|
12
|
+
const url = assertSafeHttpUrl(item.source, { context: "Creative media" });
|
|
13
|
+
prepared.push({
|
|
14
|
+
source: item.source,
|
|
15
|
+
url,
|
|
16
|
+
role: item.role,
|
|
17
|
+
mime_type: inferMimeType(item.source),
|
|
18
|
+
name: inferName(item.source),
|
|
19
|
+
from_user_upload: false,
|
|
20
|
+
});
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const uploaded = await upload(item.source, { register: false });
|
|
24
|
+
prepared.push({
|
|
25
|
+
source: item.source,
|
|
26
|
+
url: uploaded.url,
|
|
27
|
+
role: item.role,
|
|
28
|
+
mime_type: uploaded.mime_type || inferMimeType(uploaded.filename || item.source),
|
|
29
|
+
name: uploaded.filename || inferName(item.source),
|
|
30
|
+
from_user_upload: true,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return prepared;
|
|
34
|
+
}
|
|
35
|
+
function isRemoteSource(source) {
|
|
36
|
+
return /^https?:\/\//i.test(source);
|
|
37
|
+
}
|
|
38
|
+
function inferName(source) {
|
|
39
|
+
try {
|
|
40
|
+
if (isRemoteSource(source)) {
|
|
41
|
+
const url = new URL(source);
|
|
42
|
+
const value = basename(url.pathname);
|
|
43
|
+
return value || "creative-media";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Input validation already rejects malformed URLs. Keep a safe fallback
|
|
48
|
+
// here for callers that use this helper independently.
|
|
49
|
+
}
|
|
50
|
+
return basename(source) || "creative-media";
|
|
51
|
+
}
|
|
52
|
+
function inferMimeType(source) {
|
|
53
|
+
const extension = extname(source.split("?")[0]?.split("#")[0] ?? "")
|
|
54
|
+
.slice(1)
|
|
55
|
+
.toLowerCase();
|
|
56
|
+
const mime = MIME_BY_EXTENSION[extension];
|
|
57
|
+
return mime ?? "application/octet-stream";
|
|
58
|
+
}
|
|
59
|
+
const MIME_BY_EXTENSION = {
|
|
60
|
+
aac: "audio/aac",
|
|
61
|
+
avif: "image/avif",
|
|
62
|
+
bmp: "image/bmp",
|
|
63
|
+
flac: "audio/flac",
|
|
64
|
+
gif: "image/gif",
|
|
65
|
+
heic: "image/heic",
|
|
66
|
+
heif: "image/heif",
|
|
67
|
+
jpeg: "image/jpeg",
|
|
68
|
+
jpg: "image/jpeg",
|
|
69
|
+
m4a: "audio/mp4",
|
|
70
|
+
m4v: "video/x-m4v",
|
|
71
|
+
mkv: "video/x-matroska",
|
|
72
|
+
mov: "video/quicktime",
|
|
73
|
+
mp3: "audio/mpeg",
|
|
74
|
+
mp4: "video/mp4",
|
|
75
|
+
oga: "audio/ogg",
|
|
76
|
+
ogg: "audio/ogg",
|
|
77
|
+
opus: "audio/opus",
|
|
78
|
+
png: "image/png",
|
|
79
|
+
svg: "image/svg+xml",
|
|
80
|
+
wav: "audio/wav",
|
|
81
|
+
webm: "video/webm",
|
|
82
|
+
webp: "image/webp",
|
|
83
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { CreativeInput } from "./contract.js";
|
|
2
|
+
import type { PreparedCreativeMedia } from "./media.js";
|
|
3
|
+
export interface CreativeRequestIds {
|
|
4
|
+
local_thread_id: string;
|
|
5
|
+
local_message_id: string;
|
|
6
|
+
}
|
|
7
|
+
export interface CreativeAgentAttachment {
|
|
8
|
+
uri: string;
|
|
9
|
+
mime_type: string;
|
|
10
|
+
from_user_upload: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface CreativeAgentPromptText {
|
|
13
|
+
type: "text";
|
|
14
|
+
content: string;
|
|
15
|
+
}
|
|
16
|
+
export interface CreativeAgentPromptMedia {
|
|
17
|
+
type: "media";
|
|
18
|
+
url: string;
|
|
19
|
+
name: string;
|
|
20
|
+
mime: string;
|
|
21
|
+
}
|
|
22
|
+
export type CreativeAgentPromptItem = CreativeAgentPromptText | CreativeAgentPromptMedia;
|
|
23
|
+
export interface CreativeAgentContent {
|
|
24
|
+
type: "plain" | "user";
|
|
25
|
+
text?: string;
|
|
26
|
+
prompt?: CreativeAgentPromptItem[];
|
|
27
|
+
parameters?: Record<string, unknown>;
|
|
28
|
+
answer?: {
|
|
29
|
+
answers: string[][];
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export interface CreativeAgentRequest {
|
|
33
|
+
content: CreativeAgentContent;
|
|
34
|
+
name: "user" | "ask_user_question";
|
|
35
|
+
role: "user";
|
|
36
|
+
local_thread_id: string;
|
|
37
|
+
local_message_id: string;
|
|
38
|
+
thread_id: string;
|
|
39
|
+
attachments: CreativeAgentAttachment[];
|
|
40
|
+
extra: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Adapt the public Creative contract to the request shape emitted by the
|
|
44
|
+
* Editor chat sender. No model, skill, or tool identifier is selected here.
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildCreativeAgentRequest(input: CreativeInput, media: PreparedCreativeMedia[] | undefined, ids: CreativeRequestIds): CreativeAgentRequest;
|
|
47
|
+
export interface CreativeAgentMessage {
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
message_id?: string;
|
|
50
|
+
messageId?: string;
|
|
51
|
+
thread_id?: string;
|
|
52
|
+
threadId?: string;
|
|
53
|
+
role?: string;
|
|
54
|
+
name?: string;
|
|
55
|
+
status?: string;
|
|
56
|
+
event?: string;
|
|
57
|
+
content?: unknown;
|
|
58
|
+
extra?: Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
export declare function normalizeCreativeMessages(messages: CreativeAgentMessage[], fallbackConversationId?: string): import("./contract.js").CreativeResult;
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { CliUsageError } from "../core/output/cli-error.js";
|
|
2
|
+
/**
|
|
3
|
+
* Adapt the public Creative contract to the request shape emitted by the
|
|
4
|
+
* Editor chat sender. No model, skill, or tool identifier is selected here.
|
|
5
|
+
*/
|
|
6
|
+
export function buildCreativeAgentRequest(input, media = [], ids) {
|
|
7
|
+
const promptItems = [];
|
|
8
|
+
if (input.prompt !== undefined) {
|
|
9
|
+
promptItems.push({ type: "text", content: input.prompt });
|
|
10
|
+
}
|
|
11
|
+
for (const item of media) {
|
|
12
|
+
// The Editor's first/last-frame plugin serializes the semantic role as a
|
|
13
|
+
// text prefix before the media node; role is not a completion media field.
|
|
14
|
+
if (item.role === "first_frame" || item.role === "last_frame") {
|
|
15
|
+
promptItems.push({
|
|
16
|
+
type: "text",
|
|
17
|
+
content: item.role === "first_frame" ? "首帧" : "尾帧",
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
promptItems.push({
|
|
21
|
+
type: "media",
|
|
22
|
+
url: item.url,
|
|
23
|
+
name: item.name,
|
|
24
|
+
mime: item.mime_type,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const isAnswer = Array.isArray(input.answers);
|
|
28
|
+
const content = isAnswer
|
|
29
|
+
? buildAnswerContent(input, promptItems)
|
|
30
|
+
: buildCreationContent(input, promptItems);
|
|
31
|
+
const extra = {};
|
|
32
|
+
if (input.conversation_id && input.last_tool_message_id) {
|
|
33
|
+
extra.lastToolMessageId = input.last_tool_message_id;
|
|
34
|
+
extra.last_tool_message_id = input.last_tool_message_id;
|
|
35
|
+
}
|
|
36
|
+
if (isAnswer) {
|
|
37
|
+
const questions = input.answers.map(toEditorAnswer);
|
|
38
|
+
extra.askUserQuestionAnsweredQuestions = questions;
|
|
39
|
+
extra.questions = questions;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
content,
|
|
43
|
+
name: isAnswer ? "ask_user_question" : "user",
|
|
44
|
+
role: "user",
|
|
45
|
+
local_thread_id: input.conversation_id ? "" : ids.local_thread_id,
|
|
46
|
+
local_message_id: ids.local_message_id,
|
|
47
|
+
thread_id: input.conversation_id ?? "",
|
|
48
|
+
attachments: media.map((item) => ({
|
|
49
|
+
uri: item.url,
|
|
50
|
+
mime_type: item.mime_type,
|
|
51
|
+
from_user_upload: item.from_user_upload,
|
|
52
|
+
})),
|
|
53
|
+
extra,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function buildCreationContent(input, promptItems) {
|
|
57
|
+
const parameters = input.parameters
|
|
58
|
+
? toEditorParameters(input.parameters)
|
|
59
|
+
: undefined;
|
|
60
|
+
// The Editor sends the compact plain-text form when no structured content
|
|
61
|
+
// exists. Keeping that form preserves the existing Agent text-chat path.
|
|
62
|
+
if (promptItems.length === 1 && promptItems[0]?.type === "text" && !parameters) {
|
|
63
|
+
return { type: "plain", text: promptItems[0].content };
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
type: "plain",
|
|
67
|
+
prompt: promptItems,
|
|
68
|
+
...(parameters ? { parameters } : {}),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function buildAnswerContent(input, promptItems) {
|
|
72
|
+
const text = input.prompt ?? "";
|
|
73
|
+
return {
|
|
74
|
+
type: "user",
|
|
75
|
+
text,
|
|
76
|
+
prompt: promptItems.length > 0 ? promptItems : [{ type: "text", content: text }],
|
|
77
|
+
...(input.parameters ? { parameters: toEditorParameters(input.parameters) } : {}),
|
|
78
|
+
answer: {
|
|
79
|
+
answers: input.answers.map((answer) => [...answer.values]),
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Keep the public contract descriptive while matching the field names used by
|
|
85
|
+
* the Editor's structured content payload. In particular, Editor serializes
|
|
86
|
+
* video duration as the string field `duration`.
|
|
87
|
+
*/
|
|
88
|
+
function toEditorParameters(parameters) {
|
|
89
|
+
return {
|
|
90
|
+
...(parameters.ratio !== undefined ? { ratio: parameters.ratio } : {}),
|
|
91
|
+
...(parameters.resolution !== undefined ? { resolution: parameters.resolution } : {}),
|
|
92
|
+
...(parameters.duration_seconds !== undefined
|
|
93
|
+
? { duration: String(parameters.duration_seconds) }
|
|
94
|
+
: {}),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function toEditorAnswer(answer) {
|
|
98
|
+
return {
|
|
99
|
+
question: answer.question,
|
|
100
|
+
answers: [...answer.values],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export function normalizeCreativeMessages(messages, fallbackConversationId = "") {
|
|
104
|
+
if (messages.length === 0) {
|
|
105
|
+
invalidAgentResponse("GD Agent 没有返回可识别的最终结果");
|
|
106
|
+
}
|
|
107
|
+
const systemError = messages.find((message) => message.event === "system_error");
|
|
108
|
+
if (systemError) {
|
|
109
|
+
const content = contentRecord(systemError.content);
|
|
110
|
+
invalidAgentResponse(stringValue(content?.text) || "GD Agent 返回系统错误");
|
|
111
|
+
}
|
|
112
|
+
const latest = new Map();
|
|
113
|
+
const anonymous = [];
|
|
114
|
+
for (const message of messages) {
|
|
115
|
+
const id = stringValue(message.message_id ?? message.messageId);
|
|
116
|
+
if (id)
|
|
117
|
+
latest.set(id, message);
|
|
118
|
+
else
|
|
119
|
+
anonymous.push(message);
|
|
120
|
+
}
|
|
121
|
+
const normalizedMessages = [...latest.values(), ...anonymous];
|
|
122
|
+
const conversationId = firstString(normalizedMessages, ["thread_id", "threadId"]) || fallbackConversationId;
|
|
123
|
+
if (!conversationId) {
|
|
124
|
+
invalidAgentResponse("GD Agent 返回结果缺少 conversation_id");
|
|
125
|
+
}
|
|
126
|
+
const ask = normalizedMessages.find((message) => isAskUserQuestion(message));
|
|
127
|
+
const outputs = [];
|
|
128
|
+
const textParts = [];
|
|
129
|
+
for (const message of normalizedMessages) {
|
|
130
|
+
const content = contentRecord(message.content);
|
|
131
|
+
const type = stringValue(content?.type);
|
|
132
|
+
if (type === "plain" || type === "user" || type === "text" || type === "system") {
|
|
133
|
+
const text = stringValue(content?.text);
|
|
134
|
+
if (text)
|
|
135
|
+
textParts.push(text);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (type === "function_response") {
|
|
139
|
+
collectFunctionResponse(content?.text, outputs, textParts);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (typeof message.content === "string" && message.content.trim()) {
|
|
143
|
+
textParts.push(message.content.trim());
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (ask) {
|
|
147
|
+
const questionData = parseAskUserQuestion(ask);
|
|
148
|
+
const lastToolMessageId = stringValue(ask.message_id ?? ask.messageId) ||
|
|
149
|
+
stringValue(ask.extra?.last_tool_message_id ?? ask.extra?.lastToolMessageId);
|
|
150
|
+
if (!lastToolMessageId) {
|
|
151
|
+
invalidAgentResponse("GD Agent 追问结果缺少追问消息标识");
|
|
152
|
+
}
|
|
153
|
+
if (questionData.questions.length === 0 ||
|
|
154
|
+
questionData.questions.some((question) => !question.question)) {
|
|
155
|
+
invalidAgentResponse("GD Agent 追问结果缺少有效问题");
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
conversation_id: conversationId,
|
|
159
|
+
finish_reason: "requires_input",
|
|
160
|
+
message: textParts.at(-1) ?? questionData.title,
|
|
161
|
+
outputs: dedupeOutputs(outputs),
|
|
162
|
+
last_tool_message_id: lastToolMessageId,
|
|
163
|
+
questions: questionData,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const textOutputs = textParts
|
|
167
|
+
.filter(Boolean)
|
|
168
|
+
.map((text) => ({ type: "text", text }));
|
|
169
|
+
return {
|
|
170
|
+
conversation_id: conversationId,
|
|
171
|
+
finish_reason: "completed",
|
|
172
|
+
message: textParts.at(-1) ?? "创作已完成",
|
|
173
|
+
outputs: dedupeOutputs([...textOutputs, ...outputs]),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function invalidAgentResponse(message) {
|
|
177
|
+
throw new CliUsageError({
|
|
178
|
+
code: "CREATIVE_RESPONSE_INVALID",
|
|
179
|
+
type: "business",
|
|
180
|
+
message,
|
|
181
|
+
hint: "请稍后重试;如果问题持续,请保留本次请求信息联系稿定支持。",
|
|
182
|
+
retryable: true,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function isAskUserQuestion(message) {
|
|
186
|
+
const content = contentRecord(message.content);
|
|
187
|
+
if (content?.type !== "function_call")
|
|
188
|
+
return false;
|
|
189
|
+
const payload = parseJson(content.text);
|
|
190
|
+
return objectRecord(payload)?.name === "ask_user_question";
|
|
191
|
+
}
|
|
192
|
+
function parseAskUserQuestion(message) {
|
|
193
|
+
const content = contentRecord(message.content);
|
|
194
|
+
const payload = objectRecord(parseJson(content?.text));
|
|
195
|
+
const args = objectRecord(parseJson(payload?.arguments)) ?? objectRecord(payload?.arguments) ?? {};
|
|
196
|
+
const questions = Array.isArray(args.questions)
|
|
197
|
+
? args.questions.map((question) => {
|
|
198
|
+
const item = objectRecord(question) ?? {};
|
|
199
|
+
const options = Array.isArray(item.options)
|
|
200
|
+
? item.options
|
|
201
|
+
.map((option) => {
|
|
202
|
+
const value = objectRecord(option);
|
|
203
|
+
const label = stringValue(value?.label);
|
|
204
|
+
return label ? { label } : null;
|
|
205
|
+
})
|
|
206
|
+
.filter((option) => option !== null)
|
|
207
|
+
: [];
|
|
208
|
+
return {
|
|
209
|
+
question: stringValue(item.question),
|
|
210
|
+
options,
|
|
211
|
+
allow_multiple: Boolean(item.allow_multiple ?? item.allowMultiple),
|
|
212
|
+
};
|
|
213
|
+
})
|
|
214
|
+
: [];
|
|
215
|
+
return {
|
|
216
|
+
title: stringValue(args.title),
|
|
217
|
+
questions,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function collectFunctionResponse(raw, outputs, textParts) {
|
|
221
|
+
const parsed = parseJson(raw);
|
|
222
|
+
const values = Array.isArray(parsed) ? parsed : parsed === undefined ? [] : [parsed];
|
|
223
|
+
for (const value of values) {
|
|
224
|
+
const record = objectRecord(value);
|
|
225
|
+
if (!record) {
|
|
226
|
+
if (typeof value === "string" && value.trim())
|
|
227
|
+
textParts.push(value.trim());
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const text = stringValue(record.text);
|
|
231
|
+
if (text && !record.uri && !record.url)
|
|
232
|
+
textParts.push(text);
|
|
233
|
+
const url = stringValue(record.uri ?? record.url);
|
|
234
|
+
if (!url) {
|
|
235
|
+
if (Object.keys(record).length > 0 && !text)
|
|
236
|
+
outputs.push({ type: "json", value });
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const mimeType = stringValue(record.mime_type ?? record.mimeType) || inferMimeType(url);
|
|
240
|
+
outputs.push({
|
|
241
|
+
type: mediaTypeFromMime(mimeType, url),
|
|
242
|
+
url,
|
|
243
|
+
mime_type: mimeType,
|
|
244
|
+
...(stringValue(record.filename ?? record.name)
|
|
245
|
+
? { name: stringValue(record.filename ?? record.name) }
|
|
246
|
+
: {}),
|
|
247
|
+
...(stringValue(record.asset_id ?? record.assetId)
|
|
248
|
+
? { asset_id: stringValue(record.asset_id ?? record.assetId) }
|
|
249
|
+
: {}),
|
|
250
|
+
...(numberValue(record.width) !== undefined ? { width: numberValue(record.width) } : {}),
|
|
251
|
+
...(numberValue(record.height) !== undefined ? { height: numberValue(record.height) } : {}),
|
|
252
|
+
...(numberValue(record.duration_seconds ?? record.duration) !== undefined
|
|
253
|
+
? { duration_seconds: numberValue(record.duration_seconds ?? record.duration) }
|
|
254
|
+
: {}),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function dedupeOutputs(outputs) {
|
|
259
|
+
const seen = new Set();
|
|
260
|
+
return outputs.filter((output) => {
|
|
261
|
+
const key = output.type === "text"
|
|
262
|
+
? `text:${output.text}`
|
|
263
|
+
: output.type === "json"
|
|
264
|
+
? `json:${JSON.stringify(output.value)}`
|
|
265
|
+
: `${output.type}:${output.url}`;
|
|
266
|
+
if (seen.has(key))
|
|
267
|
+
return false;
|
|
268
|
+
seen.add(key);
|
|
269
|
+
return true;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
function contentRecord(value) {
|
|
273
|
+
return objectRecord(value) ?? undefined;
|
|
274
|
+
}
|
|
275
|
+
function objectRecord(value) {
|
|
276
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
277
|
+
? value
|
|
278
|
+
: undefined;
|
|
279
|
+
}
|
|
280
|
+
function parseJson(value) {
|
|
281
|
+
if (typeof value !== "string")
|
|
282
|
+
return value;
|
|
283
|
+
try {
|
|
284
|
+
return JSON.parse(value);
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return undefined;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function stringValue(value) {
|
|
291
|
+
return typeof value === "string" ? value.trim() : value == null ? "" : String(value).trim();
|
|
292
|
+
}
|
|
293
|
+
function firstString(messages, keys) {
|
|
294
|
+
for (const message of messages) {
|
|
295
|
+
for (const key of keys) {
|
|
296
|
+
const value = stringValue(message[key]);
|
|
297
|
+
if (value)
|
|
298
|
+
return value;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return "";
|
|
302
|
+
}
|
|
303
|
+
function numberValue(value) {
|
|
304
|
+
const number = typeof value === "number" ? value : Number(value);
|
|
305
|
+
return Number.isFinite(number) ? number : undefined;
|
|
306
|
+
}
|
|
307
|
+
function inferMimeType(url) {
|
|
308
|
+
const extension = url.split("?")[0]?.split("#")[0]?.split(".").at(-1)?.toLowerCase();
|
|
309
|
+
return {
|
|
310
|
+
png: "image/png",
|
|
311
|
+
jpg: "image/jpeg",
|
|
312
|
+
jpeg: "image/jpeg",
|
|
313
|
+
webp: "image/webp",
|
|
314
|
+
gif: "image/gif",
|
|
315
|
+
mp4: "video/mp4",
|
|
316
|
+
webm: "video/webm",
|
|
317
|
+
mov: "video/quicktime",
|
|
318
|
+
mp3: "audio/mpeg",
|
|
319
|
+
wav: "audio/wav",
|
|
320
|
+
}[extension ?? ""] ?? "application/octet-stream";
|
|
321
|
+
}
|
|
322
|
+
function mediaTypeFromMime(mime, url) {
|
|
323
|
+
if (mime.startsWith("image/"))
|
|
324
|
+
return "image";
|
|
325
|
+
if (mime.startsWith("video/"))
|
|
326
|
+
return "video";
|
|
327
|
+
if (mime.startsWith("audio/"))
|
|
328
|
+
return "audio";
|
|
329
|
+
const inferred = inferMimeType(url);
|
|
330
|
+
if (inferred.startsWith("image/"))
|
|
331
|
+
return "image";
|
|
332
|
+
if (inferred.startsWith("video/"))
|
|
333
|
+
return "video";
|
|
334
|
+
if (inferred.startsWith("audio/"))
|
|
335
|
+
return "audio";
|
|
336
|
+
return "file";
|
|
337
|
+
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
type
|
|
2
|
-
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { type AuthGuardLike, type AuthRequirement } from "../core/auth/auth-guard.js";
|
|
3
|
+
type AuthRequirementResolver = AuthRequirement | ((command: Command) => AuthRequirement);
|
|
4
|
+
export declare function setAuthRequirement<T extends Command>(command: T, requirement: AuthRequirementResolver): T;
|
|
5
|
+
export declare function authRequirementFor(command: Command): AuthRequirement;
|
|
6
|
+
export declare function installAuthPreflight(program: Command, guard?: AuthGuardLike): void;
|
|
3
7
|
export {};
|
|
@@ -1,5 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { AuthGuard, } from "../core/auth/auth-guard.js";
|
|
2
|
+
import { CliUsageError } from "../core/output/cli-error.js";
|
|
3
|
+
import { withErrorHandler } from "./error-handler.js";
|
|
4
|
+
const commandAuthRequirements = new WeakMap();
|
|
5
|
+
export function setAuthRequirement(command, requirement) {
|
|
6
|
+
commandAuthRequirements.set(command, requirement);
|
|
7
|
+
return command;
|
|
8
|
+
}
|
|
9
|
+
export function authRequirementFor(command) {
|
|
10
|
+
const declared = commandAuthRequirements.get(command);
|
|
11
|
+
if (!declared) {
|
|
12
|
+
throw new CliUsageError({
|
|
13
|
+
code: "AUTH_POLICY_MISSING",
|
|
14
|
+
type: "unknown",
|
|
15
|
+
message: `命令 ${commandPath(command).join(" ")} 未声明鉴权级别。`,
|
|
16
|
+
retryable: false,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return typeof declared === "function" ? declared(command) : declared;
|
|
20
|
+
}
|
|
21
|
+
export function installAuthPreflight(program, guard = new AuthGuard()) {
|
|
22
|
+
program.hook("preAction", withErrorHandler(async (_thisCommand, actionCommand) => {
|
|
23
|
+
await guard.ensure(authRequirementFor(actionCommand));
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
3
26
|
function commandPath(cmd) {
|
|
4
27
|
const names = [];
|
|
5
28
|
let c = cmd;
|
|
@@ -11,64 +34,3 @@ function commandPath(cmd) {
|
|
|
11
34
|
}
|
|
12
35
|
return names;
|
|
13
36
|
}
|
|
14
|
-
function shouldSkipAuth(cmd) {
|
|
15
|
-
const path = commandPath(cmd);
|
|
16
|
-
if (path.length === 0)
|
|
17
|
-
return false;
|
|
18
|
-
if (path[0] === "config")
|
|
19
|
-
return true;
|
|
20
|
-
if (path.includes("auth")) {
|
|
21
|
-
const sub = path[path.length - 1];
|
|
22
|
-
return sub === "login" || sub === "logout";
|
|
23
|
-
}
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
function isDirectBusinessCommand(cmd) {
|
|
27
|
-
const path = commandPath(cmd);
|
|
28
|
-
return path.includes("tools") || path.includes("workflows") || path.includes("dam");
|
|
29
|
-
}
|
|
30
|
-
export function withAuth(handler) {
|
|
31
|
-
return async (...args) => {
|
|
32
|
-
const command = args[args.length - 1];
|
|
33
|
-
if (shouldSkipAuth(command)) {
|
|
34
|
-
return handler(...args);
|
|
35
|
-
}
|
|
36
|
-
const v2Store = new CredentialStoreV2();
|
|
37
|
-
const v2 = v2Store.read();
|
|
38
|
-
if (v2) {
|
|
39
|
-
if (v2Store.isAkExpired()) {
|
|
40
|
-
console.error("Error: AK/SK 已过期");
|
|
41
|
-
console.error("\n 请重新登录:\n gd-cli auth login\n");
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
if (v2Store.isBindExpired() && v2.current_org?.id) {
|
|
45
|
-
try {
|
|
46
|
-
const orgMgr = new OrgManager();
|
|
47
|
-
await orgMgr.switchOrg(v2.current_org.id);
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
console.error("Error: 组织绑定已过期");
|
|
51
|
-
console.error("\n 请切换组织:\n gd-cli org switch\n");
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
if (isDirectBusinessCommand(command) && !v2Store.read()?.current_org?.id) {
|
|
56
|
-
console.error("Error: 当前未绑定组织");
|
|
57
|
-
console.error("\n 请先切换组织:");
|
|
58
|
-
console.error(" gd-cli org switch\n");
|
|
59
|
-
process.exit(1);
|
|
60
|
-
}
|
|
61
|
-
return handler(...args);
|
|
62
|
-
}
|
|
63
|
-
if (isDirectBusinessCommand(command)) {
|
|
64
|
-
console.error("Error: 未找到 AK/SK 凭证");
|
|
65
|
-
console.error("\n 请先登录并绑定组织:");
|
|
66
|
-
console.error(" gd-cli auth login\n");
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
console.error("Error: 未找到 AK/SK 凭证");
|
|
70
|
-
console.error("\n 请先登录:");
|
|
71
|
-
console.error(" gd-cli auth login\n");
|
|
72
|
-
process.exit(1);
|
|
73
|
-
};
|
|
74
|
-
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type CliErrorBody } from "../core/output/envelope.js";
|
|
1
2
|
type ActionHandler = (...args: any[]) => Promise<void>;
|
|
2
3
|
export declare function withErrorHandler(handler: ActionHandler): ActionHandler;
|
|
4
|
+
export declare function toCliErrorBody(error: unknown): CliErrorBody;
|
|
3
5
|
export {};
|
|
@@ -11,11 +11,10 @@ const ERROR_GUIDES = {
|
|
|
11
11
|
AUTH_EXPIRED: "凭证已过期,请重新登录:\n gd-cli auth login",
|
|
12
12
|
AUTH_REFRESH_FAILED: "凭证刷新失败:\n gd-cli auth login",
|
|
13
13
|
AUTH_SCOPE_DENIED: "权限不足,请重新授权",
|
|
14
|
-
ORG_BIND_EXPIRED: "组织绑定已过期:\n gd-cli org switch",
|
|
15
14
|
ORG_INVALID: "当前组织无效:\n gd-cli org list",
|
|
16
15
|
PARAM_MISSING: "查看命令帮助:\n gd-cli <command> --help",
|
|
17
16
|
PARAM_INVALID: "参数值不合法,请检查参数格式",
|
|
18
|
-
TOOL_NOT_FOUND: "
|
|
17
|
+
TOOL_NOT_FOUND: "能力路由不存在,请检查输入或重试当前命令",
|
|
19
18
|
APP_NOT_FOUND: "Workflow 不存在:\n gd-cli workflows list",
|
|
20
19
|
TASK_NOT_FOUND: "任务不存在或已过期",
|
|
21
20
|
RATE_LIMITED: "请求过于频繁,请稍后重试",
|
|
@@ -104,7 +103,7 @@ function isCommandLike(value) {
|
|
|
104
103
|
"opts" in value &&
|
|
105
104
|
typeof value.opts === "function");
|
|
106
105
|
}
|
|
107
|
-
function toCliErrorBody(error) {
|
|
106
|
+
export function toCliErrorBody(error) {
|
|
108
107
|
if (error instanceof CliUsageError) {
|
|
109
108
|
return error.body;
|
|
110
109
|
}
|
|
@@ -227,7 +226,7 @@ function printCliUsageError(body) {
|
|
|
227
226
|
.filter(Boolean)
|
|
228
227
|
: [];
|
|
229
228
|
if (missingTools.length > 0) {
|
|
230
|
-
console.error(`
|
|
229
|
+
console.error(` 缺失内部能力: ${missingTools.join(", ")}`);
|
|
231
230
|
}
|
|
232
231
|
if (body.hint)
|
|
233
232
|
console.error(`\n 说明: ${body.hint}`);
|