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