seacloud-sdk 0.10.3 → 0.11.0

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/dist/cli.js CHANGED
@@ -13,7 +13,51 @@ var SeacloudError = class extends Error {
13
13
  };
14
14
 
15
15
  // src/core/config.ts
16
- function getApiToken(providedApiKey) {
16
+ function isInIframe() {
17
+ try {
18
+ return typeof globalThis.window !== "undefined" && globalThis.window.self !== globalThis.window.top;
19
+ } catch (e) {
20
+ return true;
21
+ }
22
+ }
23
+ async function getTokenFromParent(timeout = 5e3) {
24
+ if (!isInIframe()) {
25
+ return null;
26
+ }
27
+ return new Promise((resolve) => {
28
+ const messageHandler = (event) => {
29
+ if (event.data && event.data.type === "seaverse:token") {
30
+ cleanup();
31
+ const token = event.data.payload?.accessToken;
32
+ resolve(token || null);
33
+ } else if (event.data && event.data.type === "seaverse:error") {
34
+ cleanup();
35
+ console.warn("[SeaCloud SDK] Error getting token from parent:", event.data.error);
36
+ resolve(null);
37
+ }
38
+ };
39
+ const timeoutId = setTimeout(() => {
40
+ cleanup();
41
+ resolve(null);
42
+ }, timeout);
43
+ const cleanup = () => {
44
+ clearTimeout(timeoutId);
45
+ globalThis.window.removeEventListener("message", messageHandler);
46
+ };
47
+ globalThis.window.addEventListener("message", messageHandler);
48
+ try {
49
+ globalThis.window.parent.postMessage(
50
+ { type: "seaverse:get_token" },
51
+ "*"
52
+ // 允许任何源,支持跨域场景
53
+ );
54
+ } catch (e) {
55
+ cleanup();
56
+ resolve(null);
57
+ }
58
+ });
59
+ }
60
+ async function getApiToken(providedApiKey) {
17
61
  if (providedApiKey) {
18
62
  return providedApiKey;
19
63
  }
@@ -29,23 +73,27 @@ function getApiToken(providedApiKey) {
29
73
  if (typeof process !== "undefined" && process.env?.API_SERVICE_TOKEN) {
30
74
  return process.env.API_SERVICE_TOKEN;
31
75
  }
32
- throw new Error(
33
- 'SeaCloud SDK: No API token found. Please ensure token is available in localStorage.getItem("auth_token") (browser) or process.env.API_SERVICE_TOKEN (Node.js), or initialize with initSeacloud({ apiKey: "your-token" }).'
34
- );
76
+ if (typeof globalThis.window !== "undefined") {
77
+ const parentToken = await getTokenFromParent();
78
+ if (parentToken) {
79
+ return parentToken;
80
+ }
81
+ }
82
+ return "";
35
83
  }
36
84
  function createConfig(options = {}) {
37
- const apiKey = getApiToken(options.apiKey);
85
+ const apiKey = options.apiKey;
38
86
  const baseUrl = options.baseUrl || (typeof process !== "undefined" ? process.env?.SEACLOUD_BASE_URL : void 0) || "https://proxy-rs.seaverse.ai";
39
87
  const fetchImpl = options.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
40
88
  if (!fetchImpl) {
41
89
  throw new Error("fetch is not available. Please provide a fetch implementation in config or upgrade to Node.js 18+");
42
90
  }
43
91
  return {
44
- apiKey,
92
+ apiKey: apiKey || "",
93
+ // 提供默认空字符串,实际请求时会动态获取
45
94
  baseUrl,
46
95
  fetch: fetchImpl,
47
- timeout: options.timeout || 3e4,
48
- xProject: options.xProject || "SeaVerse"
96
+ timeout: options.timeout || 3e4
49
97
  };
50
98
  }
51
99
  function validateConfig(config) {
@@ -87,7 +135,7 @@ var SeacloudClient = class {
87
135
  */
88
136
  async createTask(endpoint, body) {
89
137
  const url = `${this.config.baseUrl}${endpoint}`;
90
- const currentToken = getApiToken(this.providedApiKey);
138
+ const currentToken = await getApiToken(this.providedApiKey);
91
139
  const controller = new AbortController();
92
140
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
93
141
  try {
@@ -134,7 +182,7 @@ var SeacloudClient = class {
134
182
  */
135
183
  async getTaskStatus(endpoint, taskId) {
136
184
  const url = `${this.config.baseUrl}${endpoint}/task/${taskId}`;
137
- const currentToken = getApiToken(this.providedApiKey);
185
+ const currentToken = await getApiToken(this.providedApiKey);
138
186
  const controller = new AbortController();
139
187
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
140
188
  try {
@@ -205,8 +253,7 @@ function initSeacloud(apiKeyOrConfig, options) {
205
253
  globalConfig.client = new SeacloudClient({
206
254
  apiKey: apiKey || "",
207
255
  baseUrl: config.baseUrl,
208
- timeout: config.timeout,
209
- xProject: config.xProject
256
+ timeout: config.timeout
210
257
  });
211
258
  if (config.intervalMs !== void 0) {
212
259
  globalConfig.defaultPollingOptions.intervalMs = config.intervalMs;
@@ -232,6 +279,7 @@ async function llmChatCompletions(params) {
232
279
  const client = getClient();
233
280
  const config = client.getConfig();
234
281
  const url = `${config.baseUrl}/llm/chat/completions`;
282
+ const token = await getApiToken(config.apiKey);
235
283
  const controller = new AbortController();
236
284
  const timeoutId = setTimeout(() => controller.abort(), config.timeout);
237
285
  try {
@@ -239,7 +287,7 @@ async function llmChatCompletions(params) {
239
287
  method: "POST",
240
288
  headers: {
241
289
  "Content-Type": "application/json",
242
- "Authorization": `Bearer ${config.apiKey}`
290
+ "Authorization": `Bearer ${token}`
243
291
  },
244
292
  body: JSON.stringify(params),
245
293
  signal: controller.signal
@@ -325,16 +373,14 @@ async function agentChatCompletions(params) {
325
373
  const controller = new AbortController();
326
374
  const timeoutId = setTimeout(() => controller.abort(), config.timeout);
327
375
  try {
328
- const headers = {
329
- "Content-Type": "application/json",
330
- "Authorization": `Bearer ${config.apiKey}`
331
- };
332
- if (config.xProject) {
333
- headers["X-Project"] = config.xProject;
334
- }
335
376
  const response = await config.fetch(url, {
336
377
  method: "POST",
337
- headers,
378
+ headers: {
379
+ "Content-Type": "application/json",
380
+ "Authorization": `Bearer ${config.apiKey}`,
381
+ "X-Project": "SeaArt"
382
+ // Required header for agent API
383
+ },
338
384
  body: JSON.stringify(requestBody),
339
385
  signal: controller.signal
340
386
  });
@@ -519,17 +565,14 @@ async function appSearch(params) {
519
565
  const timeoutId = setTimeout(() => controller.abort(), config.timeout);
520
566
  try {
521
567
  const requestId = `req-${Date.now()}-${Math.random().toString(36).substring(7)}`;
522
- const headers = {
523
- "Content-Type": "application/json",
524
- "Authorization": `Bearer ${config.apiKey}`,
525
- "X-Request-Id": requestId
526
- };
527
- if (config.xProject) {
528
- headers["X-Project"] = config.xProject;
529
- }
530
568
  const response = await config.fetch(url, {
531
569
  method: "POST",
532
- headers,
570
+ headers: {
571
+ "Content-Type": "application/json",
572
+ "Authorization": `Bearer ${config.apiKey}`,
573
+ "X-Request-Id": requestId,
574
+ "X-Project": "KIIRA"
575
+ },
533
576
  body: JSON.stringify(params),
534
577
  signal: controller.signal
535
578
  });
@@ -545,6 +588,73 @@ async function appSearch(params) {
545
588
  throw error;
546
589
  }
547
590
  }
591
+
592
+ // src/api/scan.ts
593
+ async function scan(params) {
594
+ if (!params.uri && !params.img_base64) {
595
+ throw new SeacloudError("\u5FC5\u987B\u63D0\u4F9B uri \u6216 img_base64 \u4E2D\u7684\u81F3\u5C11\u4E00\u4E2A\u53C2\u6570");
596
+ }
597
+ if (params.is_video === 1 && params.img_base64) {
598
+ throw new SeacloudError("\u89C6\u9891\u68C0\u6D4B\u4E0D\u652F\u6301 base64 \u8F93\u5165\uFF0C\u5FC5\u987B\u4F7F\u7528 uri \u53C2\u6570");
599
+ }
600
+ if (!params.risk_types || params.risk_types.length === 0) {
601
+ throw new SeacloudError("\u5FC5\u987B\u63D0\u4F9B\u81F3\u5C11\u4E00\u4E2A\u98CE\u9669\u7C7B\u578B");
602
+ }
603
+ const client = getClient();
604
+ const config = client.getConfig();
605
+ const url = `${config.baseUrl}/scan`;
606
+ const token = await getApiToken(config.apiKey);
607
+ const controller = new AbortController();
608
+ const timeoutId = setTimeout(() => controller.abort(), config.timeout);
609
+ try {
610
+ const response = await config.fetch(url, {
611
+ method: "POST",
612
+ headers: {
613
+ "Content-Type": "application/json",
614
+ "Authorization": `Bearer ${token}`
615
+ },
616
+ body: JSON.stringify(params),
617
+ signal: controller.signal
618
+ });
619
+ clearTimeout(timeoutId);
620
+ if (!response.ok) {
621
+ const errorBody = await response.text();
622
+ throw new SeacloudError(
623
+ `HTTP ${response.status}: ${errorBody}`,
624
+ response.status,
625
+ errorBody
626
+ );
627
+ }
628
+ const responseData = await response.json();
629
+ let result;
630
+ if (responseData.success !== void 0 && responseData.data) {
631
+ result = responseData.data;
632
+ } else {
633
+ result = responseData;
634
+ }
635
+ if (!result.ok) {
636
+ throw new SeacloudError(
637
+ `\u5185\u5BB9\u5B89\u5168\u68C0\u6D4B\u4E1A\u52A1\u9519\u8BEF: ${result.error || "\u672A\u77E5\u9519\u8BEF"}`,
638
+ void 0,
639
+ result
640
+ );
641
+ }
642
+ return result;
643
+ } catch (error) {
644
+ clearTimeout(timeoutId);
645
+ if (error instanceof SeacloudError) {
646
+ throw error;
647
+ }
648
+ if (error.name === "AbortError") {
649
+ throw new SeacloudError(`Request timeout after ${config.timeout}ms`);
650
+ }
651
+ throw new SeacloudError(
652
+ `Request failed: ${error.message}`,
653
+ void 0,
654
+ error
655
+ );
656
+ }
657
+ }
548
658
  var __filename$1 = fileURLToPath(import.meta.url);
549
659
  dirname(__filename$1);
550
660
  function showHelp() {
@@ -558,6 +668,8 @@ Commands:
558
668
  llm <prompt> Chat with LLM models
559
669
  agent <prompt> Chat with Fast Agent (supports image/video generation)
560
670
  app <subcommand> App-related operations (search, generation)
671
+ scan <uri> Content safety scan (image/video/audio)
672
+ status <task-id> Query task status by task ID
561
673
  <model> Test specific model generation
562
674
 
563
675
  App Subcommands:
@@ -583,11 +695,21 @@ App Generation Options:
583
695
  --template-id <id> Template ID (required)
584
696
  --params <json> Input parameters as JSON string (required)
585
697
 
698
+ Scan Options:
699
+ --risk-types <types> Risk types (default: POLITY,EROTIC,VIOLENT,CHILD)
700
+ --detected-age Enable age detection (default: false)
701
+ --api-key <key> API key (or set API_SERVICE_TOKEN env var)
702
+ --base-url <url> Base URL (default: http://proxy.sg.seaverse.dev)
703
+
586
704
  Model Generation Options:
587
705
  --api-key <key> API key (or set API_SERVICE_TOKEN env var)
588
706
  --base-url <url> Base URL (default: http://proxy.sg.seaverse.dev)
589
707
  --params <json> JSON parameters for the model
590
708
 
709
+ Status Query Options:
710
+ --api-key <key> API key (or set API_SERVICE_TOKEN env var)
711
+ --base-url <url> Base URL (default: http://proxy.sg.seaverse.dev)
712
+
591
713
  Examples:
592
714
  # Chat with LLM (non-streaming)
593
715
  seacloud llm "What is the capital of France?"
@@ -611,6 +733,18 @@ Examples:
611
733
  # Create generation task (app generation)
612
734
  seacloud app generation --template-id "d26trpte878eqsnm3bjg" --params '[{"field":"prompt1","value":"hello"}]'
613
735
 
736
+ # Query task status
737
+ seacloud status d123456789abcdef
738
+
739
+ # Scan image
740
+ seacloud scan "https://example.com/image.jpg"
741
+
742
+ # Scan video with custom risk types
743
+ seacloud scan "https://example.com/video.mp4" --risk-types EROTIC,VIOLENT
744
+
745
+ # Scan with age detection
746
+ seacloud scan "https://example.com/photo.jpg" --detected-age
747
+
614
748
  # Test model generation
615
749
  seacloud flux_1_1_pro --params '{"prompt":"a beautiful sunset"}'
616
750
 
@@ -909,6 +1043,167 @@ async function runAppSearch(templateIdsStr, args) {
909
1043
  });
910
1044
  console.log(JSON.stringify(result, null, 2));
911
1045
  }
1046
+ function detectMediaType(uri) {
1047
+ const videoExtRegex = /\.(?:mp4|avi|mkv|mov|flv|wmv|webm|m4v|3gp|ts|mts|mpeg|mpg|vob|ogv|m2ts|divx|rm|rmvb|asf|f4v)(?:[^a-z0-9]|$)/i;
1048
+ const audioExtRegex = /\.(?:mp3|wav|ogg|m4a|aac|flac|wma|m4b|m4p|m4r)(?:[^a-z0-9]|$)/i;
1049
+ if (videoExtRegex.test(uri)) {
1050
+ return { isVideo: 1, mediaType: "video" };
1051
+ }
1052
+ if (audioExtRegex.test(uri)) {
1053
+ return { isVideo: 1, mediaType: "audio" };
1054
+ }
1055
+ return { isVideo: 0, mediaType: "image" };
1056
+ }
1057
+ async function runScan(uri, args) {
1058
+ const options = {
1059
+ riskTypes: ["POLITY", "EROTIC", "VIOLENT", "CHILD"],
1060
+ detectedAge: false
1061
+ };
1062
+ for (let i = 0; i < args.length; i++) {
1063
+ const arg = args[i];
1064
+ if (arg === "--risk-types") {
1065
+ options.riskTypes = args[++i].split(",").map((t) => t.trim());
1066
+ } else if (arg === "--detected-age") {
1067
+ options.detectedAge = true;
1068
+ } else if (arg === "--api-key") {
1069
+ options.apiKey = args[++i];
1070
+ } else if (arg === "--base-url") {
1071
+ options.baseUrl = args[++i];
1072
+ }
1073
+ }
1074
+ const apiKey = options.apiKey || process.env.API_SERVICE_TOKEN || "";
1075
+ const baseUrl = options.baseUrl || process.env.SEACLOUD_BASE_URL || "http://proxy.sg.seaverse.dev";
1076
+ initSeacloud(apiKey, { baseUrl });
1077
+ const { isVideo, mediaType } = detectMediaType(uri);
1078
+ console.log(`\u8D44\u6E90\u5730\u5740: ${uri}`);
1079
+ console.log(`\u5A92\u4F53\u7C7B\u578B: ${mediaType}`);
1080
+ console.log(`\u98CE\u9669\u7C7B\u578B: ${options.riskTypes.join(", ")}`);
1081
+ console.log(`\u5E74\u9F84\u68C0\u6D4B: ${options.detectedAge ? "\u542F\u7528" : "\u7981\u7528"}`);
1082
+ console.log("");
1083
+ try {
1084
+ const result = await scan({
1085
+ uri,
1086
+ risk_types: options.riskTypes,
1087
+ detected_age: options.detectedAge ? 1 : 0,
1088
+ is_video: isVideo
1089
+ });
1090
+ console.log("\u2705 \u626B\u63CF\u5B8C\u6210!\n");
1091
+ console.log(`\u8BF7\u6C42\u72B6\u6001: ${result.ok ? "\u6210\u529F" : "\u5931\u8D25"}`);
1092
+ if (result.nsfw_level !== void 0) {
1093
+ console.log(`\u98CE\u9669\u7EA7\u522B: ${result.nsfw_level} (0-6)`);
1094
+ }
1095
+ if (result.risk_types && result.risk_types.length > 0) {
1096
+ console.log(`\u68C0\u6D4B\u5230\u7684\u98CE\u9669\u7C7B\u578B: ${result.risk_types.join(", ")}`);
1097
+ }
1098
+ if (result.age_group && result.age_group.length > 0) {
1099
+ console.log(`\u5E74\u9F84\u7EC4: ${JSON.stringify(result.age_group)}`);
1100
+ }
1101
+ if (isVideo === 1) {
1102
+ if (result.video_duration !== void 0) {
1103
+ console.log(`\u89C6\u9891\u65F6\u957F: ${result.video_duration} \u79D2`);
1104
+ }
1105
+ if (result.frame_count !== void 0) {
1106
+ console.log(`\u603B\u62BD\u5E27\u6570: ${result.frame_count}`);
1107
+ }
1108
+ if (result.frames_checked !== void 0) {
1109
+ console.log(`\u5B9E\u9645\u68C0\u6D4B\u5E27\u6570: ${result.frames_checked}`);
1110
+ }
1111
+ if (result.max_risk_frame !== void 0) {
1112
+ console.log(`\u6700\u9AD8\u98CE\u9669\u5E27\u7D22\u5F15: ${result.max_risk_frame}`);
1113
+ }
1114
+ if (result.early_exit !== void 0) {
1115
+ console.log(`\u63D0\u524D\u9000\u51FA: ${result.early_exit ? "\u662F" : "\u5426"}`);
1116
+ }
1117
+ }
1118
+ if (result.label_items && result.label_items.length > 0) {
1119
+ console.log(`
1120
+ \u68C0\u6D4B\u5230\u7684\u6807\u7B7E (${result.label_items.length} \u4E2A):`);
1121
+ result.label_items.forEach((item, i) => {
1122
+ console.log(` ${i + 1}. ${item.name}`);
1123
+ console.log(` \u98CE\u9669\u7EA7\u522B: ${item.score}, \u7C7B\u578B: ${item.risk_type}`);
1124
+ });
1125
+ }
1126
+ if (result.frame_results && result.frame_results.length > 0) {
1127
+ console.log(`
1128
+ \u5E27\u68C0\u6D4B\u7ED3\u679C (\u524D5\u5E27):`);
1129
+ result.frame_results.slice(0, 5).forEach((frame) => {
1130
+ console.log(` \u5E27 ${frame.frame_index}: \u98CE\u9669\u7EA7\u522B ${frame.nsfw_level}, \u6807\u7B7E\u6570 ${frame.label_items.length}`);
1131
+ });
1132
+ if (result.frame_results.length > 5) {
1133
+ console.log(` ... \u8FD8\u6709 ${result.frame_results.length - 5} \u5E27\u7ED3\u679C`);
1134
+ }
1135
+ }
1136
+ if (result.error) {
1137
+ console.log(`
1138
+ \u274C \u9519\u8BEF: ${result.error}`);
1139
+ }
1140
+ } catch (error) {
1141
+ console.error("\n\u274C \u626B\u63CF\u5931\u8D25:", error.message);
1142
+ process.exit(1);
1143
+ }
1144
+ }
1145
+ async function runTaskStatus(taskId, args) {
1146
+ const options = {};
1147
+ for (let i = 0; i < args.length; i++) {
1148
+ const arg = args[i];
1149
+ if (arg === "--api-key") options.apiKey = args[++i];
1150
+ else if (arg === "--base-url") options.baseUrl = args[++i];
1151
+ }
1152
+ const apiKey = options.apiKey || process.env.API_SERVICE_TOKEN || "";
1153
+ const baseUrl = options.baseUrl || process.env.SEACLOUD_BASE_URL || "http://proxy.sg.seaverse.dev";
1154
+ const client = new SeacloudClient({ apiKey, baseUrl });
1155
+ console.log(`Querying task status...`);
1156
+ console.log(`Task ID: ${taskId}`);
1157
+ console.log(`Base URL: ${baseUrl}
1158
+ `);
1159
+ try {
1160
+ const result = await client.getTaskStatus("/model/v1/generation", taskId);
1161
+ console.log(`Status: ${result.status}`);
1162
+ console.log(`Task ID: ${result.id}
1163
+ `);
1164
+ if (result.status === "completed") {
1165
+ console.log("\u2705 Task completed successfully!\n");
1166
+ console.log("Output:");
1167
+ console.log(JSON.stringify(result.output, null, 2));
1168
+ if (result.output) {
1169
+ const urls = [];
1170
+ for (const item of result.output) {
1171
+ if (item.content) {
1172
+ for (const resource of item.content) {
1173
+ if (resource.url) {
1174
+ urls.push(resource.url);
1175
+ }
1176
+ }
1177
+ }
1178
+ }
1179
+ if (urls.length > 0) {
1180
+ console.log("\nGenerated URLs:");
1181
+ urls.forEach((url, i) => {
1182
+ console.log(` ${i + 1}. ${url}`);
1183
+ });
1184
+ }
1185
+ }
1186
+ } else if (result.status === "failed") {
1187
+ console.log("\u274C Task failed!\n");
1188
+ console.log("Error:", JSON.stringify(result.error, null, 2));
1189
+ } else if (result.status === "processing") {
1190
+ console.log("\u23F3 Task is still processing...");
1191
+ console.log("Please check again later.");
1192
+ } else if (result.status === "pending") {
1193
+ console.log("\u23F3 Task is pending...");
1194
+ console.log("Please check again later.");
1195
+ } else {
1196
+ console.log("Full result:");
1197
+ console.log(JSON.stringify(result, null, 2));
1198
+ }
1199
+ } catch (error) {
1200
+ console.error("\nError querying task status:", error.message);
1201
+ if (error.statusCode) {
1202
+ console.error("HTTP Status Code:", error.statusCode);
1203
+ }
1204
+ process.exit(1);
1205
+ }
1206
+ }
912
1207
  async function main() {
913
1208
  const args = process.argv.slice(2);
914
1209
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
@@ -931,6 +1226,20 @@ async function main() {
931
1226
  process.exit(1);
932
1227
  }
933
1228
  await runAgent(args[1], args.slice(2));
1229
+ } else if (command === "scan") {
1230
+ if (args.length < 2) {
1231
+ console.error("Error: URI required for scan command");
1232
+ console.log('Usage: seacloud scan "<uri>" [options]');
1233
+ process.exit(1);
1234
+ }
1235
+ await runScan(args[1], args.slice(2));
1236
+ } else if (command === "status") {
1237
+ if (args.length < 2) {
1238
+ console.error("Error: task ID required for status command");
1239
+ console.log("Usage: seacloud status <task-id> [options]");
1240
+ process.exit(1);
1241
+ }
1242
+ await runTaskStatus(args[1], args.slice(2));
934
1243
  } else if (command === "app") {
935
1244
  const subcommand = args[1];
936
1245
  if (!subcommand) {