sa2kit 3.3.0 → 3.6.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.
Files changed (143) hide show
  1. package/dist/{chunk-LGVPFYPR.mjs → chunk-4H3SGG3T.mjs} +27 -3
  2. package/dist/chunk-4H3SGG3T.mjs.map +1 -0
  3. package/dist/{chunk-KVYHCGRY.js → chunk-7Z3XR2Y4.js} +552 -263
  4. package/dist/chunk-7Z3XR2Y4.js.map +1 -0
  5. package/dist/{chunk-5H2FICSO.js → chunk-LBMUIWJR.js} +27 -3
  6. package/dist/chunk-LBMUIWJR.js.map +1 -0
  7. package/dist/chunk-XPY45Y75.js +1143 -0
  8. package/dist/chunk-XPY45Y75.js.map +1 -0
  9. package/dist/{chunk-YIRPPMCN.mjs → chunk-XSTMLLJV.mjs} +474 -198
  10. package/dist/chunk-XSTMLLJV.mjs.map +1 -0
  11. package/dist/chunk-ZJLS5JU5.mjs +1090 -0
  12. package/dist/chunk-ZJLS5JU5.mjs.map +1 -0
  13. package/dist/common/aiApi/client/index.d.mts +71 -0
  14. package/dist/common/aiApi/client/index.d.ts +71 -0
  15. package/dist/common/aiApi/client/index.js +165 -0
  16. package/dist/common/aiApi/client/index.js.map +1 -0
  17. package/dist/common/aiApi/client/index.mjs +151 -0
  18. package/dist/common/aiApi/client/index.mjs.map +1 -0
  19. package/dist/common/aiApi/index.d.mts +184 -0
  20. package/dist/common/aiApi/index.d.ts +184 -0
  21. package/dist/common/aiApi/index.js +217 -0
  22. package/dist/common/aiApi/index.mjs +4 -0
  23. package/dist/common/aiApi/server/index.d.mts +3 -0
  24. package/dist/common/aiApi/server/index.d.ts +3 -0
  25. package/dist/common/aiApi/server/index.js +217 -0
  26. package/dist/common/aiApi/server/index.mjs +4 -0
  27. package/dist/common/auth/server/index.js +8 -8
  28. package/dist/common/auth/server/index.mjs +2 -2
  29. package/dist/common/components/index.js +176 -177
  30. package/dist/common/components/index.mjs +1 -2
  31. package/dist/common/config/bootstrap/index.d.mts +24 -0
  32. package/dist/common/config/bootstrap/index.d.ts +24 -0
  33. package/dist/common/config/bootstrap/index.js +15 -15
  34. package/dist/common/config/bootstrap/index.mjs +1 -1
  35. package/dist/common/config/server/index.js +14 -14
  36. package/dist/common/config/server/index.mjs +1 -1
  37. package/dist/common/index.js +2 -3
  38. package/dist/common/index.mjs +1 -2
  39. package/dist/index.d.mts +314 -154
  40. package/dist/index.d.ts +314 -154
  41. package/dist/index.js +1055 -369
  42. package/dist/index.js.map +1 -1
  43. package/dist/index.mjs +1005 -360
  44. package/dist/index.mjs.map +1 -1
  45. package/dist/types-CiqMQ-uu.d.mts +166 -0
  46. package/dist/types-CiqMQ-uu.d.ts +166 -0
  47. package/package.json +15 -50
  48. package/dist/chunk-3R6JHA6D.js +0 -120
  49. package/dist/chunk-3R6JHA6D.js.map +0 -1
  50. package/dist/chunk-4PJM4752.js +0 -4
  51. package/dist/chunk-4PJM4752.js.map +0 -1
  52. package/dist/chunk-5H2FICSO.js.map +0 -1
  53. package/dist/chunk-7PMT4L4I.js +0 -324
  54. package/dist/chunk-7PMT4L4I.js.map +0 -1
  55. package/dist/chunk-FY2X3LYR.mjs +0 -3
  56. package/dist/chunk-FY2X3LYR.mjs.map +0 -1
  57. package/dist/chunk-GS4SAW25.mjs +0 -116
  58. package/dist/chunk-GS4SAW25.mjs.map +0 -1
  59. package/dist/chunk-HL4H2HF6.js +0 -279
  60. package/dist/chunk-HL4H2HF6.js.map +0 -1
  61. package/dist/chunk-IJIQUMAK.mjs +0 -272
  62. package/dist/chunk-IJIQUMAK.mjs.map +0 -1
  63. package/dist/chunk-KVYHCGRY.js.map +0 -1
  64. package/dist/chunk-LGVPFYPR.mjs.map +0 -1
  65. package/dist/chunk-MMDSZIXD.mjs +0 -286
  66. package/dist/chunk-MMDSZIXD.mjs.map +0 -1
  67. package/dist/chunk-N2O3OX5Y.mjs +0 -243
  68. package/dist/chunk-N2O3OX5Y.mjs.map +0 -1
  69. package/dist/chunk-RRQ2X26Z.js +0 -106
  70. package/dist/chunk-RRQ2X26Z.js.map +0 -1
  71. package/dist/chunk-RVNQI6BI.js +0 -249
  72. package/dist/chunk-RVNQI6BI.js.map +0 -1
  73. package/dist/chunk-UJUWDF7M.mjs +0 -336
  74. package/dist/chunk-UJUWDF7M.mjs.map +0 -1
  75. package/dist/chunk-VCKXK6V5.js +0 -345
  76. package/dist/chunk-VCKXK6V5.js.map +0 -1
  77. package/dist/chunk-VIEXDTNF.mjs +0 -100
  78. package/dist/chunk-VIEXDTNF.mjs.map +0 -1
  79. package/dist/chunk-YIRPPMCN.mjs.map +0 -1
  80. package/dist/common/ai/llm/core/index.d.mts +0 -70
  81. package/dist/common/ai/llm/core/index.d.ts +0 -70
  82. package/dist/common/ai/llm/core/index.js +0 -54
  83. package/dist/common/ai/llm/core/index.mjs +0 -5
  84. package/dist/common/ai/llm/electron/index.d.mts +0 -6
  85. package/dist/common/ai/llm/electron/index.d.ts +0 -6
  86. package/dist/common/ai/llm/electron/index.js +0 -67
  87. package/dist/common/ai/llm/electron/index.mjs +0 -10
  88. package/dist/common/ai/llm/index.d.mts +0 -3
  89. package/dist/common/ai/llm/index.d.ts +0 -3
  90. package/dist/common/ai/llm/index.js +0 -54
  91. package/dist/common/ai/llm/index.js.map +0 -1
  92. package/dist/common/ai/llm/index.mjs +0 -5
  93. package/dist/common/ai/llm/index.mjs.map +0 -1
  94. package/dist/common/ai/llm/miniapp/index.d.mts +0 -6
  95. package/dist/common/ai/llm/miniapp/index.d.ts +0 -6
  96. package/dist/common/ai/llm/miniapp/index.js +0 -59
  97. package/dist/common/ai/llm/miniapp/index.js.map +0 -1
  98. package/dist/common/ai/llm/miniapp/index.mjs +0 -6
  99. package/dist/common/ai/llm/miniapp/index.mjs.map +0 -1
  100. package/dist/common/ai/llm/rn/index.d.mts +0 -6
  101. package/dist/common/ai/llm/rn/index.d.ts +0 -6
  102. package/dist/common/ai/llm/rn/index.js +0 -59
  103. package/dist/common/ai/llm/rn/index.js.map +0 -1
  104. package/dist/common/ai/llm/rn/index.mjs +0 -6
  105. package/dist/common/ai/llm/rn/index.mjs.map +0 -1
  106. package/dist/common/ai/llm/ui/electron/index.d.mts +0 -5
  107. package/dist/common/ai/llm/ui/electron/index.d.ts +0 -5
  108. package/dist/common/ai/llm/ui/electron/index.js +0 -22
  109. package/dist/common/ai/llm/ui/electron/index.js.map +0 -1
  110. package/dist/common/ai/llm/ui/electron/index.mjs +0 -9
  111. package/dist/common/ai/llm/ui/electron/index.mjs.map +0 -1
  112. package/dist/common/ai/llm/ui/miniapp/index.d.mts +0 -9
  113. package/dist/common/ai/llm/ui/miniapp/index.d.ts +0 -9
  114. package/dist/common/ai/llm/ui/miniapp/index.js +0 -14
  115. package/dist/common/ai/llm/ui/miniapp/index.js.map +0 -1
  116. package/dist/common/ai/llm/ui/miniapp/index.mjs +0 -5
  117. package/dist/common/ai/llm/ui/miniapp/index.mjs.map +0 -1
  118. package/dist/common/ai/llm/ui/rn/index.d.mts +0 -9
  119. package/dist/common/ai/llm/ui/rn/index.d.ts +0 -9
  120. package/dist/common/ai/llm/ui/rn/index.js +0 -14
  121. package/dist/common/ai/llm/ui/rn/index.js.map +0 -1
  122. package/dist/common/ai/llm/ui/rn/index.mjs +0 -5
  123. package/dist/common/ai/llm/ui/rn/index.mjs.map +0 -1
  124. package/dist/common/ai/llm/ui/web/index.d.mts +0 -15
  125. package/dist/common/ai/llm/ui/web/index.d.ts +0 -15
  126. package/dist/common/ai/llm/ui/web/index.js +0 -21
  127. package/dist/common/ai/llm/ui/web/index.js.map +0 -1
  128. package/dist/common/ai/llm/ui/web/index.mjs +0 -8
  129. package/dist/common/ai/llm/ui/web/index.mjs.map +0 -1
  130. package/dist/common/ai/llm/web/index.d.mts +0 -6
  131. package/dist/common/ai/llm/web/index.d.ts +0 -6
  132. package/dist/common/ai/llm/web/index.js +0 -66
  133. package/dist/common/ai/llm/web/index.js.map +0 -1
  134. package/dist/common/ai/llm/web/index.mjs +0 -9
  135. package/dist/common/ai/llm/web/index.mjs.map +0 -1
  136. package/dist/types-B2rs_jq1.d.mts +0 -38
  137. package/dist/types-DgACCUpT.d.ts +0 -122
  138. package/dist/types-DwS2Eg0q.d.ts +0 -38
  139. package/dist/types-LU_BGSzk.d.mts +0 -122
  140. /package/dist/common/{ai/llm/core → aiApi}/index.js.map +0 -0
  141. /package/dist/common/{ai/llm/core → aiApi}/index.mjs.map +0 -0
  142. /package/dist/common/{ai/llm/electron → aiApi/server}/index.js.map +0 -0
  143. /package/dist/common/{ai/llm/electron → aiApi/server}/index.mjs.map +0 -0
package/dist/index.js CHANGED
@@ -37541,8 +37541,66 @@ AI\u56DE\u5E94\uFF1A"`;
37541
37541
  ))));
37542
37542
  };
37543
37543
 
