seacloud-sdk 0.10.3 → 0.11.2

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,19 +73,24 @@ 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
96
  timeout: options.timeout || 3e4,
@@ -87,7 +136,7 @@ var SeacloudClient = class {
87
136
  */
88
137
  async createTask(endpoint, body) {
89
138
  const url = `${this.config.baseUrl}${endpoint}`;
90
- const currentToken = getApiToken(this.providedApiKey);
139
+ const currentToken = await getApiToken(this.providedApiKey);
91
140
  const controller = new AbortController();
92
141
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
93
142
  try {
@@ -134,7 +183,7 @@ var SeacloudClient = class {
134
183
  */
135
184
  async getTaskStatus(endpoint, taskId) {
136
185
  const url = `${this.config.baseUrl}${endpoint}/task/${taskId}`;
137
- const currentToken = getApiToken(this.providedApiKey);
186
+ const currentToken = await getApiToken(this.providedApiKey);
138
187
  const controller = new AbortController();
139
188
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
140
189
  try {
@@ -232,6 +281,7 @@ async function llmChatCompletions(params) {
232
281
  const client = getClient();
233
282
  const config = client.getConfig();
234
283
  const url = `${config.baseUrl}/llm/chat/completions`;
284
+ const token = await getApiToken(config.apiKey);
235
285
  const controller = new AbortController();
236
286
  const timeoutId = setTimeout(() => controller.abort(), config.timeout);
237
287
  try {
@@ -239,7 +289,7 @@ async function llmChatCompletions(params) {
239
289
  method: "POST",
240
290
  headers: {
241
291
  "Content-Type": "application/json",
242
- "Authorization": `Bearer ${config.apiKey}`
292
+ "Authorization": `Bearer ${token}`
243
293
  },
244
294
  body: JSON.stringify(params),
245
295
  signal: controller.signal
@@ -545,6 +595,73 @@ async function appSearch(params) {
545
595
  throw error;
546
596
  }
547
597
  }
598
+
599
+ // src/api/scan.ts
600
+ async function scan(params) {
601
+ if (!params.uri && !params.img_base64) {
602
+ throw new SeacloudError("\u5FC5\u987B\u63D0\u4F9B uri \u6216 img_base64 \u4E2D\u7684\u81F3\u5C11\u4E00\u4E2A\u53C2\u6570");
603
+ }
604
+ if (params.is_video === 1 && params.img_base64) {
605
+ throw new SeacloudError("\u89C6\u9891\u68C0\u6D4B\u4E0D\u652F\u6301 base64 \u8F93\u5165\uFF0C\u5FC5\u987B\u4F7F\u7528 uri \u53C2\u6570");
606
+ }
607
+ if (!params.risk_types || params.risk_types.length === 0) {
608
+ throw new SeacloudError("\u5FC5\u987B\u63D0\u4F9B\u81F3\u5C11\u4E00\u4E2A\u98CE\u9669\u7C7B\u578B");
609
+ }
610
+ const client = getClient();
611
+ const config = client.getConfig();
612
+ const url = `${config.baseUrl}/scan`;
613
+ const token = await getApiToken(config.apiKey);
614
+ const controller = new AbortController();
615
+ const timeoutId = setTimeout(() => controller.abort(), config.timeout);
616
+ try {
617
+ const response = await config.fetch(url, {
618
+ method: "POST",
619
+ headers: {
620
+ "Content-Type": "application/json",
621
+ "Authorization": `Bearer ${token}`
622
+ },
623
+ body: JSON.stringify(params),
624
+ signal: controller.signal
625
+ });
626
+ clearTimeout(timeoutId);
627
+ if (!response.ok) {
628
+ const errorBody = await response.text();
629
+ throw new SeacloudError(
630
+ `HTTP ${response.status}: ${errorBody}`,
631
+ response.status,
632
+ errorBody
633
+ );
634
+ }
635
+ const responseData = await response.json();
636
+ let result;
637
+ if (responseData.success !== void 0 && responseData.data) {
638
+ result = responseData.data;
639
+ } else {
640
+ result = responseData;
641
+ }
642
+ if (!result.ok) {
643
+ throw new SeacloudError(
644
+ `\u5185\u5BB9\u5B89\u5168\u68C0\u6D4B\u4E1A\u52A1\u9519\u8BEF: ${result.error || "\u672A\u77E5\u9519\u8BEF"}`,
645
+ void 0,
646
+ result
647
+ );
648
+ }
649
+ return result;
650
+ } catch (error) {
651
+ clearTimeout(timeoutId);
652
+ if (error instanceof SeacloudError) {
653
+ throw error;
654
+ }
655
+ if (error.name === "AbortError") {
656
+ throw new SeacloudError(`Request timeout after ${config.timeout}ms`);
657
+ }
658
+ throw new SeacloudError(
659
+ `Request failed: ${error.message}`,
660
+ void 0,
661
+ error
662
+ );
663
+ }
664
+ }
548
665
  var __filename$1 = fileURLToPath(import.meta.url);
549
666
  dirname(__filename$1);
550
667
  function showHelp() {
@@ -558,6 +675,8 @@ Commands:
558
675
  llm <prompt> Chat with LLM models
559
676
  agent <prompt> Chat with Fast Agent (supports image/video generation)
560
677
  app <subcommand> App-related operations (search, generation)
678
+ scan <uri> Content safety scan (image/video/audio)
679
+ status <task-id> Query task status by task ID
561
680
  <model> Test specific model generation
562
681
 
563
682
  App Subcommands:
@@ -583,11 +702,21 @@ App Generation Options:
583
702
  --template-id <id> Template ID (required)
584
703
  --params <json> Input parameters as JSON string (required)
585
704
 
705
+ Scan Options:
706
+ --risk-types <types> Risk types (default: POLITY,EROTIC,VIOLENT,CHILD)
707
+ --detected-age Enable age detection (default: false)
708
+ --api-key <key> API key (or set API_SERVICE_TOKEN env var)
709
+ --base-url <url> Base URL (default: http://proxy.sg.seaverse.dev)
710
+
586
711
  Model Generation Options:
587
712
  --api-key <key> API key (or set API_SERVICE_TOKEN env var)
588
713
  --base-url <url> Base URL (default: http://proxy.sg.seaverse.dev)
589
714
  --params <json> JSON parameters for the model
590
715
 
716
+ Status Query Options:
717
+ --api-key <key> API key (or set API_SERVICE_TOKEN env var)
718
+ --base-url <url> Base URL (default: http://proxy.sg.seaverse.dev)
719
+
591
720
  Examples:
592
721
  # Chat with LLM (non-streaming)
593
722
  seacloud llm "What is the capital of France?"
@@ -611,6 +740,18 @@ Examples:
611
740
  # Create generation task (app generation)
612
741
  seacloud app generation --template-id "d26trpte878eqsnm3bjg" --params '[{"field":"prompt1","value":"hello"}]'
613
742
 
743
+ # Query task status
744
+ seacloud status d123456789abcdef
745
+
746
+ # Scan image
747
+ seacloud scan "https://example.com/image.jpg"
748
+
749
+ # Scan video with custom risk types
750
+ seacloud scan "https://example.com/video.mp4" --risk-types EROTIC,VIOLENT
751
+
752
+ # Scan with age detection
753
+ seacloud scan "https://example.com/photo.jpg" --detected-age
754
+
614
755
  # Test model generation
615
756
  seacloud flux_1_1_pro --params '{"prompt":"a beautiful sunset"}'
616
757
 
@@ -909,6 +1050,167 @@ async function runAppSearch(templateIdsStr, args) {
909
1050
  });
910
1051
  console.log(JSON.stringify(result, null, 2));
911
1052
  }
1053
+ function detectMediaType(uri) {
1054
+ 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;
1055
+ const audioExtRegex = /\.(?:mp3|wav|ogg|m4a|aac|flac|wma|m4b|m4p|m4r)(?:[^a-z0-9]|$)/i;
1056
+ if (videoExtRegex.test(uri)) {
1057
+ return { isVideo: 1, mediaType: "video" };
1058
+ }
1059
+ if (audioExtRegex.test(uri)) {
1060
+ return { isVideo: 1, mediaType: "audio" };
1061
+ }
1062
+ return { isVideo: 0, mediaType: "image" };
1063
+ }
1064
+ async function runScan(uri, args) {
1065
+ const options = {
1066
+ riskTypes: ["POLITY", "EROTIC", "VIOLENT", "CHILD"],
1067
+ detectedAge: false
1068
+ };
1069
+ for (let i = 0; i < args.length; i++) {
1070
+ const arg = args[i];
1071
+ if (arg === "--risk-types") {
1072
+ options.riskTypes = args[++i].split(",").map((t) => t.trim());
1073
+ } else if (arg === "--detected-age") {
1074
+ options.detectedAge = true;
1075
+ } else if (arg === "--api-key") {
1076
+ options.apiKey = args[++i];
1077
+ } else if (arg === "--base-url") {
1078
+ options.baseUrl = args[++i];
1079
+ }
1080
+ }
1081
+ const apiKey = options.apiKey || process.env.API_SERVICE_TOKEN || "";
1082
+ const baseUrl = options.baseUrl || process.env.SEACLOUD_BASE_URL || "http://proxy.sg.seaverse.dev";
1083
+ initSeacloud(apiKey, { baseUrl });
1084
+ const { isVideo, mediaType } = detectMediaType(uri);
1085
+ console.log(`\u8D44\u6E90\u5730\u5740: ${uri}`);
1086
+ console.log(`\u5A92\u4F53\u7C7B\u578B: ${mediaType}`);
1087
+ console.log(`\u98CE\u9669\u7C7B\u578B: ${options.riskTypes.join(", ")}`);
1088
+ console.log(`\u5E74\u9F84\u68C0\u6D4B: ${options.detectedAge ? "\u542F\u7528" : "\u7981\u7528"}`);
1089
+ console.log("");
1090
+ try {
1091
+ const result = await scan({
1092
+ uri,
1093
+ risk_types: options.riskTypes,
1094
+ detected_age: options.detectedAge ? 1 : 0,
1095
+ is_video: isVideo
1096
+ });
1097
+ console.log("\u2705 \u626B\u63CF\u5B8C\u6210!\n");
1098
+ console.log(`\u8BF7\u6C42\u72B6\u6001: ${result.ok ? "\u6210\u529F" : "\u5931\u8D25"}`);
1099
+ if (result.nsfw_level !== void 0) {
1100
+ console.log(`\u98CE\u9669\u7EA7\u522B: ${result.nsfw_level} (0-6)`);
1101
+ }
1102
+ if (result.risk_types && result.risk_types.length > 0) {
1103
+ console.log(`\u68C0\u6D4B\u5230\u7684\u98CE\u9669\u7C7B\u578B: ${result.risk_types.join(", ")}`);
1104
+ }
1105
+ if (result.age_group && result.age_group.length > 0) {
1106
+ console.log(`\u5E74\u9F84\u7EC4: ${JSON.stringify(result.age_group)}`);
1107
+ }
1108
+ if (isVideo === 1) {
1109
+ if (result.video_duration !== void 0) {
1110
+ console.log(`\u89C6\u9891\u65F6\u957F: ${result.video_duration} \u79D2`);
1111
+ }
1112
+ if (result.frame_count !== void 0) {
1113
+ console.log(`\u603B\u62BD\u5E27\u6570: ${result.frame_count}`);
1114
+ }
1115
+ if (result.frames_checked !== void 0) {
1116
+ console.log(`\u5B9E\u9645\u68C0\u6D4B\u5E27\u6570: ${result.frames_checked}`);
1117
+ }
1118
+ if (result.max_risk_frame !== void 0) {
1119
+ console.log(`\u6700\u9AD8\u98CE\u9669\u5E27\u7D22\u5F15: ${result.max_risk_frame}`);
1120
+ }
1121
+ if (result.early_exit !== void 0) {
1122
+ console.log(`\u63D0\u524D\u9000\u51FA: ${result.early_exit ? "\u662F" : "\u5426"}`);
1123
+ }
1124
+ }
1125
+ if (result.label_items && result.label_items.length > 0) {
1126
+ console.log(`
1127
+ \u68C0\u6D4B\u5230\u7684\u6807\u7B7E (${result.label_items.length} \u4E2A):`);
1128
+ result.label_items.forEach((item, i) => {
1129
+ console.log(` ${i + 1}. ${item.name}`);
1130
+ console.log(` \u98CE\u9669\u7EA7\u522B: ${item.score}, \u7C7B\u578B: ${item.risk_type}`);
1131
+ });
1132
+ }
1133
+ if (result.frame_results && result.frame_results.length > 0) {
1134
+ console.log(`
1135
+ \u5E27\u68C0\u6D4B\u7ED3\u679C (\u524D5\u5E27):`);
1136
+ result.frame_results.slice(0, 5).forEach((frame) => {
1137
+ console.log(` \u5E27 ${frame.frame_index}: \u98CE\u9669\u7EA7\u522B ${frame.nsfw_level}, \u6807\u7B7E\u6570 ${frame.label_items.length}`);
1138
+ });
1139
+ if (result.frame_results.length > 5) {
1140
+ console.log(` ... \u8FD8\u6709 ${result.frame_results.length - 5} \u5E27\u7ED3\u679C`);
1141
+ }
1142
+ }
1143
+ if (result.error) {
1144
+ console.log(`
1145
+ \u274C \u9519\u8BEF: ${result.error}`);
1146
+ }
1147
+ } catch (error) {
1148
+ console.error("\n\u274C \u626B\u63CF\u5931\u8D25:", error.message);
1149
+ process.exit(1);
1150
+ }
1151
+ }
1152
+ async function runTaskStatus(taskId, args) {
1153
+ const options = {};
1154
+ for (let i = 0; i < args.length; i++) {
1155
+ const arg = args[i];
1156
+ if (arg === "--api-key") options.apiKey = args[++i];
1157
+ else if (arg === "--base-url") options.baseUrl = args[++i];
1158
+ }
1159
+ const apiKey = options.apiKey || process.env.API_SERVICE_TOKEN || "";
1160
+ const baseUrl = options.baseUrl || process.env.SEACLOUD_BASE_URL || "http://proxy.sg.seaverse.dev";
1161
+ const client = new SeacloudClient({ apiKey, baseUrl });
1162
+ console.log(`Querying task status...`);
1163
+ console.log(`Task ID: ${taskId}`);
1164
+ console.log(`Base URL: ${baseUrl}
1165
+ `);
1166
+ try {
1167
+ const result = await client.getTaskStatus("/model/v1/generation", taskId);
1168
+ console.log(`Status: ${result.status}`);
1169
+ console.log(`Task ID: ${result.id}
1170
+ `);
1171
+ if (result.status === "completed") {
1172
+ console.log("\u2705 Task completed successfully!\n");
1173
+ console.log("Output:");
1174
+ console.log(JSON.stringify(result.output, null, 2));
1175
+ if (result.output) {
1176
+ const urls = [];
1177
+ for (const item of result.output) {
1178
+ if (item.content) {
1179
+ for (const resource of item.content) {
1180
+ if (resource.url) {
1181
+ urls.push(resource.url);
1182
+ }
1183
+ }
1184
+ }
1185
+ }
1186
+ if (urls.length > 0) {
1187
+ console.log("\nGenerated URLs:");
1188
+ urls.forEach((url, i) => {
1189
+ console.log(` ${i + 1}. ${url}`);
1190
+ });
1191
+ }
1192
+ }
1193
+ } else if (result.status === "failed") {
1194
+ console.log("\u274C Task failed!\n");
1195
+ console.log("Error:", JSON.stringify(result.error, null, 2));
1196
+ } else if (result.status === "processing") {
1197
+ console.log("\u23F3 Task is still processing...");
1198
+ console.log("Please check again later.");
1199
+ } else if (result.status === "pending") {
1200
+ console.log("\u23F3 Task is pending...");
1201
+ console.log("Please check again later.");
1202
+ } else {
1203
+ console.log("Full result:");
1204
+ console.log(JSON.stringify(result, null, 2));
1205
+ }
1206
+ } catch (error) {
1207
+ console.error("\nError querying task status:", error.message);
1208
+ if (error.statusCode) {
1209
+ console.error("HTTP Status Code:", error.statusCode);
1210
+ }
1211
+ process.exit(1);
1212
+ }
1213
+ }
912
1214
  async function main() {
913
1215
  const args = process.argv.slice(2);
914
1216
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
@@ -931,6 +1233,20 @@ async function main() {
931
1233
  process.exit(1);
932
1234
  }
933
1235
  await runAgent(args[1], args.slice(2));
1236
+ } else if (command === "scan") {
1237
+ if (args.length < 2) {
1238
+ console.error("Error: URI required for scan command");
1239
+ console.log('Usage: seacloud scan "<uri>" [options]');
1240
+ process.exit(1);
1241
+ }
1242
+ await runScan(args[1], args.slice(2));
1243
+ } else if (command === "status") {
1244
+ if (args.length < 2) {
1245
+ console.error("Error: task ID required for status command");
1246
+ console.log("Usage: seacloud status <task-id> [options]");
1247
+ process.exit(1);
1248
+ }
1249
+ await runTaskStatus(args[1], args.slice(2));
934
1250
  } else if (command === "app") {
935
1251
  const subcommand = args[1];
936
1252
  if (!subcommand) {