gaoding-cli 1.0.0-alpha.10

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 (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/contracts/operations/agent.send/input.schema.json +354 -0
  4. package/contracts/operations/agent.send/output.schema.json +271 -0
  5. package/contracts/operations/auth.status/input.schema.json +7 -0
  6. package/contracts/operations/auth.status/output.schema.json +132 -0
  7. package/contracts/operations/dam.delete/input.schema.json +23 -0
  8. package/contracts/operations/dam.delete/output.schema.json +6 -0
  9. package/contracts/operations/dam.get/input.schema.json +16 -0
  10. package/contracts/operations/dam.get/output.schema.json +46 -0
  11. package/contracts/operations/dam.list/input.schema.json +44 -0
  12. package/contracts/operations/dam.list/output.schema.json +71 -0
  13. package/contracts/operations/dam.search/input.schema.json +46 -0
  14. package/contracts/operations/dam.search/output.schema.json +71 -0
  15. package/contracts/operations/dam.upload/input.schema.json +31 -0
  16. package/contracts/operations/dam.upload/output.schema.json +46 -0
  17. package/contracts/operations/editor.apply/input.schema.json +134 -0
  18. package/contracts/operations/editor.apply/output.schema.json +15 -0
  19. package/contracts/operations/editor.connect/input.schema.json +15 -0
  20. package/contracts/operations/editor.connect/output.schema.json +17 -0
  21. package/contracts/operations/editor.disconnect/input.schema.json +8 -0
  22. package/contracts/operations/editor.disconnect/output.schema.json +14 -0
  23. package/contracts/operations/editor.save/input.schema.json +8 -0
  24. package/contracts/operations/editor.save/output.schema.json +23 -0
  25. package/contracts/operations/editor.screenshot/input.schema.json +8 -0
  26. package/contracts/operations/editor.screenshot/output.schema.json +16 -0
  27. package/contracts/operations/editor.snapshot/input.schema.json +8 -0
  28. package/contracts/operations/editor.snapshot/output.schema.json +113 -0
  29. package/contracts/operations/model.get/input.schema.json +18 -0
  30. package/contracts/operations/model.get/output.schema.json +190 -0
  31. package/contracts/operations/model.list/input.schema.json +15 -0
  32. package/contracts/operations/model.list/output.schema.json +111 -0
  33. package/contracts/operations/org.current/input.schema.json +7 -0
  34. package/contracts/operations/org.current/output.schema.json +49 -0
  35. package/contracts/operations/org.list/input.schema.json +7 -0
  36. package/contracts/operations/org.list/output.schema.json +71 -0
  37. package/contracts/operations/tool.call/input.schema.json +35 -0
  38. package/contracts/operations/tool.call/output.schema.json +129 -0
  39. package/contracts/operations/tool.list/input.schema.json +7 -0
  40. package/contracts/operations/tool.list/output.schema.json +45 -0
  41. package/dist/bin/gd-cli.js +38 -0
  42. package/dist/bin/postinstall.js +42 -0
  43. package/dist/src/bootstrap/create-cli.js +55 -0
  44. package/dist/src/bootstrap/create-runtime.js +222 -0
  45. package/dist/src/bootstrap/validators.js +118 -0
  46. package/dist/src/cli/action-binding.js +63 -0
  47. package/dist/src/cli/agent-commands.js +39 -0
  48. package/dist/src/cli/auth-commands.js +47 -0
  49. package/dist/src/cli/dam-commands.js +207 -0
  50. package/dist/src/cli/editor-commands.js +73 -0
  51. package/dist/src/cli/errors.js +90 -0
  52. package/dist/src/cli/model-commands.js +47 -0
  53. package/dist/src/cli/org-commands.js +52 -0
  54. package/dist/src/cli/presenter.js +80 -0
  55. package/dist/src/cli/prompt.js +30 -0
  56. package/dist/src/cli/tool-commands.js +65 -0
  57. package/dist/src/cli/update-command.js +12 -0
  58. package/dist/src/contracts/schema.js +4 -0
  59. package/dist/src/features/agent/creative-agent-adapter.js +31 -0
  60. package/dist/src/features/agent/creative-protocol.js +324 -0
  61. package/dist/src/features/agent/creative-stream.js +127 -0
  62. package/dist/src/features/agent/use-cases.js +113 -0
  63. package/dist/src/features/auth/access-policy.js +101 -0
  64. package/dist/src/features/auth/credential-store.js +62 -0
  65. package/dist/src/features/auth/sso-service.js +179 -0
  66. package/dist/src/features/auth/state.js +129 -0
  67. package/dist/src/features/auth/use-cases.js +106 -0
  68. package/dist/src/features/dam/asset-projection.js +270 -0
  69. package/dist/src/features/dam/dam-api-adapter.js +206 -0
  70. package/dist/src/features/dam/object-storage.js +191 -0
  71. package/dist/src/features/dam/registered-uploader.js +224 -0
  72. package/dist/src/features/dam/storage-upload.js +141 -0
  73. package/dist/src/features/dam/transient-uploader.js +17 -0
  74. package/dist/src/features/dam/use-cases.js +151 -0
  75. package/dist/src/features/editor/bridge-client.js +96 -0
  76. package/dist/src/features/editor/bridge-process.js +222 -0
  77. package/dist/src/features/editor/bridge-server.js +311 -0
  78. package/dist/src/features/editor/protocol.js +1 -0
  79. package/dist/src/features/editor/session-state.js +78 -0
  80. package/dist/src/features/editor/session.js +193 -0
  81. package/dist/src/features/editor/use-cases.js +61 -0
  82. package/dist/src/features/org/org-service.js +76 -0
  83. package/dist/src/features/org/use-cases.js +140 -0
  84. package/dist/src/features/skill/bundled-skills.js +55 -0
  85. package/dist/src/features/skill/installer.js +265 -0
  86. package/dist/src/features/tool/catalog.js +79 -0
  87. package/dist/src/features/tool/dynamic-schema.js +96 -0
  88. package/dist/src/features/tool/mns-catalog-adapter.js +262 -0
  89. package/dist/src/features/tool/tool-api-adapter.js +200 -0
  90. package/dist/src/features/tool/use-cases.js +126 -0
  91. package/dist/src/features/update/update-service.js +194 -0
  92. package/dist/src/platform/json-input.js +64 -0
  93. package/dist/src/platform/local-json-file.js +59 -0
  94. package/dist/src/platform/open-browser.js +8 -0
  95. package/dist/src/platform/redact.js +80 -0
  96. package/dist/src/platform/safe-upload-file.js +146 -0
  97. package/dist/src/platform/signature.js +19 -0
  98. package/dist/src/platform/signed-http-transport.js +109 -0
  99. package/dist/src/platform/url-safety.js +100 -0
  100. package/package.json +56 -0
  101. package/skills/gd-cli/SKILL.md +15 -0
  102. package/skills/gd-cli/references/auth-org.md +9 -0
  103. package/skills/gd-cli/references/creation.md +38 -0
  104. package/skills/gd-cli/references/dam.md +20 -0
  105. package/skills/gd-cli/references/editor.md +12 -0
  106. package/skills/gd-cli/references/errors.md +10 -0
  107. package/skills/gd-cli/references/update.md +11 -0
@@ -0,0 +1,31 @@
1
+ import { buildCreativeRequest, creativeContextId, creativeTurnMessages, normalizeCreativeMessages } from "./creative-protocol.js";
2
+ import { parseCreativeStream } from "./creative-stream.js";
3
+ export function createCreativeAgentAdapter(dependencies) {
4
+ return {
5
+ async complete(message, access, signal) {
6
+ signal.throwIfAborted();
7
+ const ids = message.contextId === undefined
8
+ ? {
9
+ localThreadId: dependencies.idFactory(),
10
+ localMessageId: dependencies.idFactory()
11
+ }
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
20
+ });
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
27
+ });
28
+ return normalizeCreativeMessages(creativeTurnMessages(messages, ids.localMessageId));
29
+ }
30
+ };
31
+ }
@@ -0,0 +1,324 @@
1
+ import { RemoteRequestError } from "../../platform/signed-http-transport.js";
2
+ export function buildCreativeRequest(message, ids) {
3
+ const prompt = [];
4
+ const textParts = [];
5
+ const attachments = [];
6
+ const parameters = {};
7
+ const answerValues = [];
8
+ const answeredQuestions = [];
9
+ let questionMessageId;
10
+ let hasAnswers = false;
11
+ for (const part of message.parts) {
12
+ if (part.kind === "text") {
13
+ textParts.push(part.text);
14
+ prompt.push({ type: "text", content: part.text });
15
+ continue;
16
+ }
17
+ if (part.kind === "asset") {
18
+ if (part.role === "first_frame" || part.role === "last_frame") {
19
+ prompt.push({
20
+ type: "text",
21
+ content: part.role === "first_frame" ? "首帧" : "尾帧"
22
+ });
23
+ }
24
+ prompt.push({
25
+ type: "media",
26
+ url: part.asset.url,
27
+ name: part.asset.filename,
28
+ mime: part.asset.mediaType
29
+ });
30
+ attachments.push({
31
+ uri: part.asset.url,
32
+ mime_type: part.asset.mediaType,
33
+ from_user_upload: part.asset.fromUserUpload
34
+ });
35
+ continue;
36
+ }
37
+ if (part.kind === "parameters") {
38
+ if (part.data.ratio !== undefined)
39
+ parameters.ratio = part.data.ratio;
40
+ if (part.data.resolution !== undefined)
41
+ parameters.resolution = part.data.resolution;
42
+ if (part.data.durationSeconds !== undefined) {
43
+ parameters.duration = String(part.data.durationSeconds);
44
+ }
45
+ continue;
46
+ }
47
+ hasAnswers = true;
48
+ questionMessageId ??= part.data.questionMessageId;
49
+ for (const answer of part.data.answers) {
50
+ const values = [...answer.values];
51
+ answerValues.push(values);
52
+ answeredQuestions.push({ question: answer.question, answers: values });
53
+ }
54
+ }
55
+ const hasParameters = Object.keys(parameters).length > 0;
56
+ let content;
57
+ if (hasAnswers) {
58
+ content = {
59
+ type: "user",
60
+ text: textParts.join("\n"),
61
+ prompt: prompt.length > 0 ? prompt : [{ type: "text", content: "" }],
62
+ ...(hasParameters ? { parameters } : {}),
63
+ answer: { answers: answerValues }
64
+ };
65
+ }
66
+ else if (prompt.length === 1 && prompt[0]?.type === "text" && !hasParameters) {
67
+ content = { type: "plain", text: prompt[0].content };
68
+ }
69
+ else {
70
+ content = {
71
+ type: "plain",
72
+ prompt,
73
+ ...(hasParameters ? { parameters } : {})
74
+ };
75
+ }
76
+ const extra = {};
77
+ if (hasAnswers && questionMessageId !== undefined) {
78
+ extra.lastToolMessageId = questionMessageId;
79
+ extra.last_tool_message_id = questionMessageId;
80
+ extra.askUserQuestionAnsweredQuestions = answeredQuestions;
81
+ extra.questions = answeredQuestions;
82
+ }
83
+ return {
84
+ content,
85
+ name: hasAnswers ? "ask_user_question" : "user",
86
+ role: "user",
87
+ local_thread_id: message.contextId === undefined ? ids.localThreadId ?? "" : "",
88
+ local_message_id: ids.localMessageId,
89
+ thread_id: message.contextId ?? "",
90
+ attachments,
91
+ extra
92
+ };
93
+ }
94
+ function record(value) {
95
+ return value !== null && typeof value === "object" && !Array.isArray(value)
96
+ ? value
97
+ : undefined;
98
+ }
99
+ function parseJson(value) {
100
+ if (typeof value !== "string")
101
+ return value;
102
+ try {
103
+ return JSON.parse(value);
104
+ }
105
+ catch {
106
+ return undefined;
107
+ }
108
+ }
109
+ function nonEmptyString(value) {
110
+ return typeof value === "string" && value.trim() !== "" ? value : undefined;
111
+ }
112
+ function fail() {
113
+ throw new RemoteRequestError();
114
+ }
115
+ export function creativeContextId(messages) {
116
+ if (messages.some((message) => message.event === "system_error"))
117
+ return fail();
118
+ const contextId = messages
119
+ .map((message) => nonEmptyString(message.thread_id))
120
+ .find((value) => value !== undefined);
121
+ return contextId ?? fail();
122
+ }
123
+ export function creativeTurnMessages(value, localMessageId) {
124
+ if (!Array.isArray(value) || !value.every((message) => record(message) !== undefined)) {
125
+ return fail();
126
+ }
127
+ const messages = value;
128
+ const start = messages.findIndex((message) => message.role === "user" && message.local_message_id === localMessageId);
129
+ if (start < 0)
130
+ return fail();
131
+ return messages.slice(start);
132
+ }
133
+ function contentOf(message) {
134
+ return record(message.content);
135
+ }
136
+ function functionPayload(message) {
137
+ const content = contentOf(message);
138
+ if (content?.type !== "function_call")
139
+ return undefined;
140
+ return record(parseJson(content.text));
141
+ }
142
+ function isQuestionCall(message) {
143
+ return functionPayload(message)?.name === "ask_user_question";
144
+ }
145
+ function hasHiddenTransportRole(message) {
146
+ return message.role === "user" || message.role === "system" || message.role === "tool";
147
+ }
148
+ function inferMediaType(url) {
149
+ let pathname;
150
+ try {
151
+ pathname = new URL(url).pathname;
152
+ }
153
+ catch {
154
+ pathname = url.split(/[?#]/, 1)[0] ?? "";
155
+ }
156
+ const extension = pathname.split(".").at(-1)?.toLowerCase();
157
+ return {
158
+ gif: "image/gif",
159
+ jpeg: "image/jpeg",
160
+ jpg: "image/jpeg",
161
+ mov: "video/quicktime",
162
+ mp3: "audio/mpeg",
163
+ mp4: "video/mp4",
164
+ png: "image/png",
165
+ wav: "audio/wav",
166
+ webm: "video/webm",
167
+ webp: "image/webp"
168
+ }[extension ?? ""] ?? "application/octet-stream";
169
+ }
170
+ function functionResponseParts(raw) {
171
+ const parsed = parseJson(raw);
172
+ const values = Array.isArray(parsed) ? parsed : parsed === undefined ? [] : [parsed];
173
+ const parts = [];
174
+ for (const value of values) {
175
+ const item = record(value);
176
+ if (item === undefined)
177
+ continue;
178
+ const text = nonEmptyString(item.text);
179
+ if (text !== undefined)
180
+ parts.push({ text });
181
+ const url = nonEmptyString(item.uri ?? item.url);
182
+ if (url === undefined)
183
+ continue;
184
+ const mediaType = nonEmptyString(item.mime_type ?? item.mimeType) ?? inferMediaType(url);
185
+ const filename = nonEmptyString(item.filename ?? item.name);
186
+ parts.push({
187
+ url,
188
+ mediaType,
189
+ ...(filename === undefined ? {} : { filename })
190
+ });
191
+ }
192
+ return parts;
193
+ }
194
+ function parseQuestionPart(message) {
195
+ const payload = functionPayload(message);
196
+ if (payload?.name !== "ask_user_question")
197
+ return fail();
198
+ const argumentsValue = parseJson(payload.arguments);
199
+ const argumentsRecord = record(argumentsValue);
200
+ if (argumentsRecord === undefined || !Array.isArray(argumentsRecord.questions)
201
+ || argumentsRecord.questions.length === 0)
202
+ return fail();
203
+ const questions = argumentsRecord.questions.map((value) => {
204
+ const question = record(value);
205
+ const text = nonEmptyString(question?.question);
206
+ if (question === undefined || text === undefined)
207
+ return fail();
208
+ const rawOptions = question.options ?? [];
209
+ if (!Array.isArray(rawOptions))
210
+ return fail();
211
+ const options = rawOptions.map((rawOption) => {
212
+ const option = record(rawOption);
213
+ const label = nonEmptyString(option?.label);
214
+ if (option === undefined || label === undefined)
215
+ return fail();
216
+ return { label };
217
+ });
218
+ const allowMultiple = question.allow_multiple ?? question.allowMultiple;
219
+ if (typeof allowMultiple !== "boolean")
220
+ return fail();
221
+ return { question: text, options, allowMultiple };
222
+ });
223
+ const title = nonEmptyString(argumentsRecord.title);
224
+ return {
225
+ data: {
226
+ ...(title === undefined ? {} : { title }),
227
+ questions: [questions[0], ...questions.slice(1)]
228
+ }
229
+ };
230
+ }
231
+ function dedupeParts(parts) {
232
+ const texts = new Set();
233
+ const urls = new Set();
234
+ return parts.filter((part) => {
235
+ if ("text" in part) {
236
+ if (texts.has(part.text))
237
+ return false;
238
+ texts.add(part.text);
239
+ return true;
240
+ }
241
+ if ("url" in part) {
242
+ if (urls.has(part.url))
243
+ return false;
244
+ urls.add(part.url);
245
+ }
246
+ return true;
247
+ });
248
+ }
249
+ export function normalizeCreativeMessages(messages) {
250
+ const contextId = creativeContextId(messages);
251
+ const latest = new Map();
252
+ const anonymous = [];
253
+ for (const message of messages) {
254
+ const id = nonEmptyString(message.message_id);
255
+ if (id === undefined)
256
+ anonymous.push(message);
257
+ else
258
+ latest.set(id, message);
259
+ }
260
+ const normalized = [...latest.values(), ...anonymous];
261
+ const parts = [];
262
+ for (const message of normalized) {
263
+ if (hasHiddenTransportRole(message))
264
+ continue;
265
+ const content = contentOf(message);
266
+ if (content !== undefined) {
267
+ if (content.type === "plain" || content.type === "user" || content.type === "text") {
268
+ const text = nonEmptyString(content.text);
269
+ if (text !== undefined)
270
+ parts.push({ text });
271
+ }
272
+ else if (content.type === "function_response") {
273
+ parts.push(...functionResponseParts(content.text));
274
+ }
275
+ }
276
+ else {
277
+ const text = nonEmptyString(message.content);
278
+ if (text !== undefined)
279
+ parts.push({ text });
280
+ }
281
+ }
282
+ const question = normalized.find((message) => !hasHiddenTransportRole(message) && isQuestionCall(message));
283
+ let questionMessageId;
284
+ if (question !== undefined) {
285
+ questionMessageId = nonEmptyString(question.message_id);
286
+ if (questionMessageId === undefined)
287
+ return fail();
288
+ parts.push(parseQuestionPart(question));
289
+ }
290
+ const publicParts = dedupeParts(parts);
291
+ if (publicParts.length === 0)
292
+ return fail();
293
+ const usageItems = [];
294
+ for (const message of normalized) {
295
+ if (contentOf(message)?.type !== "function_response")
296
+ continue;
297
+ const extra = record(message.extra);
298
+ const amount = extra?.deduct_points;
299
+ if (amount === undefined)
300
+ continue;
301
+ if (typeof amount !== "number" || !Number.isFinite(amount) || amount < 0)
302
+ return fail();
303
+ if (amount === 0)
304
+ continue;
305
+ const model = nonEmptyString(extra?.scene_code);
306
+ if (model === undefined)
307
+ return fail();
308
+ usageItems.push({ model, cost: { unit: "gaodou", amount } });
309
+ }
310
+ return {
311
+ message: {
312
+ ...(questionMessageId === undefined ? {} : { messageId: questionMessageId }),
313
+ contextId,
314
+ parts: [publicParts[0], ...publicParts.slice(1)]
315
+ },
316
+ usage: {
317
+ cost: {
318
+ unit: "gaodou",
319
+ amount: usageItems.reduce((total, item) => total + item.cost.amount, 0)
320
+ },
321
+ items: usageItems
322
+ }
323
+ };
324
+ }
@@ -0,0 +1,127 @@
1
+ function isMessage(value) {
2
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3
+ }
4
+ function extractPayloads(raw) {
5
+ const lines = raw.replace(/\r\n?/g, "\n").split("\n");
6
+ const payloads = [];
7
+ let dataLines = [];
8
+ let foundData = false;
9
+ const flush = () => {
10
+ if (dataLines.length > 0)
11
+ payloads.push(dataLines.join("\n"));
12
+ dataLines = [];
13
+ };
14
+ for (const line of lines) {
15
+ if (line.startsWith("data:")) {
16
+ foundData = true;
17
+ dataLines.push(line.slice(5).replace(/^ /, ""));
18
+ }
19
+ else if (line === "") {
20
+ flush();
21
+ }
22
+ else if (line.startsWith(":") || line.startsWith("event:") || line.startsWith("id:")) {
23
+ continue;
24
+ }
25
+ else if (!foundData || dataLines.length > 0) {
26
+ dataLines.push(line);
27
+ }
28
+ }
29
+ flush();
30
+ return payloads;
31
+ }
32
+ function jsonEnd(value, start) {
33
+ const stack = [];
34
+ let inString = false;
35
+ let escaped = false;
36
+ for (let index = start; index < value.length; index += 1) {
37
+ const character = value[index];
38
+ if (inString) {
39
+ if (escaped)
40
+ escaped = false;
41
+ else if (character === "\\")
42
+ escaped = true;
43
+ else if (character === '"')
44
+ inString = false;
45
+ continue;
46
+ }
47
+ if (character === '"') {
48
+ inString = true;
49
+ continue;
50
+ }
51
+ if (character === "{" || character === "[")
52
+ stack.push(character);
53
+ else if (character === "}" || character === "]") {
54
+ const opening = stack.pop();
55
+ if ((character === "}" && opening !== "{") || (character === "]" && opening !== "[")) {
56
+ return -1;
57
+ }
58
+ if (stack.length === 0)
59
+ return index;
60
+ }
61
+ }
62
+ return -1;
63
+ }
64
+ function parseSequence(payload) {
65
+ if (payload.trim() === "[DONE]")
66
+ return [];
67
+ const values = [];
68
+ let index = 0;
69
+ while (index < payload.length) {
70
+ const objectStart = payload.indexOf("{", index);
71
+ const arrayStart = payload.indexOf("[", index);
72
+ const starts = [objectStart, arrayStart].filter((value) => value >= 0);
73
+ if (starts.length === 0)
74
+ break;
75
+ const start = Math.min(...starts);
76
+ const end = jsonEnd(payload, start);
77
+ if (end < 0)
78
+ break;
79
+ try {
80
+ values.push(JSON.parse(payload.slice(start, end + 1)));
81
+ }
82
+ catch {
83
+ // Ignore only the malformed value; a later complete value can still be read.
84
+ }
85
+ index = end + 1;
86
+ }
87
+ return values;
88
+ }
89
+ export async function parseCreativeStream(stream, signal) {
90
+ signal.throwIfAborted();
91
+ const reader = stream.getReader();
92
+ const decoder = new TextDecoder();
93
+ let raw = "";
94
+ const onAbort = () => {
95
+ void reader.cancel(signal.reason).catch(() => undefined);
96
+ };
97
+ signal.addEventListener("abort", onAbort, { once: true });
98
+ try {
99
+ while (true) {
100
+ signal.throwIfAborted();
101
+ const result = await reader.read();
102
+ signal.throwIfAborted();
103
+ if (result.done)
104
+ break;
105
+ raw += decoder.decode(result.value, { stream: true });
106
+ }
107
+ raw += decoder.decode();
108
+ }
109
+ finally {
110
+ signal.removeEventListener("abort", onAbort);
111
+ reader.releaseLock();
112
+ }
113
+ const messages = [];
114
+ for (const payload of extractPayloads(raw)) {
115
+ for (const value of parseSequence(payload)) {
116
+ if (Array.isArray(value)) {
117
+ for (const item of value)
118
+ if (isMessage(item))
119
+ messages.push(item);
120
+ }
121
+ else if (isMessage(value)) {
122
+ messages.push(value);
123
+ }
124
+ }
125
+ }
126
+ return messages;
127
+ }
@@ -0,0 +1,113 @@
1
+ import { parseSafeRemoteAssetUrl, UrlSafetyError } from "../../platform/url-safety.js";
2
+ export class AgentInputError extends Error {
3
+ constructor() {
4
+ super("Agent 输入不可用。");
5
+ this.name = "AgentInputError";
6
+ }
7
+ }
8
+ const mediaTypes = {
9
+ avif: "image/avif",
10
+ gif: "image/gif",
11
+ heic: "image/heic",
12
+ jpeg: "image/jpeg",
13
+ jpg: "image/jpeg",
14
+ mp4: "video/mp4",
15
+ png: "image/png",
16
+ webp: "image/webp"
17
+ };
18
+ function pathnameFilename(url) {
19
+ const segment = url.pathname.split("/").filter(Boolean).at(-1);
20
+ if (segment === undefined)
21
+ return "creative-media";
22
+ try {
23
+ return decodeURIComponent(segment) || "creative-media";
24
+ }
25
+ catch {
26
+ return segment;
27
+ }
28
+ }
29
+ function inferMediaType(filename) {
30
+ const extension = filename.split(".").at(-1)?.toLowerCase();
31
+ return extension === undefined || extension === filename.toLowerCase()
32
+ ? "application/octet-stream"
33
+ : mediaTypes[extension] ?? "application/octet-stream";
34
+ }
35
+ export function createAgentUseCases(dependencies) {
36
+ return {
37
+ async send({ input, access, signal }) {
38
+ signal.throwIfAborted();
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 });
56
+ continue;
57
+ }
58
+ let url;
59
+ try {
60
+ url = parseSafeRemoteAssetUrl(part.url);
61
+ }
62
+ catch (error) {
63
+ if (error instanceof UrlSafetyError)
64
+ throw new AgentInputError();
65
+ throw error;
66
+ }
67
+ const filename = part.filename ?? pathnameFilename(url);
68
+ 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",
83
+ data: {
84
+ questionMessageId: part.data.questionMessageId,
85
+ answers: part.data.answers.map((answer) => ({
86
+ question: answer.question,
87
+ values: [...answer.values]
88
+ }))
89
+ }
90
+ });
91
+ continue;
92
+ }
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
+ });
103
+ }
104
+ signal.throwIfAborted();
105
+ return dependencies.completion.complete({
106
+ ...(input.message.contextId === undefined
107
+ ? {}
108
+ : { contextId: input.message.contextId }),
109
+ parts: prepared
110
+ }, access, signal);
111
+ }
112
+ };
113
+ }
@@ -0,0 +1,101 @@
1
+ import { OrganizationInvalidError } from "../org/use-cases.js";
2
+ export class AuthRequiredError extends Error {
3
+ constructor() {
4
+ super("请先执行 gd-cli auth login");
5
+ this.name = "AuthRequiredError";
6
+ }
7
+ }
8
+ export class AuthExpiredError extends Error {
9
+ constructor() {
10
+ super("登录已过期,请退出后重新登录。");
11
+ this.name = "AuthExpiredError";
12
+ }
13
+ }
14
+ const REFRESH_WINDOW_MS = 5 * 60 * 1000;
15
+ const organizationRefreshes = new Map();
16
+ export function createAccessPolicies(dependencies) {
17
+ async function readValidState(signal) {
18
+ signal.throwIfAborted();
19
+ const state = await dependencies.store.read();
20
+ signal.throwIfAborted();
21
+ if (!state)
22
+ throw new AuthRequiredError();
23
+ if (Date.parse(state.credential.expiresAt) <= dependencies.now().getTime()) {
24
+ throw new AuthExpiredError();
25
+ }
26
+ return state;
27
+ }
28
+ async function refreshOrganization(state, signal) {
29
+ const key = `${state.credential.accessKey}\0${state.organization.id}`;
30
+ let refresh = organizationRefreshes.get(key);
31
+ if (!refresh) {
32
+ refresh = (async () => {
33
+ let organization;
34
+ try {
35
+ organization = await dependencies.refreshCurrentOrganization({ state, signal });
36
+ }
37
+ catch (error) {
38
+ if (error instanceof OrganizationInvalidError) {
39
+ const latest = await dependencies.store.read();
40
+ if (sameOrganization(latest, state)) {
41
+ const { organization: _organization, ...withoutOrganization } = latest;
42
+ await dependencies.store.write(withoutOrganization);
43
+ }
44
+ }
45
+ throw error;
46
+ }
47
+ const latest = await dependencies.store.read();
48
+ if (sameOrganization(latest, state)) {
49
+ await dependencies.store.write({ ...latest, organization });
50
+ }
51
+ })();
52
+ organizationRefreshes.set(key, refresh);
53
+ }
54
+ try {
55
+ await refresh;
56
+ }
57
+ finally {
58
+ if (organizationRefreshes.get(key) === refresh)
59
+ organizationRefreshes.delete(key);
60
+ }
61
+ }
62
+ return {
63
+ none: {
64
+ async authorize(signal) {
65
+ signal.throwIfAborted();
66
+ return { policy: "none" };
67
+ }
68
+ },
69
+ credential: {
70
+ async authorize(signal) {
71
+ const state = await readValidState(signal);
72
+ return { policy: "credential", state };
73
+ }
74
+ },
75
+ organization: {
76
+ async authorize(signal) {
77
+ let state = await readValidState(signal);
78
+ if (!state.organization)
79
+ throw new OrganizationInvalidError();
80
+ if (bindingNeedsRefresh(state.organization, dependencies.now())) {
81
+ await refreshOrganization(state, signal);
82
+ state = await readValidState(signal);
83
+ if (!state.organization)
84
+ throw new OrganizationInvalidError();
85
+ }
86
+ return {
87
+ policy: "organization",
88
+ state: state
89
+ };
90
+ }
91
+ }
92
+ };
93
+ }
94
+ function bindingNeedsRefresh(organization, now) {
95
+ return organization.bindExpiresAt === undefined
96
+ || Date.parse(organization.bindExpiresAt) <= now.getTime() + REFRESH_WINDOW_MS;
97
+ }
98
+ function sameOrganization(current, expected) {
99
+ return current?.credential.accessKey === expected.credential.accessKey
100
+ && current.organization?.id === expected.organization.id;
101
+ }