37544
- // src/common/ai/llm/client/request.ts
37545
- var requestJson = async (options) => {
37544
+ // src/common/aiApi/types.ts
37545
+ var CORE_LLM_COMPLETION_TASK_ID = "core.llmCompletion";
37546
+ var CORE_STRUCTURED_MULTIMODAL_TASK_ID = "core.structuredMultimodal";
37547
+ var CORE_CONNECTIVITY_TEST_TASK_ID = "core.connectivityTest";
37548
+
37549
+ // src/common/aiApi/resolveConfig.ts
37550
+ var DEFAULT_BASE_URL = "https://api.openai.com/v1";
37551
+ var DEFAULT_TEXT_MODEL = "gpt-4o-mini";
37552
+ var DEFAULT_VISION_MODEL = "gpt-4o-mini";
37553
+ var DEFAULT_AUDIO_MODEL = "whisper-1";
37554
+ var DEFAULT_AUDIO_STRATEGY = "auto";
37555
+ var DEFAULT_TIMEOUT_MS = 6e4;
37556
+ var DEFAULT_MAX_IMAGE_BYTES = 5 * 1024 * 1024;
37557
+ var DEFAULT_MAX_AUDIO_BYTES = 25 * 1024 * 1024;
37558
+ function readEnv(name) {
37559
+ if (typeof process === "undefined" || !process.env) {
37560
+ return "";
37561
+ }
37562
+ return process.env[name]?.trim() ?? "";
37563
+ }
37564
+ function resolveAiConnectionConfig(...sources) {
37565
+ const merged = {};
37566
+ for (const source of sources) {
37567
+ if (!source) continue;
37568
+ Object.assign(merged, source);
37569
+ }
37570
+ const envApiKey = readEnv("AI_API_KEY") || readEnv("OPENAI_API_KEY");
37571
+ const apiKey = merged.apiKey?.trim() || envApiKey;
37572
+ if (!apiKey) {
37573
+ return null;
37574
+ }
37575
+ const baseUrl = merged.baseUrl?.trim() || readEnv("AI_BASE_URL") || DEFAULT_BASE_URL;
37576
+ const visionModel = merged.visionModel?.trim() || readEnv("AI_VISION_MODEL") || merged.model?.trim() || DEFAULT_VISION_MODEL;
37577
+ const textModel = merged.textModel?.trim() || readEnv("AI_TEXT_MODEL") || merged.model?.trim() || visionModel || DEFAULT_TEXT_MODEL;
37578
+ const audioModel = merged.audioModel?.trim() || readEnv("AI_AUDIO_MODEL") || DEFAULT_AUDIO_MODEL;
37579
+ const envAudioStrategy = readEnv("AI_AUDIO_STRATEGY");
37580
+ const audioStrategy = merged.audioStrategy ?? (envAudioStrategy || DEFAULT_AUDIO_STRATEGY);
37581
+ return {
37582
+ apiKey,
37583
+ baseUrl,
37584
+ model: merged.model?.trim() || textModel,
37585
+ textModel,
37586
+ visionModel,
37587
+ audioModel,
37588
+ audioStrategy,
37589
+ timeoutMs: merged.timeoutMs ?? Number(readEnv("AI_TIMEOUT_MS") || DEFAULT_TIMEOUT_MS),
37590
+ maxImageBytes: merged.maxImageBytes ?? Number(readEnv("AI_MAX_IMAGE_BYTES") || DEFAULT_MAX_IMAGE_BYTES),
37591
+ maxAudioBytes: merged.maxAudioBytes ?? Number(readEnv("AI_MAX_AUDIO_BYTES") || DEFAULT_MAX_AUDIO_BYTES)
37592
+ };
37593
+ }
37594
+ function requireAiConnectionConfig(...sources) {
37595
+ const config = resolveAiConnectionConfig(...sources);
37596
+ if (!config) {
37597
+ throw new Error("\u672A\u914D\u7F6E AI API Key\uFF0C\u8BF7\u4F20\u5165 connection / clientSettings \u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF AI_API_KEY");
37598
+ }
37599
+ return config;
37600
+ }
37601
+
37602
+ // src/common/aiApi/requestJson.ts
37603
+ async function requestJson(options) {
37546
37604
  const { url, method = "POST", headers = {}, body, timeoutMs, requestAdapter } = options;
37547
37605
  if (requestAdapter) {
37548
37606
  return requestAdapter.request({
@@ -37560,7 +37618,7 @@ var requestJson = async (options) => {
37560
37618
  const response = await fetch(url, {
37561
37619
  method,
37562
37620
  headers,
37563
- body: body ? JSON.stringify(body) : void 0,
37621
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
37564
37622
  signal: controller?.signal
37565
37623
  });
37566
37624
  const text3 = await response.text();
@@ -37573,8 +37631,9 @@ var requestJson = async (options) => {
37573
37631
  }
37574
37632
  }
37575
37633
  if (!response.ok) {
37576
- const errorMessage2 = data?.error?.message || data?.error || data?.message || `Request failed with status ${response.status}`;
37577
- const error = new Error(errorMessage2);
37634
+ const record = data;
37635
+ const errorMessage2 = (typeof record?.error === "object" ? record.error?.message : record?.error) || record?.message || `Request failed with status ${response.status}`;
37636
+ const error = new Error(String(errorMessage2));
37578
37637
  error.status = response.status;
37579
37638
  error.data = data;
37580
37639
  throw error;
@@ -37585,404 +37644,990 @@ var requestJson = async (options) => {
37585
37644
  clearTimeout(timeoutId);
37586
37645
  }
37587
37646
  }
37588
- };
37647
+ }
37589
37648
 
37590
- // src/common/ai/llm/providers/openai-compatible.ts
37649
+ // src/common/aiApi/callChat.ts
37591
37650
  var DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
37592
- var DEFAULT_OPENAI_MODEL = "gpt-3.5-turbo";
37593
- var joinUrl = (base, path) => {
37594
- const trimmedBase = base.replace(/\/+$/, "");
37595
- const trimmedPath = path.replace(/^\/+/, "");
37596
- return `${trimmedBase}/${trimmedPath}`;
37597
- };
37598
- var normalizeToolCalls = (toolCalls) => {
37599
- if (!toolCalls || !toolCalls.length) {
37600
- return void 0;
37651
+ var DEFAULT_TEXT_MODEL2 = "gpt-4o-mini";
37652
+ function joinUrl(base, path) {
37653
+ return `${base.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
37654
+ }
37655
+ function resolveMessages(input) {
37656
+ if (input.messages?.length) {
37657
+ return input.messages;
37601
37658
  }
37602
- return toolCalls.map((call, index2) => ({
37603
- id: call?.id ?? String(index2),
37604
- type: "function",
37605
- function: {
37606
- name: call?.function?.name ?? "",
37607
- arguments: call?.function?.arguments ?? ""
37608
- }
37609
- }));
37610
- };
37611
- var normalizeUsage = (usage) => {
37612
- if (!usage) {
37613
- return void 0;
37659
+ const messages = [];
37660
+ if (input.systemPrompt?.trim()) {
37661
+ messages.push({ role: "system", content: input.systemPrompt.trim() });
37662
+ }
37663
+ if (input.userPrompt?.trim()) {
37664
+ messages.push({ role: "user", content: input.userPrompt.trim() });
37614
37665
  }
37666
+ return messages;
37667
+ }
37668
+ async function callChat(options) {
37669
+ const {
37670
+ baseUrl,
37671
+ apiKey,
37672
+ model,
37673
+ systemPrompt,
37674
+ userPrompt,
37675
+ messages,
37676
+ temperature,
37677
+ maxTokens,
37678
+ topP,
37679
+ stop,
37680
+ timeoutMs = 6e4,
37681
+ requestAdapter
37682
+ } = options;
37683
+ if (!baseUrl?.trim()) {
37684
+ throw new Error("baseUrl \u4E3A\u5FC5\u586B");
37685
+ }
37686
+ if (!apiKey?.trim()) {
37687
+ throw new Error("apiKey \u4E3A\u5FC5\u586B");
37688
+ }
37689
+ const resolvedMessages = resolveMessages({ systemPrompt, userPrompt, messages });
37690
+ if (!resolvedMessages.length) {
37691
+ throw new Error("userPrompt \u6216 messages \u81F3\u5C11\u63D0\u4F9B\u4E00\u9879");
37692
+ }
37693
+ const resolvedModel = model?.trim() || DEFAULT_TEXT_MODEL2;
37694
+ const payload = {
37695
+ model: resolvedModel,
37696
+ messages: resolvedMessages
37697
+ };
37698
+ if (temperature !== void 0) payload.temperature = temperature;
37699
+ if (maxTokens !== void 0) payload.max_tokens = maxTokens;
37700
+ if (topP !== void 0) payload.top_p = topP;
37701
+ if (stop !== void 0) payload.stop = stop;
37702
+ const raw = await requestJson({
37703
+ url: joinUrl(baseUrl.trim(), "chat/completions"),
37704
+ method: "POST",
37705
+ headers: {
37706
+ "Content-Type": "application/json",
37707
+ Authorization: `Bearer ${apiKey.trim()}`
37708
+ },
37709
+ body: payload,
37710
+ timeoutMs,
37711
+ requestAdapter
37712
+ });
37713
+ const content = raw.choices?.[0]?.message?.content ?? "";
37714
+ const usage = raw.usage ? {
37715
+ promptTokens: raw.usage.prompt_tokens,
37716
+ completionTokens: raw.usage.completion_tokens,
37717
+ totalTokens: raw.usage.total_tokens
37718
+ } : void 0;
37615
37719
  return {
37616
- promptTokens: usage.prompt_tokens,
37617
- completionTokens: usage.completion_tokens,
37618
- totalTokens: usage.total_tokens
37619
- };
37620
- };
37621
- var mapMessage = (message) => {
37622
- const mapped = {
37623
- role: message.role,
37624
- content: message.content
37625
- };
37626
- if (message.name) {
37627
- mapped.name = message.name;
37720
+ content,
37721
+ model: raw.model ?? resolvedModel,
37722
+ usage,
37723
+ raw
37724
+ };
37725
+ }
37726
+
37727
+ // src/common/aiApi/callCompletion.ts
37728
+ async function callCompletion(params, clientSettings) {
37729
+ const config = requireAiConnectionConfig(params.connection, clientSettings);
37730
+ const model = params.model || config.textModel;
37731
+ const result = await callChat({
37732
+ baseUrl: config.baseUrl,
37733
+ apiKey: config.apiKey,
37734
+ model,
37735
+ systemPrompt: params.systemPrompt,
37736
+ userPrompt: params.userPrompt,
37737
+ temperature: params.temperature,
37738
+ maxTokens: params.maxTokens,
37739
+ timeoutMs: config.timeoutMs
37740
+ });
37741
+ return {
37742
+ content: result.content,
37743
+ model: result.model || model,
37744
+ raw: result.raw
37745
+ };
37746
+ }
37747
+
37748
+ // src/common/aiApi/modelHeuristics.ts
37749
+ var NON_CHAT_PATTERNS = [
37750
+ /embed/i,
37751
+ /whisper/i,
37752
+ /tts/i,
37753
+ /dall-e/i,
37754
+ /moderation/i,
37755
+ /realtime/i,
37756
+ /audio/i,
37757
+ /transcrib/i,
37758
+ /sora/i
37759
+ ];
37760
+ var VISION_HINT_PATTERNS = [
37761
+ /^gpt-4o/i,
37762
+ /^gpt-4-turbo/i,
37763
+ /^gpt-4-vision/i,
37764
+ /^gpt-4\.1/i,
37765
+ /claude-3/i,
37766
+ /gemini.*(pro|flash|vision)/i,
37767
+ /qwen.*vl/i,
37768
+ /vision/i,
37769
+ /-vl/i,
37770
+ /llava/i,
37771
+ /doubao.*vision/i,
37772
+ /glm-4v/i,
37773
+ /internvl/i,
37774
+ /pixtral/i,
37775
+ /deepseek-vl/i
37776
+ ];
37777
+ var PREFERRED_VISION_MODELS = [
37778
+ "gpt-4o-mini",
37779
+ "gpt-4o",
37780
+ "gpt-4-turbo",
37781
+ "gpt-4-vision-preview",
37782
+ "gpt-4.1-mini",
37783
+ "gpt-4.1",
37784
+ "claude-3-5-sonnet-latest",
37785
+ "claude-3-5-haiku-latest",
37786
+ "gemini-2.0-flash",
37787
+ "gemini-1.5-flash",
37788
+ "gemini-1.5-pro",
37789
+ "qwen-vl-max",
37790
+ "qwen2-vl-72b-instruct"
37791
+ ];
37792
+ function isChatModel(id) {
37793
+ return !NON_CHAT_PATTERNS.some((pattern) => pattern.test(id));
37794
+ }
37795
+ var NATIVE_AUDIO_CHAT_PATTERNS = [
37796
+ /^gpt-4o/i,
37797
+ /^gpt-4-turbo/i,
37798
+ /^gpt-4\.1/i,
37799
+ /gemini-2\.0/i,
37800
+ /gemini-1\.5/i
37801
+ ];
37802
+ var STT_MODEL_PATTERNS = [/whisper/i, /transcrib/i, /sensevoice/i, /paraformer/i];
37803
+ function isLikelyNativeAudioChatModel(id) {
37804
+ const trimmed = id.trim();
37805
+ if (!trimmed) return false;
37806
+ if (STT_MODEL_PATTERNS.some((pattern) => pattern.test(trimmed))) return false;
37807
+ return NATIVE_AUDIO_CHAT_PATTERNS.some((pattern) => pattern.test(trimmed));
37808
+ }
37809
+ function isLikelySttModel(id) {
37810
+ return STT_MODEL_PATTERNS.some((pattern) => pattern.test(id.trim()));
37811
+ }
37812
+ function filterSttModels(modelIds) {
37813
+ return modelIds.filter(isLikelySttModel).sort((a, b) => a.localeCompare(b));
37814
+ }
37815
+ var PREFERRED_STT_MODELS = ["whisper-1", "whisper-large-v3", "whisper-large-v3-turbo"];
37816
+ function pickDefaultSttModel(modelIds, current) {
37817
+ const sttModels = filterSttModels(modelIds);
37818
+ if (sttModels.length === 0) return void 0;
37819
+ const trimmedCurrent = current?.trim();
37820
+ if (trimmedCurrent && sttModels.includes(trimmedCurrent)) {
37821
+ return trimmedCurrent;
37822
+ }
37823
+ for (const preferred of PREFERRED_STT_MODELS) {
37824
+ const match = sttModels.find((id) => id === preferred || id.startsWith(`${preferred}-`));
37825
+ if (match) return match;
37826
+ }
37827
+ return sttModels[0];
37828
+ }
37829
+ function isLikelyVisionModel(id) {
37830
+ return VISION_HINT_PATTERNS.some((pattern) => pattern.test(id));
37831
+ }
37832
+ var TEXT_ONLY_MODEL_PATTERNS = [
37833
+ /^deepseek-chat/i,
37834
+ /^deepseek-reasoner/i,
37835
+ /^deepseek-r1/i,
37836
+ /^deepseek-v[34](?!.*vl)/i,
37837
+ /^gpt-3\.5/i,
37838
+ /^o1-mini/i,
37839
+ /^o3-mini/i
37840
+ ];
37841
+ function isKnownTextOnlyModel(id) {
37842
+ const trimmed = id.trim();
37843
+ if (!trimmed) return false;
37844
+ return TEXT_ONLY_MODEL_PATTERNS.some((pattern) => pattern.test(trimmed));
37845
+ }
37846
+ function filterChatModels(modelIds) {
37847
+ return modelIds.filter(isChatModel).sort((a, b) => a.localeCompare(b));
37848
+ }
37849
+ function filterVisionModels(modelIds) {
37850
+ return filterChatModels(modelIds).filter((id) => isLikelyVisionModel(id) && !isKnownTextOnlyModel(id)).sort((a, b) => a.localeCompare(b));
37851
+ }
37852
+ function matchesPreferred(modelId, preferred) {
37853
+ return modelId === preferred || modelId.startsWith(`${preferred}-`);
37854
+ }
37855
+ function pickDefaultVisionModel(modelIds, current) {
37856
+ const visionModels = filterVisionModels(modelIds);
37857
+ if (visionModels.length === 0) return void 0;
37858
+ const trimmedCurrent = current?.trim();
37859
+ if (trimmedCurrent && visionModels.includes(trimmedCurrent)) {
37860
+ return trimmedCurrent;
37628
37861
  }
37629
- if (message.toolCallId) {
37630
- mapped.tool_call_id = message.toolCallId;
37862
+ for (const preferred of PREFERRED_VISION_MODELS) {
37863
+ const match = visionModels.find((id) => matchesPreferred(id, preferred));
37864
+ if (match) return match;
37631
37865
  }
37632
- if (message.toolCalls?.length) {
37633
- mapped.tool_calls = message.toolCalls.map((call) => ({
37634
- id: call.id,
37635
- type: call.type,
37636
- function: {
37637
- name: call.function.name,
37638
- arguments: call.function.arguments
37639
- }
37640
- }));
37866
+ return visionModels[0];
37867
+ }
37868
+
37869
+ // src/common/aiApi/visionMessageFormats.ts
37870
+ function detectVisionMessageFormat(baseUrl) {
37871
+ const normalized = baseUrl.toLowerCase();
37872
+ if (normalized.includes("ollama") || normalized.includes(":11434") || normalized.includes("11434/")) {
37873
+ return "ollama";
37641
37874
  }
37642
- return mapped;
37643
- };
37644
- var createOpenAICompatibleProvider = () => {
37645
- return {
37646
- id: "openai-compatible",
37647
- sendChat: async (request, config) => {
37648
- if (request.stream) {
37649
- throw new Error("Streaming is not supported in sendChat. Use streamChat when available.");
37650
- }
37651
- const model = request.model ?? config.model ?? DEFAULT_OPENAI_MODEL;
37652
- if (!model) {
37653
- throw new Error("Model is required for chat completion requests.");
37654
- }
37655
- const payload = {
37656
- model,
37657
- messages: request.messages.map(mapMessage)
37658
- };
37659
- if (request.temperature !== void 0) {
37660
- payload.temperature = request.temperature;
37661
- }
37662
- if (request.maxTokens !== void 0) {
37663
- payload.max_tokens = request.maxTokens;
37664
- }
37665
- if (request.topP !== void 0) {
37666
- payload.top_p = request.topP;
37667
- }
37668
- if (request.stop !== void 0) {
37669
- payload.stop = request.stop;
37875
+ return "openai";
37876
+ }
37877
+ function assertVisionCapableModel(modelId, options) {
37878
+ if (!options?.hasImages) return;
37879
+ const model = modelId.trim();
37880
+ if (!model) {
37881
+ throw new Error("\u8BC6\u56FE\u9700\u8981\u9009\u62E9\u89C6\u89C9\u6A21\u578B\uFF0C\u8BF7\u5728 AI \u8BBE\u7F6E\u4E2D\u914D\u7F6E");
37882
+ }
37883
+ const format = options.baseUrl ? detectVisionMessageFormat(options.baseUrl) : "openai";
37884
+ if (format === "ollama") return;
37885
+ if (isKnownTextOnlyModel(model)) {
37886
+ throw new Error(
37887
+ `\u5F53\u524D\u6A21\u578B\u300C${model}\u300D\u4E0D\u652F\u6301\u56FE\u7247\u8F93\u5165\u3002\u8BF7\u6539\u7528\u89C6\u89C9\u6A21\u578B\uFF0C\u4F8B\u5982 gpt-4o-mini\u3001qwen-vl-max\u3001gemini-1.5-flash\u3001deepseek-vl \u7B49\uFF08DeepSeek \u6587\u672C\u6A21\u578B\u65E0\u6CD5\u8BC6\u56FE\uFF09\u3002`
37888
+ );
37889
+ }
37890
+ if (!isLikelyVisionModel(model)) {
37891
+ throw new Error(
37892
+ `\u6A21\u578B\u300C${model}\u300D\u53EF\u80FD\u4E0D\u652F\u6301\u56FE\u7247\u8BC6\u56FE\u3002\u8BF7\u5728 AI \u8BBE\u7F6E\u4E2D\u9009\u62E9\u540D\u79F0\u542B vl\u3001vision\u3001gpt-4o\u3001gemini \u7B49\u6807\u8BC6\u7684\u89C6\u89C9\u6A21\u578B\u3002`
37893
+ );
37894
+ }
37895
+ }
37896
+ function isImageUrlVariantError(message) {
37897
+ return /unknown variant [`']?image_url[`']?/i.test(message) || /expected [`']?text[`']?/i.test(message) || /does not support.*image/i.test(message);
37898
+ }
37899
+ function toVisionApiErrorMessage(rawMessage, modelId) {
37900
+ if (isImageUrlVariantError(rawMessage)) {
37901
+ return `\u5F53\u524D\u6A21\u578B\u300C${modelId}\u300D\u4E0D\u63A5\u53D7\u56FE\u7247\u8BF7\u6C42\uFF08${rawMessage}\uFF09\u3002\u8BF7\u66F4\u6362\u4E3A\u652F\u6301\u8BC6\u56FE\u7684\u89C6\u89C9\u6A21\u578B\u3002`;
37902
+ }
37903
+ return rawMessage;
37904
+ }
37905
+
37906
+ // src/common/aiApi/audioStrategy.ts
37907
+ function resolveAudioHandling(options) {
37908
+ if (!options.hasAudio) {
37909
+ return "none";
37910
+ }
37911
+ if (options.strategy === "stt") {
37912
+ return "stt";
37913
+ }
37914
+ if (options.strategy === "native") {
37915
+ return "native";
37916
+ }
37917
+ if (detectVisionMessageFormat(options.baseUrl) === "ollama") {
37918
+ return "stt";
37919
+ }
37920
+ if (isLikelyNativeAudioChatModel(options.model)) {
37921
+ return "native";
37922
+ }
37923
+ return "stt";
37924
+ }
37925
+ function isAudioInputError(message) {
37926
+ return /input_audio/i.test(message) || /unknown variant [`']?input_audio[`']?/i.test(message) || /does not support.*audio/i.test(message) || /audio input/i.test(message) || /invalid audio/i.test(message);
37927
+ }
37928
+ function appendTranscriptionsToPrompt(userPrompt, transcriptions) {
37929
+ if (transcriptions.length === 0) {
37930
+ return userPrompt;
37931
+ }
37932
+ const blocks = transcriptions.map((text3, index2) => {
37933
+ const label = transcriptions.length === 1 ? "[\u8BED\u97F3\u8F6C\u5199]" : `[\u8BED\u97F3\u8F6C\u5199 ${index2 + 1}]`;
37934
+ return `${label}
37935
+ ${text3.trim()}`;
37936
+ });
37937
+ const joined = blocks.join("\n\n");
37938
+ const trimmedPrompt = userPrompt.trim();
37939
+ return trimmedPrompt ? `${trimmedPrompt}
37940
+
37941
+ ${joined}` : joined;
37942
+ }
37943
+
37944
+ // src/common/aiApi/audioUtils.ts
37945
+ var ALLOWED_MIME = /* @__PURE__ */ new Set([
37946
+ "audio/wav",
37947
+ "audio/x-wav",
37948
+ "audio/mpeg",
37949
+ "audio/mp3",
37950
+ "audio/mp4",
37951
+ "audio/webm",
37952
+ "audio/ogg",
37953
+ "audio/flac"
37954
+ ]);
37955
+ var MIME_TO_FORMAT = {
37956
+ "audio/wav": "wav",
37957
+ "audio/x-wav": "wav",
37958
+ "audio/mpeg": "mp3",
37959
+ "audio/mp3": "mp3",
37960
+ "audio/mp4": "mp4",
37961
+ "audio/webm": "webm",
37962
+ "audio/ogg": "ogg",
37963
+ "audio/flac": "flac"
37964
+ };
37965
+ function mimeToAudioFormat(mimeType) {
37966
+ return MIME_TO_FORMAT[mimeType] ?? mimeType.split("/").pop() ?? "wav";
37967
+ }
37968
+ function assertValidAudioInput(audio, maxAudioBytes = 25 * 1024 * 1024) {
37969
+ if (!audio.base64?.trim()) {
37970
+ throw new Error("\u97F3\u9891\u6570\u636E\u4E0D\u80FD\u4E3A\u7A7A");
37971
+ }
37972
+ if (!ALLOWED_MIME.has(audio.mimeType)) {
37973
+ throw new Error(`\u4E0D\u652F\u6301\u7684\u97F3\u9891\u683C\u5F0F: ${audio.mimeType}`);
37974
+ }
37975
+ const byteLength = estimateBase64ByteLength(audio.base64);
37976
+ if (byteLength > maxAudioBytes) {
37977
+ throw new Error(`\u97F3\u9891\u8FC7\u5927\uFF0C\u6700\u5927 ${Math.round(maxAudioBytes / 1024 / 1024)}MB`);
37978
+ }
37979
+ }
37980
+ function estimateBase64ByteLength(base64) {
37981
+ if (typeof Buffer !== "undefined") {
37982
+ return Buffer.byteLength(base64, "base64");
37983
+ }
37984
+ const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
37985
+ return Math.floor(base64.length * 3 / 4) - padding;
37986
+ }
37987
+ async function fileToAiAudioInput(file) {
37988
+ const buffer = await file.arrayBuffer();
37989
+ if (typeof Buffer !== "undefined") {
37990
+ return {
37991
+ base64: Buffer.from(buffer).toString("base64"),
37992
+ mimeType: file.type || "audio/wav"
37993
+ };
37994
+ }
37995
+ const bytes = new Uint8Array(buffer);
37996
+ let binary = "";
37997
+ for (let index2 = 0; index2 < bytes.length; index2 += 1) {
37998
+ binary += String.fromCharCode(bytes[index2]);
37999
+ }
38000
+ return { base64: btoa(binary), mimeType: file.type || "audio/wav" };
38001
+ }
38002
+ function base64ToBlob(base64, mimeType) {
38003
+ if (typeof Buffer !== "undefined") {
38004
+ const bytes2 = Buffer.from(base64, "base64");
38005
+ return new Blob([bytes2], { type: mimeType });
38006
+ }
38007
+ const binary = atob(base64);
38008
+ const bytes = new Uint8Array(binary.length);
38009
+ for (let index2 = 0; index2 < binary.length; index2 += 1) {
38010
+ bytes[index2] = binary.charCodeAt(index2);
38011
+ }
38012
+ return new Blob([bytes], { type: mimeType });
38013
+ }
38014
+
38015
+ // src/common/aiApi/multimodalMessageFormats.ts
38016
+ function buildMultimodalMessages(options) {
38017
+ const { systemPrompt, userPrompt, images, nativeAudios, format } = options;
38018
+ const hasImages = images.length > 0;
38019
+ const hasNativeAudio = nativeAudios.length > 0;
38020
+ if (format === "ollama" && hasImages) {
38021
+ return [
38022
+ { role: "system", content: systemPrompt },
38023
+ {
38024
+ role: "user",
38025
+ content: userPrompt,
38026
+ images: images.map((image) => image.base64)
37670
38027
  }
37671
- if (request.tools) {
37672
- payload.tools = request.tools;
38028
+ ];
38029
+ }
38030
+ const userContent = [{ type: "text", text: userPrompt }];
38031
+ for (const image of images) {
38032
+ userContent.push({
38033
+ type: "image_url",
38034
+ image_url: {
38035
+ url: `data:${image.mimeType};base64,${image.base64}`
37673
38036
  }
37674
- if (request.toolChoice) {
37675
- payload.tool_choice = request.toolChoice;
38037
+ });
38038
+ }
38039
+ for (const audio of nativeAudios) {
38040
+ userContent.push({
38041
+ type: "input_audio",
38042
+ input_audio: {
38043
+ data: audio.base64,
38044
+ format: mimeToAudioFormat(audio.mimeType)
37676
38045
  }
37677
- const url = joinUrl(config.baseUrl || DEFAULT_OPENAI_BASE_URL, "chat/completions");
37678
- const response = await requestJson({
37679
- url,
37680
- method: "POST",
37681
- headers: config.headers,
37682
- body: payload,
37683
- timeoutMs: config.timeoutMs,
37684
- requestAdapter: config.requestAdapter
37685
- });
37686
- const firstMessage = response.choices?.[0]?.message;
37687
- const toolCalls = normalizeToolCalls(firstMessage?.tool_calls);
37688
- const content = firstMessage?.content ?? "";
37689
- const assistantMessage = {
37690
- role: "assistant",
37691
- content,
37692
- toolCalls
37693
- };
37694
- return {
37695
- id: response.id ?? "",
37696
- content,
37697
- message: assistantMessage,
37698
- usage: normalizeUsage(response.usage),
37699
- toolCalls,
37700
- raw: response
37701
- };
37702
- }
37703
- };
37704
- };
38046
+ });
38047
+ }
38048
+ const useStructuredContent = hasImages || hasNativeAudio;
38049
+ return [
38050
+ { role: "system", content: systemPrompt },
38051
+ { role: "user", content: useStructuredContent ? userContent : userPrompt }
38052
+ ];
38053
+ }
38054
+ function assertMultimodalCapableModel(modelId, options) {
38055
+ assertVisionCapableModel(modelId, {
38056
+ baseUrl: options.baseUrl,
38057
+ hasImages: options.hasImages
38058
+ });
38059
+ if (!options.hasNativeAudio) return;
38060
+ const format = options.baseUrl ? detectVisionMessageFormat(options.baseUrl) : "openai";
38061
+ if (format === "ollama") {
38062
+ throw new Error('\u5F53\u524D Ollama \u8FDE\u63A5\u4E0D\u652F\u6301 chat \u5185\u5D4C\u97F3\u9891\uFF0C\u8BF7\u6539\u7528 audioStrategy: "stt" \u6216 "auto"');
38063
+ }
38064
+ }
37705
38065
 
37706
- // src/common/ai/llm/client/createAiClient.ts
37707
- var DEFAULT_TIMEOUT_MS = 6e4;
37708
- var resolveApiKey = async (config) => {
37709
- if (config.apiKey) {
37710
- return config.apiKey;
38066
+ // src/common/aiApi/imageUtils.ts
38067
+ var ALLOWED_MIME2 = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp", "image/gif"]);
38068
+ function assertValidImageInput(image, maxImageBytes = 5 * 1024 * 1024) {
38069
+ if (!image.base64?.trim()) {
38070
+ throw new Error("\u56FE\u7247\u6570\u636E\u4E0D\u80FD\u4E3A\u7A7A");
37711
38071
  }
37712
- if (config.getApiKey) {
37713
- return await config.getApiKey();
38072
+ if (!ALLOWED_MIME2.has(image.mimeType)) {
38073
+ throw new Error(`\u4E0D\u652F\u6301\u7684\u56FE\u7247\u683C\u5F0F: ${image.mimeType}`);
37714
38074
  }
37715
- return void 0;
37716
- };
37717
- var hasAuthorizationHeader = (headers) => {
37718
- return Object.keys(headers).some((key) => key.toLowerCase() === "authorization");
37719
- };
37720
- var resolveHeaders = async (config) => {
37721
- const headers = {
37722
- "Content-Type": "application/json",
37723
- ...config.headers ?? {}
37724
- };
37725
- if (!hasAuthorizationHeader(headers)) {
37726
- const apiKey = await resolveApiKey(config);
37727
- if (apiKey) {
37728
- headers.Authorization = `Bearer ${apiKey}`;
38075
+ const byteLength = estimateBase64ByteLength2(image.base64);
38076
+ if (byteLength > maxImageBytes) {
38077
+ throw new Error(`\u56FE\u7247\u8FC7\u5927\uFF0C\u6700\u5927 ${Math.round(maxImageBytes / 1024 / 1024)}MB`);
38078
+ }
38079
+ }
38080
+ function estimateBase64ByteLength2(base64) {
38081
+ if (typeof Buffer !== "undefined") {
38082
+ return Buffer.byteLength(base64, "base64");
38083
+ }
38084
+ const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
38085
+ return Math.floor(base64.length * 3 / 4) - padding;
38086
+ }
38087
+ async function fileToAiImageInput(file) {
38088
+ const buffer = await file.arrayBuffer();
38089
+ if (typeof Buffer !== "undefined") {
38090
+ return {
38091
+ base64: Buffer.from(buffer).toString("base64"),
38092
+ mimeType: file.type || "image/jpeg"
38093
+ };
38094
+ }
38095
+ const bytes = new Uint8Array(buffer);
38096
+ let binary = "";
38097
+ for (let index2 = 0; index2 < bytes.length; index2 += 1) {
38098
+ binary += String.fromCharCode(bytes[index2]);
38099
+ }
38100
+ const base64 = btoa(binary);
38101
+ return { base64, mimeType: file.type || "image/jpeg" };
38102
+ }
38103
+
38104
+ // src/common/aiApi/mediaUtils.ts
38105
+ function splitMediaByKind(media) {
38106
+ const images = [];
38107
+ const audios = [];
38108
+ for (const item of media) {
38109
+ if (item.kind === "image") {
38110
+ images.push(item);
38111
+ } else {
38112
+ audios.push(item);
37729
38113
  }
37730
38114
  }
37731
- return headers;
37732
- };
37733
- var resolveClientConfig = async (config) => {
37734
- const headers = await resolveHeaders(config);
37735
- return {
37736
- ...config,
37737
- baseUrl: config.baseUrl ?? "https://api.openai.com/v1",
37738
- headers,
37739
- timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS
37740
- };
37741
- };
37742
- var resolveProvider = (provider) => {
37743
- return provider ?? createOpenAICompatibleProvider();
37744
- };
37745
- var createAiClient = (config) => {
37746
- const provider = resolveProvider(config.provider);
37747
- const sendChat = async (request) => {
37748
- const resolvedConfig = await resolveClientConfig(config);
37749
- config.logger?.debug?.("ai.sendChat", {
37750
- model: request.model ?? resolvedConfig.model,
37751
- messageCount: request.messages.length
38115
+ return { images, audios };
38116
+ }
38117
+ function assertValidMultimodalMedia(media, limits) {
38118
+ const items = media ?? [];
38119
+ const { images, audios } = splitMediaByKind(items);
38120
+ const maxImages = limits.maxImages ?? 8;
38121
+ const maxAudios = limits.maxAudios ?? 4;
38122
+ if (images.length > maxImages) {
38123
+ throw new Error(`\u56FE\u7247\u6570\u91CF\u8FC7\u591A\uFF0C\u6700\u591A ${maxImages} \u5F20`);
38124
+ }
38125
+ if (audios.length > maxAudios) {
38126
+ throw new Error(`\u97F3\u9891\u6570\u91CF\u8FC7\u591A\uFF0C\u6700\u591A ${maxAudios} \u6BB5`);
38127
+ }
38128
+ for (const image of images) {
38129
+ assertValidImageInput(
38130
+ { base64: image.base64, mimeType: image.mimeType },
38131
+ limits.maxImageBytes
38132
+ );
38133
+ }
38134
+ for (const audio of audios) {
38135
+ assertValidAudioInput(
38136
+ { base64: audio.base64, mimeType: audio.mimeType },
38137
+ limits.maxAudioBytes
38138
+ );
38139
+ }
38140
+ return items;
38141
+ }
38142
+
38143
+ // src/common/aiApi/transcribeAudio.ts
38144
+ function joinUrl2(baseUrl, path) {
38145
+ return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
38146
+ }
38147
+ async function transcribeAudio(options) {
38148
+ const { audio, config, model, language } = options;
38149
+ const sttModel = model?.trim() || config.audioModel;
38150
+ const blob = base64ToBlob(audio.base64, audio.mimeType);
38151
+ const extension = mimeToAudioFormat(audio.mimeType);
38152
+ const form = new FormData();
38153
+ form.append("file", blob, `audio.${extension}`);
38154
+ form.append("model", sttModel);
38155
+ if (language?.trim()) {
38156
+ form.append("language", language.trim());
38157
+ }
38158
+ const controller = new AbortController();
38159
+ const timeoutId = setTimeout(() => controller.abort(), config.timeoutMs);
38160
+ try {
38161
+ const response = await fetch(joinUrl2(config.baseUrl, "audio/transcriptions"), {
38162
+ method: "POST",
38163
+ headers: {
38164
+ Authorization: `Bearer ${config.apiKey}`
38165
+ },
38166
+ body: form,
38167
+ signal: controller.signal
37752
38168
  });
37753
- try {
37754
- return await provider.sendChat(request, resolvedConfig);
37755
- } catch (error) {
37756
- config.logger?.error?.("ai.sendChat.error", error);
37757
- throw error;
38169
+ const text3 = await response.text();
38170
+ let data = null;
38171
+ if (text3) {
38172
+ try {
38173
+ data = JSON.parse(text3);
38174
+ } catch {
38175
+ data = text3;
38176
+ }
37758
38177
  }
37759
- };
37760
- const sendMessage = async (content, options) => {
37761
- const resolvedOptions = options ?? {};
37762
- const { systemPrompt, ...requestOptions } = resolvedOptions;
37763
- const messages = [];
37764
- if (systemPrompt) {
37765
- messages.push({ role: "system", content: systemPrompt });
38178
+ if (!response.ok) {
38179
+ const record = data;
38180
+ const errorMessage2 = (typeof record?.error === "object" ? record.error?.message : record?.error) || record?.message || `STT \u8BF7\u6C42\u5931\u8D25 (${response.status})`;
38181
+ throw new Error(String(errorMessage2));
37766
38182
  }
37767
- messages.push({ role: "user", content });
37768
- return sendChat({
37769
- messages,
37770
- ...requestOptions
37771
- });
38183
+ if (typeof data === "string") {
38184
+ return data.trim();
38185
+ }
38186
+ const parsed = data;
38187
+ return (parsed.text ?? "").trim();
38188
+ } finally {
38189
+ clearTimeout(timeoutId);
38190
+ }
38191
+ }
38192
+ async function transcribeAudios(audios, config, model) {
38193
+ const results = [];
38194
+ for (const audio of audios) {
38195
+ results.push(await transcribeAudio({ audio, config, model }));
38196
+ }
38197
+ return results;
38198
+ }
38199
+
38200
+ // src/common/aiApi/callMultimodalChat.ts
38201
+ function joinUrl3(baseUrl, path) {
38202
+ return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
38203
+ }
38204
+ async function requestMultimodalChat(options) {
38205
+ const payload = {
38206
+ model: options.model,
38207
+ messages: options.messages,
38208
+ temperature: options.temperature ?? 0.2
37772
38209
  };
38210
+ if (options.maxTokens !== void 0) {
38211
+ payload.max_tokens = options.maxTokens;
38212
+ }
38213
+ if (options.jsonMode) {
38214
+ payload.response_format = { type: "json_object" };
38215
+ }
38216
+ const raw = await requestJson({
38217
+ url: joinUrl3(options.config.baseUrl, "chat/completions"),
38218
+ method: "POST",
38219
+ headers: {
38220
+ "Content-Type": "application/json",
38221
+ Authorization: `Bearer ${options.config.apiKey}`
38222
+ },
38223
+ body: payload,
38224
+ timeoutMs: options.config.timeoutMs
38225
+ });
37773
38226
  return {
37774
- sendChat,
37775
- sendMessage
38227
+ content: raw.choices?.[0]?.message?.content ?? "",
38228
+ model: raw.model ?? options.model,
38229
+ raw
37776
38230
  };
37777
- };
37778
-
37779
- // src/common/ai/llm/prompt/variables.ts
37780
- var normalizePromptVariables = (variables = {}) => {
37781
- const normalized = {};
37782
- Object.entries(variables).forEach(([key, value]) => {
37783
- if (value === null || value === void 0) {
37784
- normalized[key] = "";
37785
- } else {
37786
- normalized[key] = String(value);
37787
- }
38231
+ }
38232
+ function toAudioInputs(audios) {
38233
+ return audios.map((audio) => ({ base64: audio.base64, mimeType: audio.mimeType }));
38234
+ }
38235
+ async function callMultimodalChat(params, clientSettings) {
38236
+ const config = requireAiConnectionConfig(params.connection, clientSettings);
38237
+ const media = assertValidMultimodalMedia(params.media, {
38238
+ maxImageBytes: config.maxImageBytes,
38239
+ maxAudioBytes: config.maxAudioBytes
37788
38240
  });
37789
- return normalized;
37790
- };
38241
+ const { images, audios } = splitMediaByKind(media);
38242
+ const hasImages = images.length > 0;
38243
+ const hasAudio = audios.length > 0;
38244
+ const model = params.model || (hasImages ? config.visionModel : hasAudio ? config.visionModel : config.textModel);
38245
+ const strategy = params.audioStrategy ?? config.audioStrategy;
38246
+ const format = detectVisionMessageFormat(config.baseUrl);
38247
+ let audioHandling = resolveAudioHandling({
38248
+ hasAudio,
38249
+ strategy,
38250
+ model,
38251
+ baseUrl: config.baseUrl
38252
+ });
38253
+ let userPrompt = params.userPrompt;
38254
+ let transcriptions;
38255
+ let nativeAudios = [];
38256
+ if (audioHandling === "stt" && hasAudio) {
38257
+ transcriptions = await transcribeAudios(toAudioInputs(audios), config);
38258
+ userPrompt = appendTranscriptionsToPrompt(userPrompt, transcriptions);
38259
+ } else if (audioHandling === "native" && hasAudio) {
38260
+ nativeAudios = audios;
38261
+ }
38262
+ assertMultimodalCapableModel(model, {
38263
+ baseUrl: config.baseUrl,
38264
+ hasImages,
38265
+ hasNativeAudio: nativeAudios.length > 0
38266
+ });
38267
+ const messages = buildMultimodalMessages({
38268
+ systemPrompt: params.systemPrompt,
38269
+ userPrompt,
38270
+ images,
38271
+ nativeAudios,
38272
+ format
38273
+ });
38274
+ try {
38275
+ const result = await requestMultimodalChat({
38276
+ config,
38277
+ model,
38278
+ messages,
38279
+ temperature: params.temperature,
38280
+ maxTokens: params.maxTokens,
38281
+ jsonMode: params.jsonMode
38282
+ });
38283
+ return {
38284
+ ...result,
38285
+ audioHandling,
38286
+ transcriptions
38287
+ };
38288
+ } catch (error) {
38289
+ const message = error instanceof Error ? error.message : "AI \u8BF7\u6C42\u5931\u8D25";
38290
+ if (audioHandling === "native" && hasAudio && strategy === "auto" && isAudioInputError(message)) {
38291
+ transcriptions = await transcribeAudios(toAudioInputs(audios), config);
38292
+ userPrompt = appendTranscriptionsToPrompt(params.userPrompt, transcriptions);
38293
+ audioHandling = "stt";
38294
+ const fallbackMessages = buildMultimodalMessages({
38295
+ systemPrompt: params.systemPrompt,
38296
+ userPrompt,
38297
+ images,
38298
+ nativeAudios: [],
38299
+ format
38300
+ });
38301
+ const result = await requestMultimodalChat({
38302
+ config,
38303
+ model,
38304
+ messages: fallbackMessages,
38305
+ temperature: params.temperature,
38306
+ maxTokens: params.maxTokens,
38307
+ jsonMode: params.jsonMode
38308
+ });
38309
+ return {
38310
+ ...result,
38311
+ audioHandling,
38312
+ transcriptions
38313
+ };
38314
+ }
38315
+ throw new Error(toVisionApiErrorMessage(message, model));
38316
+ }
38317
+ }
37791
38318
 
37792
- // src/common/ai/llm/prompt/template.ts
37793
- var applyPromptTemplate = (template, variables = {}, options = {}) => {
37794
- if (!template) {
37795
- return "";
38319
+ // src/common/aiApi/jsonUtils.ts
38320
+ function extractJsonObject(text3) {
38321
+ const trimmed = text3.trim();
38322
+ if (!trimmed) {
38323
+ throw new Error("\u6A21\u578B\u8FD4\u56DE\u4E3A\u7A7A");
37796
38324
  }
37797
- const resolved = normalizePromptVariables(variables);
37798
- const missingValue = options.missingValue ?? "";
37799
- return template.replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (_match, key) => {
37800
- if (Object.prototype.hasOwnProperty.call(resolved, key)) {
37801
- return resolved[key] ?? "";
38325
+ try {
38326
+ const parsed = JSON.parse(trimmed);
38327
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
38328
+ return parsed;
37802
38329
  }
37803
- return options.preserveUnknown ? `{{${key}}}` : missingValue;
37804
- });
37805
- };
37806
-
37807
- // src/common/ai/llm/client/createChatSession.ts
37808
- var buildBaseMessages = (systemPrompt, initialMessages) => {
37809
- const baseMessages = [];
37810
- if (systemPrompt) {
37811
- baseMessages.push({ role: "system", content: systemPrompt });
38330
+ } catch {
37812
38331
  }
37813
- if (initialMessages?.length) {
37814
- baseMessages.push(...initialMessages);
38332
+ const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
38333
+ if (fenced?.[1]) {
38334
+ const parsed = JSON.parse(fenced[1].trim());
38335
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
38336
+ return parsed;
38337
+ }
37815
38338
  }
37816
- return baseMessages;
37817
- };
37818
- var createChatSession = (options) => {
37819
- let systemPrompt = options.systemPrompt;
37820
- let messages = buildBaseMessages(systemPrompt, options.initialMessages);
37821
- const reset = () => {
37822
- messages = buildBaseMessages(systemPrompt, options.initialMessages);
37823
- };
37824
- const setSystemPrompt = (prompt2) => {
37825
- systemPrompt = prompt2;
37826
- reset();
37827
- };
37828
- const sendMessage = async (input, requestOptions = {}) => {
37829
- const { template, variables, ...chatOptions } = requestOptions;
37830
- const activeTemplate = template ?? options.template;
37831
- const mergedVariables = { input, ...variables ?? {} };
37832
- const content = activeTemplate ? applyPromptTemplate(activeTemplate, mergedVariables) : input;
37833
- const userMessage = { role: "user", content };
37834
- const nextMessages = [...messages, userMessage];
37835
- const response = await options.client.sendChat({
37836
- messages: nextMessages,
37837
- ...chatOptions
37838
- });
37839
- const assistantMessage = response.message ?? {
37840
- role: "assistant",
37841
- content: response.content,
37842
- toolCalls: response.toolCalls
37843
- };
37844
- messages = [...nextMessages, assistantMessage];
37845
- return response;
37846
- };
37847
- return {
37848
- getMessages: () => messages,
37849
- setSystemPrompt,
37850
- reset,
37851
- sendMessage
37852
- };
37853
- };
37854
- var buildBaseMessages2 = (systemPrompt, initialMessages) => {
37855
- const baseMessages = [];
37856
- if (systemPrompt) {
37857
- baseMessages.push({ role: "system", content: systemPrompt });
38339
+ const start = trimmed.indexOf("{");
38340
+ const end = trimmed.lastIndexOf("}");
38341
+ if (start >= 0 && end > start) {
38342
+ const parsed = JSON.parse(trimmed.slice(start, end + 1));
38343
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
38344
+ return parsed;
38345
+ }
37858
38346
  }
37859
- if (initialMessages?.length) {
37860
- baseMessages.push(...initialMessages);
38347
+ throw new Error("\u65E0\u6CD5\u89E3\u6790\u6A21\u578B JSON \u8F93\u51FA");
38348
+ }
38349
+
38350
+ // src/common/aiApi/listModels.ts
38351
+ function joinUrl4(baseUrl, path) {
38352
+ return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
38353
+ }
38354
+ function parseModelIds(raw) {
38355
+ if (Array.isArray(raw.data)) {
38356
+ return raw.data.map((item) => item.id).filter((id) => Boolean(id));
37861
38357
  }
37862
- return baseMessages;
37863
- };
37864
- var useAiChat = (options) => {
37865
- const systemPromptRef = React107.useRef(options.systemPrompt);
37866
- const templateRef = React107.useRef(options.template);
37867
- const [messages, setMessages] = React107.useState(
37868
- () => buildBaseMessages2(systemPromptRef.current, options.initialMessages)
37869
- );
37870
- const messagesRef = React107.useRef(messages);
37871
- const [status, setStatus] = React107.useState("idle");
37872
- const [error, setError] = React107.useState(null);
37873
- const [lastResponse, setLastResponse] = React107.useState(null);
37874
- const syncMessages = (nextMessages) => {
37875
- messagesRef.current = nextMessages;
37876
- setMessages(nextMessages);
37877
- };
37878
- const reset = React107.useCallback(() => {
37879
- syncMessages(buildBaseMessages2(systemPromptRef.current, options.initialMessages));
37880
- setStatus("idle");
37881
- setError(null);
37882
- setLastResponse(null);
37883
- }, [options.initialMessages]);
37884
- const setSystemPrompt = React107.useCallback(
37885
- (prompt2) => {
37886
- systemPromptRef.current = prompt2;
37887
- reset();
37888
- },
37889
- [reset]
37890
- );
37891
- const sendMessage = React107.useCallback(
37892
- async (input, requestOptions = {}) => {
37893
- const { template, variables, ...chatOptions } = requestOptions;
37894
- const activeTemplate = template ?? templateRef.current;
37895
- const mergedVariables = { input, ...variables ?? {} };
37896
- const content = activeTemplate ? applyPromptTemplate(activeTemplate, mergedVariables) : input;
37897
- const userMessage = { role: "user", content };
37898
- const nextMessages = [...messagesRef.current, userMessage];
37899
- syncMessages(nextMessages);
37900
- setStatus("loading");
37901
- setError(null);
37902
- try {
37903
- const response = await options.client.sendChat({
37904
- messages: nextMessages,
37905
- ...chatOptions
37906
- });
37907
- const assistantMessage = response.message ?? {
37908
- role: "assistant",
37909
- content: response.content,
37910
- toolCalls: response.toolCalls
37911
- };
37912
- const updatedMessages = [...nextMessages, assistantMessage];
37913
- syncMessages(updatedMessages);
37914
- setStatus("success");
37915
- setLastResponse(response);
37916
- return response;
37917
- } catch (err) {
37918
- const nextError = err instanceof Error ? err : new Error(String(err));
37919
- setStatus("error");
37920
- setError(nextError);
37921
- throw nextError;
37922
- }
38358
+ if (Array.isArray(raw.models)) {
38359
+ return raw.models.map((item) => typeof item === "string" ? item : item.id).filter((id) => Boolean(id));
38360
+ }
38361
+ return [];
38362
+ }
38363
+ async function listOpenAiCompatibleModels(clientSettings, currentVisionModel) {
38364
+ const config = resolveAiConnectionConfig(clientSettings);
38365
+ if (!config) {
38366
+ throw new Error("\u672A\u914D\u7F6E AI API Key\uFF0C\u8BF7\u5728\u8BBE\u7F6E\u4E2D\u586B\u5199\u6216\u914D\u7F6E\u670D\u52A1\u7AEF\u73AF\u5883\u53D8\u91CF");
38367
+ }
38368
+ const raw = await requestJson({
38369
+ url: joinUrl4(config.baseUrl, "models"),
38370
+ method: "GET",
38371
+ headers: {
38372
+ Authorization: `Bearer ${config.apiKey}`
37923
38373
  },
37924
- [options.client]
37925
- );
37926
- return {
37927
- status,
37928
- isLoading: status === "loading",
37929
- error,
37930
- messages,
37931
- lastResponse,
37932
- sendMessage,
37933
- reset,
37934
- setSystemPrompt
37935
- };
37936
- };
38374
+ timeoutMs: config.timeoutMs
38375
+ });
38376
+ const modelIds = parseModelIds(raw);
38377
+ if (modelIds.length === 0) {
38378
+ throw new Error("\u63A5\u53E3\u672A\u8FD4\u56DE\u53EF\u7528\u6A21\u578B");
38379
+ }
38380
+ const models = filterChatModels(modelIds);
38381
+ const visionModels = filterVisionModels(modelIds);
38382
+ const suggestedVisionModel = pickDefaultVisionModel(modelIds, currentVisionModel);
38383
+ return { models, visionModels, suggestedVisionModel };
38384
+ }
37937
38385
 
37938
- // src/common/ai/llm/skills/registry.ts
37939
- var skillToToolDefinition = (skill) => {
37940
- const toolDefinition = {
37941
- type: "function",
37942
- function: {
37943
- name: skill.name,
37944
- description: skill.description
37945
- }
37946
- };
37947
- if (skill.inputSchema) {
37948
- toolDefinition.function.parameters = skill.inputSchema;
38386
+ // src/common/aiApi/taskRegistry.ts
38387
+ var registry2 = /* @__PURE__ */ new Map();
38388
+ function registerAiTask(task) {
38389
+ if (registry2.has(task.id)) {
38390
+ console.warn(`[aiApi] task "${task.id}" already registered, skipping duplicate`);
38391
+ return;
37949
38392
  }
37950
- return toolDefinition;
37951
- };
37952
- var InMemorySkillRegistry = class {
37953
- constructor(initialSkills) {
37954
- this.skills = /* @__PURE__ */ new Map();
37955
- initialSkills?.forEach((skill) => {
37956
- this.skills.set(skill.name, skill);
37957
- });
38393
+ registry2.set(task.id, task);
38394
+ }
38395
+ function getAiTask(taskId) {
38396
+ return registry2.get(taskId);
38397
+ }
38398
+ function listAiTasks() {
38399
+ return Array.from(registry2.keys());
38400
+ }
38401
+ function clearAiTasksForTest() {
38402
+ registry2.clear();
38403
+ }
38404
+
38405
+ // src/common/aiApi/runTask.ts
38406
+ function extractInputConnection(input) {
38407
+ if (!input || typeof input !== "object") return void 0;
38408
+ const connection = input.connection;
38409
+ return connection && typeof connection === "object" ? connection : void 0;
38410
+ }
38411
+ async function runAiTask(taskId, input, ctx = {}) {
38412
+ const started = Date.now();
38413
+ if (!resolveAiConnectionConfig(extractInputConnection(input), ctx.clientSettings)) {
38414
+ return {
38415
+ success: false,
38416
+ taskId,
38417
+ error: {
38418
+ code: "AI_CONFIG_MISSING",
38419
+ message: "\u672A\u914D\u7F6E AI API Key\uFF0C\u8BF7\u4F20\u5165 clientSettings.connection \u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF AI_API_KEY"
38420
+ }
38421
+ };
38422
+ }
38423
+ const task = getAiTask(taskId);
38424
+ if (!task) {
38425
+ return {
38426
+ success: false,
38427
+ taskId,
38428
+ error: {
38429
+ code: "TASK_NOT_FOUND",
38430
+ message: `\u672A\u6CE8\u518C\u7684\u4EFB\u52A1: ${taskId}`
38431
+ }
38432
+ };
38433
+ }
38434
+ try {
38435
+ const validated = task.validateInput(input);
38436
+ const result = await task.execute(validated, ctx);
38437
+ return {
38438
+ success: true,
38439
+ taskId,
38440
+ data: result.data,
38441
+ meta: {
38442
+ model: result.meta?.model ?? "unknown",
38443
+ latencyMs: Date.now() - started,
38444
+ provider: result.meta?.provider ?? "openai-compatible",
38445
+ confidence: result.meta?.confidence,
38446
+ rawSummary: result.meta?.rawSummary
38447
+ }
38448
+ };
38449
+ } catch (error) {
38450
+ const message = error instanceof Error ? error.message : "AI \u4EFB\u52A1\u6267\u884C\u5931\u8D25";
38451
+ const code = classifyError(message);
38452
+ return {
38453
+ success: false,
38454
+ taskId,
38455
+ error: { code, message },
38456
+ meta: {
38457
+ model: "unknown",
38458
+ latencyMs: Date.now() - started
38459
+ }
38460
+ };
37958
38461
  }
37959
- registerSkill(skill) {
37960
- this.skills.set(skill.name, skill);
38462
+ }
38463
+ function classifyError(message) {
38464
+ if (message.includes("\u672A\u914D\u7F6E") || message.includes("AI_API_KEY") || message.includes("apiKey")) {
38465
+ return "AI_CONFIG_MISSING";
37961
38466
  }
37962
- unregisterSkill(name) {
37963
- return this.skills.delete(name);
38467
+ if (message.includes("\u56FE\u7247") || message.includes("\u683C\u5F0F")) {
38468
+ return message.includes("\u8FC7\u5927") ? "PAYLOAD_TOO_LARGE" : "UNSUPPORTED_MEDIA";
37964
38469
  }
37965
- getSkill(name) {
37966
- return this.skills.get(name);
38470
+ if (message.includes("\u89E3\u6790") || message.includes("JSON")) {
38471
+ return "AI_PARSE_FAILED";
37967
38472
  }
37968
- listSkills() {
37969
- return Array.from(this.skills.values());
38473
+ if (message.includes("\u5FC5\u586B") || message.includes("\u65E0\u6548")) {
38474
+ return "INVALID_INPUT";
37970
38475
  }
37971
- async executeSkill(name, input, context) {
37972
- const skill = this.skills.get(name);
37973
- if (!skill) {
37974
- throw new Error(`Skill not found: ${name}`);
38476
+ return "AI_REQUEST_FAILED";
38477
+ }
38478
+
38479
+ // src/common/aiApi/tasks/coreLlmCompletion.ts
38480
+ function isTextCompletionInput(input) {
38481
+ if (!input || typeof input !== "object") return false;
38482
+ const value = input;
38483
+ return typeof value.userPrompt === "string" && value.userPrompt.trim().length > 0;
38484
+ }
38485
+ var coreLlmCompletionTask = {
38486
+ id: CORE_LLM_COMPLETION_TASK_ID,
38487
+ description: "\u901A\u7528\u6587\u672C\u8865\u5168\uFF1Asystem/user \u63D0\u793A\u8BCD \u2192 \u6A21\u578B\u6587\u672C",
38488
+ validateInput(input) {
38489
+ if (!isTextCompletionInput(input)) {
38490
+ throw new Error("userPrompt \u4E3A\u5FC5\u586B");
37975
38491
  }
37976
- return await skill.execute(input, context);
38492
+ return input;
38493
+ },
38494
+ async execute(input, ctx) {
38495
+ const result = await callCompletion(
38496
+ {
38497
+ systemPrompt: input.systemPrompt,
38498
+ userPrompt: input.userPrompt,
38499
+ model: input.model,
38500
+ temperature: input.temperature,
38501
+ maxTokens: input.maxTokens,
38502
+ connection: input.connection
38503
+ },
38504
+ ctx.clientSettings
38505
+ );
38506
+ return {
38507
+ data: {
38508
+ content: result.content,
38509
+ rawText: result.content
38510
+ },
38511
+ meta: {
38512
+ model: result.model,
38513
+ provider: "openai-compatible"
38514
+ }
38515
+ };
37977
38516
  }
37978
- toToolDefinitions() {
37979
- return this.listSkills().map(skillToToolDefinition);
38517
+ };
38518
+
38519
+ // src/common/aiApi/tasks/coreStructuredMultimodal.ts
38520
+ function isStructuredMultimodalInput(input) {
38521
+ if (!input || typeof input !== "object") return false;
38522
+ const value = input;
38523
+ return typeof value.systemPrompt === "string" && typeof value.userPrompt === "string";
38524
+ }
38525
+ var coreStructuredMultimodalTask = {
38526
+ id: CORE_STRUCTURED_MULTIMODAL_TASK_ID,
38527
+ description: "\u901A\u7528\u7ED3\u6784\u5316\u591A\u6A21\u6001\u4EFB\u52A1\uFF1A\u6587\u672C + \u53EF\u9009\u56FE\u7247/\u8BED\u97F3 \u2192 JSON",
38528
+ validateInput(input) {
38529
+ if (!isStructuredMultimodalInput(input)) {
38530
+ throw new Error("systemPrompt \u4E0E userPrompt \u4E3A\u5FC5\u586B");
38531
+ }
38532
+ const config = resolveAiConnectionConfig(input.connection);
38533
+ assertValidMultimodalMedia(input.media, {
38534
+ maxImageBytes: config?.maxImageBytes ?? 5 * 1024 * 1024,
38535
+ maxAudioBytes: config?.maxAudioBytes ?? 25 * 1024 * 1024
38536
+ });
38537
+ return input;
38538
+ },
38539
+ async execute(input, ctx) {
38540
+ const schemaHint = input.jsonSchemaHint ? `
38541
+
38542
+ \u8BF7\u4E25\u683C\u8F93\u51FA JSON \u5BF9\u8C61\uFF0C\u7ED3\u6784\u53C2\u8003\uFF1A
38543
+ ${input.jsonSchemaHint}` : "\n\n\u8BF7\u4E25\u683C\u8F93\u51FA JSON \u5BF9\u8C61\uFF0C\u4E0D\u8981\u5305\u542B Markdown \u4EE3\u7801\u5757\u3002";
38544
+ const result = await callMultimodalChat(
38545
+ {
38546
+ systemPrompt: input.systemPrompt,
38547
+ userPrompt: `${input.userPrompt}${schemaHint}`,
38548
+ media: input.media,
38549
+ model: input.model,
38550
+ temperature: input.temperature ?? 0.2,
38551
+ maxTokens: input.maxTokens,
38552
+ jsonMode: true,
38553
+ audioStrategy: input.audioStrategy,
38554
+ connection: input.connection
38555
+ },
38556
+ ctx.clientSettings
38557
+ );
38558
+ const json2 = extractJsonObject(result.content);
38559
+ return {
38560
+ data: {
38561
+ json: json2,
38562
+ rawText: result.content
38563
+ },
38564
+ meta: {
38565
+ model: result.model,
38566
+ provider: "openai-compatible",
38567
+ rawSummary: result.audioHandling ? `audio=${result.audioHandling}` : void 0
38568
+ }
38569
+ };
37980
38570
  }
37981
38571
  };
37982
- var createSkillRegistry = (initialSkills) => {
37983
- return new InMemorySkillRegistry(initialSkills);
38572
+
38573
+ // src/common/aiApi/tasks/coreConnectivityTest.ts
38574
+ var coreConnectivityTestTask = {
38575
+ id: CORE_CONNECTIVITY_TEST_TASK_ID,
38576
+ description: "\u6D4B\u8BD5 AI API \u8FDE\u901A\u6027\uFF08\u8F7B\u91CF\u6587\u672C\u8BF7\u6C42\uFF09",
38577
+ validateInput() {
38578
+ return {};
38579
+ },
38580
+ async execute(_input, ctx) {
38581
+ const config = resolveAiConnectionConfig(ctx.clientSettings);
38582
+ if (!config) {
38583
+ throw new Error("\u672A\u914D\u7F6E AI API Key");
38584
+ }
38585
+ const result = await callChat({
38586
+ baseUrl: config.baseUrl,
38587
+ apiKey: config.apiKey,
38588
+ model: config.textModel,
38589
+ systemPrompt: "You are a connectivity probe. Reply with JSON only.",
38590
+ userPrompt: 'Return JSON: {"ok":true,"reply":"OK"}',
38591
+ temperature: 0,
38592
+ maxTokens: 32,
38593
+ timeoutMs: config.timeoutMs
38594
+ });
38595
+ let ok = false;
38596
+ let reply = result.content.trim();
38597
+ try {
38598
+ const json2 = extractJsonObject(result.content);
38599
+ ok = json2.ok === true || json2.ok === "true";
38600
+ reply = String(json2.reply ?? result.content).trim();
38601
+ } catch {
38602
+ ok = /ok/i.test(result.content);
38603
+ }
38604
+ if (!ok && !reply) {
38605
+ throw new Error("\u6A21\u578B\u672A\u8FD4\u56DE\u6709\u6548\u54CD\u5E94");
38606
+ }
38607
+ return {
38608
+ data: { ok: true, reply: reply || "OK" },
38609
+ meta: {
38610
+ model: result.model,
38611
+ provider: "openai-compatible"
38612
+ }
38613
+ };
38614
+ }
37984
38615
  };
37985
38616
 
38617
+ // src/common/aiApi/registerCoreTasks.ts
38618
+ var registered = false;
38619
+ function registerCoreAiTasks() {
38620
+ if (registered) return;
38621
+ registerAiTask(coreLlmCompletionTask);
38622
+ registerAiTask(coreStructuredMultimodalTask);
38623
+ registerAiTask(coreConnectivityTestTask);
38624
+ registered = true;
38625
+ }
38626
+ var ensureCoreAiTasksRegistered = registerCoreAiTasks;
38627
+ function resetCoreAiTasksForTest() {
38628
+ registered = false;
38629
+ }
38630
+
37986
38631
  exports.About = AboutWithDefaults_default;
37987
38632
  exports.AboutBase = About_default;
37988
38633
  exports.AlertDialog = Dialog;
@@ -38005,6 +38650,9 @@ exports.BackgroundRemover = BackgroundRemover;
38005
38650
  exports.Badge = Badge;
38006
38651
  exports.BoothVaultService = BoothVaultService;
38007
38652
  exports.Button = Button;
38653
+ exports.CORE_CONNECTIVITY_TEST_TASK_ID = CORE_CONNECTIVITY_TEST_TASK_ID;
38654
+ exports.CORE_LLM_COMPLETION_TASK_ID = CORE_LLM_COMPLETION_TASK_ID;
38655
+ exports.CORE_STRUCTURED_MULTIMODAL_TASK_ID = CORE_STRUCTURED_MULTIMODAL_TASK_ID;
38008
38656
  exports.Card = Card;
38009
38657
  exports.CardContent = CardContent;
38010
38658
  exports.CardDescription = CardDescription;
@@ -38023,7 +38671,7 @@ exports.DEFAULT_FESTIVAL_CARD_CONFIG = DEFAULT_FESTIVAL_CARD_CONFIG;
38023
38671
  exports.DEFAULT_MAX_ACTIVE_FIREWORKS = DEFAULT_MAX_ACTIVE_FIREWORKS;
38024
38672
  exports.DEFAULT_MAX_PARTICLES = DEFAULT_MAX_PARTICLES;
38025
38673
  exports.DEFAULT_OPENAI_BASE_URL = DEFAULT_OPENAI_BASE_URL;
38026
- exports.DEFAULT_OPENAI_MODEL = DEFAULT_OPENAI_MODEL;
38674
+ exports.DEFAULT_TEXT_MODEL = DEFAULT_TEXT_MODEL2;
38027
38675
  exports.DanmakuPanel = DanmakuPanel;
38028
38676
  exports.Dialog = Dialog;
38029
38677
  exports.DialogClose = DialogClose;
@@ -38073,7 +38721,6 @@ exports.GenericOrderManager = GenericOrderManager;
38073
38721
  exports.Grid = Grid;
38074
38722
  exports.Home = Home_default;
38075
38723
  exports.ImageMappingPanel = ImageMappingPanel;
38076
- exports.InMemorySkillRegistry = InMemorySkillRegistry;
38077
38724
  exports.Input = Input;
38078
38725
  exports.Label = Label;
38079
38726
  exports.LocalImageMappingPanel = LocalImageMappingPanel;
@@ -38143,17 +38790,29 @@ exports.TooltipProvider = TooltipProvider;
38143
38790
  exports.TooltipTrigger = TooltipTrigger;
38144
38791
  exports.UserInfoBar = UserInfoBar;
38145
38792
  exports.WebSocketTransport = WebSocketTransport;
38146
- exports.applyPromptTemplate = applyPromptTemplate;
38793
+ exports.appendTranscriptionsToPrompt = appendTranscriptionsToPrompt;
38147
38794
  exports.arrayUtils = arrayUtils;
38795
+ exports.assertMultimodalCapableModel = assertMultimodalCapableModel;
38796
+ exports.assertValidAudioInput = assertValidAudioInput;
38797
+ exports.assertValidImageInput = assertValidImageInput;
38798
+ exports.assertValidMultimodalMedia = assertValidMultimodalMedia;
38799
+ exports.assertVisionCapableModel = assertVisionCapableModel;
38148
38800
  exports.badgeVariants = badgeVariants;
38801
+ exports.base64ToBlob = base64ToBlob;
38149
38802
  exports.bubbleShooter = bubbleShooter_exports;
38803
+ exports.buildMultimodalMessages = buildMultimodalMessages;
38150
38804
  exports.business = business_exports;
38151
38805
  exports.buttonVariants = buttonVariants;
38806
+ exports.callChat = callChat;
38807
+ exports.callCompletion = callCompletion;
38808
+ exports.callMultimodalChat = callMultimodalChat;
38152
38809
  exports.checkVoteEligibility = checkVoteEligibility;
38810
+ exports.clearAiTasksForTest = clearAiTasksForTest;
38153
38811
  exports.cn = cn;
38154
38812
  exports.common = common_exports;
38155
- exports.createAiClient = createAiClient;
38156
- exports.createChatSession = createChatSession;
38813
+ exports.coreConnectivityTestTask = coreConnectivityTestTask;
38814
+ exports.coreLlmCompletionTask = coreLlmCompletionTask;
38815
+ exports.coreStructuredMultimodalTask = coreStructuredMultimodalTask;
38157
38816
  exports.createCreateSubmissionHandler = createCreateSubmissionHandler;
38158
38817
  exports.createDefaultMikuContestConfig = createDefaultMikuContestConfig;
38159
38818
  exports.createExportSubmissionsHandler = createExportSubmissionsHandler;
@@ -38165,21 +38824,28 @@ exports.createMikuContestApiClient = createMikuContestApiClient;
38165
38824
  exports.createMikuContestDrizzlePersistenceAdapter = createMikuContestDrizzlePersistenceAdapter;
38166
38825
  exports.createMikuContestPersistentService = createMikuContestPersistentService;
38167
38826
  exports.createMikuContestService = createMikuContestService;
38168
- exports.createOpenAICompatibleProvider = createOpenAICompatibleProvider;
38169
38827
  exports.createResetVotesHandler = createResetVotesHandler;
38170
38828
  exports.createReviewSubmissionHandler = createReviewSubmissionHandler;
38171
38829
  exports.createSetVoterRestrictionHandler = createSetVoterRestrictionHandler;
38172
- exports.createSkillRegistry = createSkillRegistry;
38173
38830
  exports.createUpdateContestConfigHandler = createUpdateContestConfigHandler;
38174
38831
  exports.createVoteHandler = createVoteHandler;
38175
38832
  exports.debugUtils = debugUtils;
38176
38833
  exports.defaultMikuVotingRules = defaultMikuVotingRules;
38177
38834
  exports.defaultVocaloidBoothConfig = defaultVocaloidBoothConfig;
38835
+ exports.detectVisionMessageFormat = detectVisionMessageFormat;
38836
+ exports.ensureCoreAiTasksRegistered = ensureCoreAiTasksRegistered;
38178
38837
  exports.errorUtils = errorUtils;
38838
+ exports.extractJsonObject = extractJsonObject;
38839
+ exports.fileToAiAudioInput = fileToAiAudioInput;
38840
+ exports.fileToAiImageInput = fileToAiImageInput;
38179
38841
  exports.fileUtils = fileUtils;
38842
+ exports.filterChatModels = filterChatModels;
38180
38843
  exports.filterExperiments = filterExperiments;
38844
+ exports.filterSttModels = filterSttModels;
38845
+ exports.filterVisionModels = filterVisionModels;
38181
38846
  exports.formatTime = formatTime;
38182
38847
  exports.generateMatchCode = generateMatchCode;
38848
+ exports.getAiTask = getAiTask;
38183
38849
  exports.getAllTags = getAllTags;
38184
38850
  exports.getCategoryColor = getCategoryColor;
38185
38851
  exports.getCategoryDisplayName = getCategoryDisplayName;
@@ -38187,7 +38853,15 @@ exports.getCompletionFilterDisplayName = getCompletionFilterDisplayName;
38187
38853
  exports.getCompletionStatusColor = getCompletionStatusColor;
38188
38854
  exports.getCompletionStatusText = getCompletionStatusText;
38189
38855
  exports.getExperimentCounts = getExperimentCounts;
38856
+ exports.isAudioInputError = isAudioInputError;
38857
+ exports.isImageUrlVariantError = isImageUrlVariantError;
38858
+ exports.isKnownTextOnlyModel = isKnownTextOnlyModel;
38859
+ exports.isLikelyNativeAudioChatModel = isLikelyNativeAudioChatModel;
38860
+ exports.isLikelySttModel = isLikelySttModel;
38861
+ exports.isLikelyVisionModel = isLikelyVisionModel;
38190
38862
  exports.japaneseUtils = japaneseUtils;
38863
+ exports.listAiTasks = listAiTasks;
38864
+ exports.listOpenAiCompatibleModels = listOpenAiCompatibleModels;
38191
38865
  exports.logger = logger;
38192
38866
  exports.mikuContestConfigs = mikuContestConfigs;
38193
38867
  exports.mikuContestDbService = mikuContestDbService;
@@ -38195,20 +38869,32 @@ exports.mikuContestNotices = mikuContestNotices;
38195
38869
  exports.mikuContestSubmissions = mikuContestSubmissions;
38196
38870
  exports.mikuContestVoterRestrictions = mikuContestVoterRestrictions;
38197
38871
  exports.mikuContestVotes = mikuContestVotes;
38872
+ exports.mimeToAudioFormat = mimeToAudioFormat;
38198
38873
  exports.miniappService = miniapp_exports;
38199
38874
  exports.miniappUI = miniapp_exports2;
38200
38875
  exports.normalizeFestivalCardConfig = normalizeFestivalCardConfig;
38201
38876
  exports.normalizeMatchCode = normalizeMatchCode;
38202
- exports.normalizePromptVariables = normalizePromptVariables;
38203
38877
  exports.normalizeVocaloidBoothConfig = normalizeVocaloidBoothConfig;
38878
+ exports.pickDefaultSttModel = pickDefaultSttModel;
38879
+ exports.pickDefaultVisionModel = pickDefaultVisionModel;
38880
+ exports.registerAiTask = registerAiTask;
38881
+ exports.registerCoreAiTasks = registerCoreAiTasks;
38882
+ exports.requestJson = requestJson;
38883
+ exports.requireAiConnectionConfig = requireAiConnectionConfig;
38884
+ exports.resetCoreAiTasksForTest = resetCoreAiTasksForTest;
38204
38885
  exports.resizeFestivalCardPages = resizeFestivalCardPages;
38886
+ exports.resolveAiConnectionConfig = resolveAiConnectionConfig;
38887
+ exports.resolveAudioHandling = resolveAudioHandling;
38205
38888
  exports.resolveScreenReceiverSignalUrl = resolveScreenReceiverSignalUrl;
38206
- exports.skillToToolDefinition = skillToToolDefinition;
38889
+ exports.runAiTask = runAiTask;
38207
38890
  exports.sortByVotesDesc = sortByVotesDesc;
38208
38891
  exports.sortExperiments = sortExperiments;
38892
+ exports.splitMediaByKind = splitMediaByKind;
38209
38893
  exports.stringUtils = stringUtils;
38894
+ exports.toVisionApiErrorMessage = toVisionApiErrorMessage;
38210
38895
  exports.toVoteDayKey = toVoteDayKey;
38211
- exports.useAiChat = useAiChat;
38896
+ exports.transcribeAudio = transcribeAudio;
38897
+ exports.transcribeAudios = transcribeAudios;
38212
38898
  exports.useAsyncStorage = useAsyncStorage;
38213
38899
  exports.useBackgroundRemoval = useBackgroundRemoval;
38214
38900
  exports.useDanmakuController = useDanmakuController;