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
@@ -0,0 +1,1090 @@
1
+ // src/common/aiApi/types.ts
2
+ var CORE_LLM_COMPLETION_TASK_ID = "core.llmCompletion";
3
+ var CORE_STRUCTURED_MULTIMODAL_TASK_ID = "core.structuredMultimodal";
4
+ var CORE_CONNECTIVITY_TEST_TASK_ID = "core.connectivityTest";
5
+
6
+ // src/common/aiApi/resolveConfig.ts
7
+ var DEFAULT_BASE_URL = "https://api.openai.com/v1";
8
+ var DEFAULT_TEXT_MODEL = "gpt-4o-mini";
9
+ var DEFAULT_VISION_MODEL = "gpt-4o-mini";
10
+ var DEFAULT_AUDIO_MODEL = "whisper-1";
11
+ var DEFAULT_AUDIO_STRATEGY = "auto";
12
+ var DEFAULT_TIMEOUT_MS = 6e4;
13
+ var DEFAULT_MAX_IMAGE_BYTES = 5 * 1024 * 1024;
14
+ var DEFAULT_MAX_AUDIO_BYTES = 25 * 1024 * 1024;
15
+ function readEnv(name) {
16
+ if (typeof process === "undefined" || !process.env) {
17
+ return "";
18
+ }
19
+ return process.env[name]?.trim() ?? "";
20
+ }
21
+ function resolveAiConnectionConfig(...sources) {
22
+ const merged = {};
23
+ for (const source of sources) {
24
+ if (!source) continue;
25
+ Object.assign(merged, source);
26
+ }
27
+ const envApiKey = readEnv("AI_API_KEY") || readEnv("OPENAI_API_KEY");
28
+ const apiKey = merged.apiKey?.trim() || envApiKey;
29
+ if (!apiKey) {
30
+ return null;
31
+ }
32
+ const baseUrl = merged.baseUrl?.trim() || readEnv("AI_BASE_URL") || DEFAULT_BASE_URL;
33
+ const visionModel = merged.visionModel?.trim() || readEnv("AI_VISION_MODEL") || merged.model?.trim() || DEFAULT_VISION_MODEL;
34
+ const textModel = merged.textModel?.trim() || readEnv("AI_TEXT_MODEL") || merged.model?.trim() || visionModel || DEFAULT_TEXT_MODEL;
35
+ const audioModel = merged.audioModel?.trim() || readEnv("AI_AUDIO_MODEL") || DEFAULT_AUDIO_MODEL;
36
+ const envAudioStrategy = readEnv("AI_AUDIO_STRATEGY");
37
+ const audioStrategy = merged.audioStrategy ?? (envAudioStrategy || DEFAULT_AUDIO_STRATEGY);
38
+ return {
39
+ apiKey,
40
+ baseUrl,
41
+ model: merged.model?.trim() || textModel,
42
+ textModel,
43
+ visionModel,
44
+ audioModel,
45
+ audioStrategy,
46
+ timeoutMs: merged.timeoutMs ?? Number(readEnv("AI_TIMEOUT_MS") || DEFAULT_TIMEOUT_MS),
47
+ maxImageBytes: merged.maxImageBytes ?? Number(readEnv("AI_MAX_IMAGE_BYTES") || DEFAULT_MAX_IMAGE_BYTES),
48
+ maxAudioBytes: merged.maxAudioBytes ?? Number(readEnv("AI_MAX_AUDIO_BYTES") || DEFAULT_MAX_AUDIO_BYTES)
49
+ };
50
+ }
51
+ function requireAiConnectionConfig(...sources) {
52
+ const config = resolveAiConnectionConfig(...sources);
53
+ if (!config) {
54
+ 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");
55
+ }
56
+ return config;
57
+ }
58
+
59
+ // src/common/aiApi/requestJson.ts
60
+ async function requestJson(options) {
61
+ const { url, method = "POST", headers = {}, body, timeoutMs, requestAdapter } = options;
62
+ if (requestAdapter) {
63
+ return requestAdapter.request({
64
+ url,
65
+ method,
66
+ headers,
67
+ body
68
+ });
69
+ }
70
+ const controller = timeoutMs ? new AbortController() : void 0;
71
+ const timeoutId = timeoutMs ? setTimeout(() => {
72
+ controller?.abort();
73
+ }, timeoutMs) : void 0;
74
+ try {
75
+ const response = await fetch(url, {
76
+ method,
77
+ headers,
78
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
79
+ signal: controller?.signal
80
+ });
81
+ const text = await response.text();
82
+ let data = null;
83
+ if (text) {
84
+ try {
85
+ data = JSON.parse(text);
86
+ } catch {
87
+ data = text;
88
+ }
89
+ }
90
+ if (!response.ok) {
91
+ const record = data;
92
+ const errorMessage = (typeof record?.error === "object" ? record.error?.message : record?.error) || record?.message || `Request failed with status ${response.status}`;
93
+ const error = new Error(String(errorMessage));
94
+ error.status = response.status;
95
+ error.data = data;
96
+ throw error;
97
+ }
98
+ return data;
99
+ } finally {
100
+ if (timeoutId) {
101
+ clearTimeout(timeoutId);
102
+ }
103
+ }
104
+ }
105
+
106
+ // src/common/aiApi/callChat.ts
107
+ var DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
108
+ var DEFAULT_TEXT_MODEL2 = "gpt-4o-mini";
109
+ function joinUrl(base, path) {
110
+ return `${base.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
111
+ }
112
+ function resolveMessages(input) {
113
+ if (input.messages?.length) {
114
+ return input.messages;
115
+ }
116
+ const messages = [];
117
+ if (input.systemPrompt?.trim()) {
118
+ messages.push({ role: "system", content: input.systemPrompt.trim() });
119
+ }
120
+ if (input.userPrompt?.trim()) {
121
+ messages.push({ role: "user", content: input.userPrompt.trim() });
122
+ }
123
+ return messages;
124
+ }
125
+ async function callChat(options) {
126
+ const {
127
+ baseUrl,
128
+ apiKey,
129
+ model,
130
+ systemPrompt,
131
+ userPrompt,
132
+ messages,
133
+ temperature,
134
+ maxTokens,
135
+ topP,
136
+ stop,
137
+ timeoutMs = 6e4,
138
+ requestAdapter
139
+ } = options;
140
+ if (!baseUrl?.trim()) {
141
+ throw new Error("baseUrl \u4E3A\u5FC5\u586B");
142
+ }
143
+ if (!apiKey?.trim()) {
144
+ throw new Error("apiKey \u4E3A\u5FC5\u586B");
145
+ }
146
+ const resolvedMessages = resolveMessages({ systemPrompt, userPrompt, messages });
147
+ if (!resolvedMessages.length) {
148
+ throw new Error("userPrompt \u6216 messages \u81F3\u5C11\u63D0\u4F9B\u4E00\u9879");
149
+ }
150
+ const resolvedModel = model?.trim() || DEFAULT_TEXT_MODEL2;
151
+ const payload = {
152
+ model: resolvedModel,
153
+ messages: resolvedMessages
154
+ };
155
+ if (temperature !== void 0) payload.temperature = temperature;
156
+ if (maxTokens !== void 0) payload.max_tokens = maxTokens;
157
+ if (topP !== void 0) payload.top_p = topP;
158
+ if (stop !== void 0) payload.stop = stop;
159
+ const raw = await requestJson({
160
+ url: joinUrl(baseUrl.trim(), "chat/completions"),
161
+ method: "POST",
162
+ headers: {
163
+ "Content-Type": "application/json",
164
+ Authorization: `Bearer ${apiKey.trim()}`
165
+ },
166
+ body: payload,
167
+ timeoutMs,
168
+ requestAdapter
169
+ });
170
+ const content = raw.choices?.[0]?.message?.content ?? "";
171
+ const usage = raw.usage ? {
172
+ promptTokens: raw.usage.prompt_tokens,
173
+ completionTokens: raw.usage.completion_tokens,
174
+ totalTokens: raw.usage.total_tokens
175
+ } : void 0;
176
+ return {
177
+ content,
178
+ model: raw.model ?? resolvedModel,
179
+ usage,
180
+ raw
181
+ };
182
+ }
183
+
184
+ // src/common/aiApi/callCompletion.ts
185
+ async function callCompletion(params, clientSettings) {
186
+ const config = requireAiConnectionConfig(params.connection, clientSettings);
187
+ const model = params.model || config.textModel;
188
+ const result = await callChat({
189
+ baseUrl: config.baseUrl,
190
+ apiKey: config.apiKey,
191
+ model,
192
+ systemPrompt: params.systemPrompt,
193
+ userPrompt: params.userPrompt,
194
+ temperature: params.temperature,
195
+ maxTokens: params.maxTokens,
196
+ timeoutMs: config.timeoutMs
197
+ });
198
+ return {
199
+ content: result.content,
200
+ model: result.model || model,
201
+ raw: result.raw
202
+ };
203
+ }
204
+
205
+ // src/common/aiApi/modelHeuristics.ts
206
+ var NON_CHAT_PATTERNS = [
207
+ /embed/i,
208
+ /whisper/i,
209
+ /tts/i,
210
+ /dall-e/i,
211
+ /moderation/i,
212
+ /realtime/i,
213
+ /audio/i,
214
+ /transcrib/i,
215
+ /sora/i
216
+ ];
217
+ var VISION_HINT_PATTERNS = [
218
+ /^gpt-4o/i,
219
+ /^gpt-4-turbo/i,
220
+ /^gpt-4-vision/i,
221
+ /^gpt-4\.1/i,
222
+ /claude-3/i,
223
+ /gemini.*(pro|flash|vision)/i,
224
+ /qwen.*vl/i,
225
+ /vision/i,
226
+ /-vl/i,
227
+ /llava/i,
228
+ /doubao.*vision/i,
229
+ /glm-4v/i,
230
+ /internvl/i,
231
+ /pixtral/i,
232
+ /deepseek-vl/i
233
+ ];
234
+ var PREFERRED_VISION_MODELS = [
235
+ "gpt-4o-mini",
236
+ "gpt-4o",
237
+ "gpt-4-turbo",
238
+ "gpt-4-vision-preview",
239
+ "gpt-4.1-mini",
240
+ "gpt-4.1",
241
+ "claude-3-5-sonnet-latest",
242
+ "claude-3-5-haiku-latest",
243
+ "gemini-2.0-flash",
244
+ "gemini-1.5-flash",
245
+ "gemini-1.5-pro",
246
+ "qwen-vl-max",
247
+ "qwen2-vl-72b-instruct"
248
+ ];
249
+ function isChatModel(id) {
250
+ return !NON_CHAT_PATTERNS.some((pattern) => pattern.test(id));
251
+ }
252
+ var NATIVE_AUDIO_CHAT_PATTERNS = [
253
+ /^gpt-4o/i,
254
+ /^gpt-4-turbo/i,
255
+ /^gpt-4\.1/i,
256
+ /gemini-2\.0/i,
257
+ /gemini-1\.5/i
258
+ ];
259
+ var STT_MODEL_PATTERNS = [/whisper/i, /transcrib/i, /sensevoice/i, /paraformer/i];
260
+ function isLikelyNativeAudioChatModel(id) {
261
+ const trimmed = id.trim();
262
+ if (!trimmed) return false;
263
+ if (STT_MODEL_PATTERNS.some((pattern) => pattern.test(trimmed))) return false;
264
+ return NATIVE_AUDIO_CHAT_PATTERNS.some((pattern) => pattern.test(trimmed));
265
+ }
266
+ function isLikelySttModel(id) {
267
+ return STT_MODEL_PATTERNS.some((pattern) => pattern.test(id.trim()));
268
+ }
269
+ function filterSttModels(modelIds) {
270
+ return modelIds.filter(isLikelySttModel).sort((a, b) => a.localeCompare(b));
271
+ }
272
+ var PREFERRED_STT_MODELS = ["whisper-1", "whisper-large-v3", "whisper-large-v3-turbo"];
273
+ function pickDefaultSttModel(modelIds, current) {
274
+ const sttModels = filterSttModels(modelIds);
275
+ if (sttModels.length === 0) return void 0;
276
+ const trimmedCurrent = current?.trim();
277
+ if (trimmedCurrent && sttModels.includes(trimmedCurrent)) {
278
+ return trimmedCurrent;
279
+ }
280
+ for (const preferred of PREFERRED_STT_MODELS) {
281
+ const match = sttModels.find((id) => id === preferred || id.startsWith(`${preferred}-`));
282
+ if (match) return match;
283
+ }
284
+ return sttModels[0];
285
+ }
286
+ function isLikelyVisionModel(id) {
287
+ return VISION_HINT_PATTERNS.some((pattern) => pattern.test(id));
288
+ }
289
+ var TEXT_ONLY_MODEL_PATTERNS = [
290
+ /^deepseek-chat/i,
291
+ /^deepseek-reasoner/i,
292
+ /^deepseek-r1/i,
293
+ /^deepseek-v[34](?!.*vl)/i,
294
+ /^gpt-3\.5/i,
295
+ /^o1-mini/i,
296
+ /^o3-mini/i
297
+ ];
298
+ function isKnownTextOnlyModel(id) {
299
+ const trimmed = id.trim();
300
+ if (!trimmed) return false;
301
+ return TEXT_ONLY_MODEL_PATTERNS.some((pattern) => pattern.test(trimmed));
302
+ }
303
+ function filterChatModels(modelIds) {
304
+ return modelIds.filter(isChatModel).sort((a, b) => a.localeCompare(b));
305
+ }
306
+ function filterVisionModels(modelIds) {
307
+ return filterChatModels(modelIds).filter((id) => isLikelyVisionModel(id) && !isKnownTextOnlyModel(id)).sort((a, b) => a.localeCompare(b));
308
+ }
309
+ function matchesPreferred(modelId, preferred) {
310
+ return modelId === preferred || modelId.startsWith(`${preferred}-`);
311
+ }
312
+ function pickDefaultVisionModel(modelIds, current) {
313
+ const visionModels = filterVisionModels(modelIds);
314
+ if (visionModels.length === 0) return void 0;
315
+ const trimmedCurrent = current?.trim();
316
+ if (trimmedCurrent && visionModels.includes(trimmedCurrent)) {
317
+ return trimmedCurrent;
318
+ }
319
+ for (const preferred of PREFERRED_VISION_MODELS) {
320
+ const match = visionModels.find((id) => matchesPreferred(id, preferred));
321
+ if (match) return match;
322
+ }
323
+ return visionModels[0];
324
+ }
325
+
326
+ // src/common/aiApi/visionMessageFormats.ts
327
+ function detectVisionMessageFormat(baseUrl) {
328
+ const normalized = baseUrl.toLowerCase();
329
+ if (normalized.includes("ollama") || normalized.includes(":11434") || normalized.includes("11434/")) {
330
+ return "ollama";
331
+ }
332
+ return "openai";
333
+ }
334
+ function assertVisionCapableModel(modelId, options) {
335
+ if (!options?.hasImages) return;
336
+ const model = modelId.trim();
337
+ if (!model) {
338
+ throw new Error("\u8BC6\u56FE\u9700\u8981\u9009\u62E9\u89C6\u89C9\u6A21\u578B\uFF0C\u8BF7\u5728 AI \u8BBE\u7F6E\u4E2D\u914D\u7F6E");
339
+ }
340
+ const format = options.baseUrl ? detectVisionMessageFormat(options.baseUrl) : "openai";
341
+ if (format === "ollama") return;
342
+ if (isKnownTextOnlyModel(model)) {
343
+ throw new Error(
344
+ `\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`
345
+ );
346
+ }
347
+ if (!isLikelyVisionModel(model)) {
348
+ throw new Error(
349
+ `\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`
350
+ );
351
+ }
352
+ }
353
+ function isImageUrlVariantError(message) {
354
+ return /unknown variant [`']?image_url[`']?/i.test(message) || /expected [`']?text[`']?/i.test(message) || /does not support.*image/i.test(message);
355
+ }
356
+ function toVisionApiErrorMessage(rawMessage, modelId) {
357
+ if (isImageUrlVariantError(rawMessage)) {
358
+ 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`;
359
+ }
360
+ return rawMessage;
361
+ }
362
+
363
+ // src/common/aiApi/audioStrategy.ts
364
+ function resolveAudioHandling(options) {
365
+ if (!options.hasAudio) {
366
+ return "none";
367
+ }
368
+ if (options.strategy === "stt") {
369
+ return "stt";
370
+ }
371
+ if (options.strategy === "native") {
372
+ return "native";
373
+ }
374
+ if (detectVisionMessageFormat(options.baseUrl) === "ollama") {
375
+ return "stt";
376
+ }
377
+ if (isLikelyNativeAudioChatModel(options.model)) {
378
+ return "native";
379
+ }
380
+ return "stt";
381
+ }
382
+ function isAudioInputError(message) {
383
+ 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);
384
+ }
385
+ function appendTranscriptionsToPrompt(userPrompt, transcriptions) {
386
+ if (transcriptions.length === 0) {
387
+ return userPrompt;
388
+ }
389
+ const blocks = transcriptions.map((text, index) => {
390
+ const label = transcriptions.length === 1 ? "[\u8BED\u97F3\u8F6C\u5199]" : `[\u8BED\u97F3\u8F6C\u5199 ${index + 1}]`;
391
+ return `${label}
392
+ ${text.trim()}`;
393
+ });
394
+ const joined = blocks.join("\n\n");
395
+ const trimmedPrompt = userPrompt.trim();
396
+ return trimmedPrompt ? `${trimmedPrompt}
397
+
398
+ ${joined}` : joined;
399
+ }
400
+
401
+ // src/common/aiApi/audioUtils.ts
402
+ var ALLOWED_MIME = /* @__PURE__ */ new Set([
403
+ "audio/wav",
404
+ "audio/x-wav",
405
+ "audio/mpeg",
406
+ "audio/mp3",
407
+ "audio/mp4",
408
+ "audio/webm",
409
+ "audio/ogg",
410
+ "audio/flac"
411
+ ]);
412
+ var MIME_TO_FORMAT = {
413
+ "audio/wav": "wav",
414
+ "audio/x-wav": "wav",
415
+ "audio/mpeg": "mp3",
416
+ "audio/mp3": "mp3",
417
+ "audio/mp4": "mp4",
418
+ "audio/webm": "webm",
419
+ "audio/ogg": "ogg",
420
+ "audio/flac": "flac"
421
+ };
422
+ function mimeToAudioFormat(mimeType) {
423
+ return MIME_TO_FORMAT[mimeType] ?? mimeType.split("/").pop() ?? "wav";
424
+ }
425
+ function assertValidAudioInput(audio, maxAudioBytes = 25 * 1024 * 1024) {
426
+ if (!audio.base64?.trim()) {
427
+ throw new Error("\u97F3\u9891\u6570\u636E\u4E0D\u80FD\u4E3A\u7A7A");
428
+ }
429
+ if (!ALLOWED_MIME.has(audio.mimeType)) {
430
+ throw new Error(`\u4E0D\u652F\u6301\u7684\u97F3\u9891\u683C\u5F0F: ${audio.mimeType}`);
431
+ }
432
+ const byteLength = estimateBase64ByteLength(audio.base64);
433
+ if (byteLength > maxAudioBytes) {
434
+ throw new Error(`\u97F3\u9891\u8FC7\u5927\uFF0C\u6700\u5927 ${Math.round(maxAudioBytes / 1024 / 1024)}MB`);
435
+ }
436
+ }
437
+ function estimateBase64ByteLength(base64) {
438
+ if (typeof Buffer !== "undefined") {
439
+ return Buffer.byteLength(base64, "base64");
440
+ }
441
+ const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
442
+ return Math.floor(base64.length * 3 / 4) - padding;
443
+ }
444
+ async function fileToAiAudioInput(file) {
445
+ const buffer = await file.arrayBuffer();
446
+ if (typeof Buffer !== "undefined") {
447
+ return {
448
+ base64: Buffer.from(buffer).toString("base64"),
449
+ mimeType: file.type || "audio/wav"
450
+ };
451
+ }
452
+ const bytes = new Uint8Array(buffer);
453
+ let binary = "";
454
+ for (let index = 0; index < bytes.length; index += 1) {
455
+ binary += String.fromCharCode(bytes[index]);
456
+ }
457
+ return { base64: btoa(binary), mimeType: file.type || "audio/wav" };
458
+ }
459
+ function base64ToBlob(base64, mimeType) {
460
+ if (typeof Buffer !== "undefined") {
461
+ const bytes2 = Buffer.from(base64, "base64");
462
+ return new Blob([bytes2], { type: mimeType });
463
+ }
464
+ const binary = atob(base64);
465
+ const bytes = new Uint8Array(binary.length);
466
+ for (let index = 0; index < binary.length; index += 1) {
467
+ bytes[index] = binary.charCodeAt(index);
468
+ }
469
+ return new Blob([bytes], { type: mimeType });
470
+ }
471
+
472
+ // src/common/aiApi/multimodalMessageFormats.ts
473
+ function buildMultimodalMessages(options) {
474
+ const { systemPrompt, userPrompt, images, nativeAudios, format } = options;
475
+ const hasImages = images.length > 0;
476
+ const hasNativeAudio = nativeAudios.length > 0;
477
+ if (format === "ollama" && hasImages) {
478
+ return [
479
+ { role: "system", content: systemPrompt },
480
+ {
481
+ role: "user",
482
+ content: userPrompt,
483
+ images: images.map((image) => image.base64)
484
+ }
485
+ ];
486
+ }
487
+ const userContent = [{ type: "text", text: userPrompt }];
488
+ for (const image of images) {
489
+ userContent.push({
490
+ type: "image_url",
491
+ image_url: {
492
+ url: `data:${image.mimeType};base64,${image.base64}`
493
+ }
494
+ });
495
+ }
496
+ for (const audio of nativeAudios) {
497
+ userContent.push({
498
+ type: "input_audio",
499
+ input_audio: {
500
+ data: audio.base64,
501
+ format: mimeToAudioFormat(audio.mimeType)
502
+ }
503
+ });
504
+ }
505
+ const useStructuredContent = hasImages || hasNativeAudio;
506
+ return [
507
+ { role: "system", content: systemPrompt },
508
+ { role: "user", content: useStructuredContent ? userContent : userPrompt }
509
+ ];
510
+ }
511
+ function assertMultimodalCapableModel(modelId, options) {
512
+ assertVisionCapableModel(modelId, {
513
+ baseUrl: options.baseUrl,
514
+ hasImages: options.hasImages
515
+ });
516
+ if (!options.hasNativeAudio) return;
517
+ const format = options.baseUrl ? detectVisionMessageFormat(options.baseUrl) : "openai";
518
+ if (format === "ollama") {
519
+ throw new Error('\u5F53\u524D Ollama \u8FDE\u63A5\u4E0D\u652F\u6301 chat \u5185\u5D4C\u97F3\u9891\uFF0C\u8BF7\u6539\u7528 audioStrategy: "stt" \u6216 "auto"');
520
+ }
521
+ }
522
+
523
+ // src/common/aiApi/imageUtils.ts
524
+ var ALLOWED_MIME2 = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp", "image/gif"]);
525
+ function assertValidImageInput(image, maxImageBytes = 5 * 1024 * 1024) {
526
+ if (!image.base64?.trim()) {
527
+ throw new Error("\u56FE\u7247\u6570\u636E\u4E0D\u80FD\u4E3A\u7A7A");
528
+ }
529
+ if (!ALLOWED_MIME2.has(image.mimeType)) {
530
+ throw new Error(`\u4E0D\u652F\u6301\u7684\u56FE\u7247\u683C\u5F0F: ${image.mimeType}`);
531
+ }
532
+ const byteLength = estimateBase64ByteLength2(image.base64);
533
+ if (byteLength > maxImageBytes) {
534
+ throw new Error(`\u56FE\u7247\u8FC7\u5927\uFF0C\u6700\u5927 ${Math.round(maxImageBytes / 1024 / 1024)}MB`);
535
+ }
536
+ }
537
+ function estimateBase64ByteLength2(base64) {
538
+ if (typeof Buffer !== "undefined") {
539
+ return Buffer.byteLength(base64, "base64");
540
+ }
541
+ const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
542
+ return Math.floor(base64.length * 3 / 4) - padding;
543
+ }
544
+ async function fileToAiImageInput(file) {
545
+ const buffer = await file.arrayBuffer();
546
+ if (typeof Buffer !== "undefined") {
547
+ return {
548
+ base64: Buffer.from(buffer).toString("base64"),
549
+ mimeType: file.type || "image/jpeg"
550
+ };
551
+ }
552
+ const bytes = new Uint8Array(buffer);
553
+ let binary = "";
554
+ for (let index = 0; index < bytes.length; index += 1) {
555
+ binary += String.fromCharCode(bytes[index]);
556
+ }
557
+ const base64 = btoa(binary);
558
+ return { base64, mimeType: file.type || "image/jpeg" };
559
+ }
560
+
561
+ // src/common/aiApi/mediaUtils.ts
562
+ function splitMediaByKind(media) {
563
+ const images = [];
564
+ const audios = [];
565
+ for (const item of media) {
566
+ if (item.kind === "image") {
567
+ images.push(item);
568
+ } else {
569
+ audios.push(item);
570
+ }
571
+ }
572
+ return { images, audios };
573
+ }
574
+ function assertValidMultimodalMedia(media, limits) {
575
+ const items = media ?? [];
576
+ const { images, audios } = splitMediaByKind(items);
577
+ const maxImages = limits.maxImages ?? 8;
578
+ const maxAudios = limits.maxAudios ?? 4;
579
+ if (images.length > maxImages) {
580
+ throw new Error(`\u56FE\u7247\u6570\u91CF\u8FC7\u591A\uFF0C\u6700\u591A ${maxImages} \u5F20`);
581
+ }
582
+ if (audios.length > maxAudios) {
583
+ throw new Error(`\u97F3\u9891\u6570\u91CF\u8FC7\u591A\uFF0C\u6700\u591A ${maxAudios} \u6BB5`);
584
+ }
585
+ for (const image of images) {
586
+ assertValidImageInput(
587
+ { base64: image.base64, mimeType: image.mimeType },
588
+ limits.maxImageBytes
589
+ );
590
+ }
591
+ for (const audio of audios) {
592
+ assertValidAudioInput(
593
+ { base64: audio.base64, mimeType: audio.mimeType },
594
+ limits.maxAudioBytes
595
+ );
596
+ }
597
+ return items;
598
+ }
599
+
600
+ // src/common/aiApi/transcribeAudio.ts
601
+ function joinUrl2(baseUrl, path) {
602
+ return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
603
+ }
604
+ async function transcribeAudio(options) {
605
+ const { audio, config, model, language } = options;
606
+ const sttModel = model?.trim() || config.audioModel;
607
+ const blob = base64ToBlob(audio.base64, audio.mimeType);
608
+ const extension = mimeToAudioFormat(audio.mimeType);
609
+ const form = new FormData();
610
+ form.append("file", blob, `audio.${extension}`);
611
+ form.append("model", sttModel);
612
+ if (language?.trim()) {
613
+ form.append("language", language.trim());
614
+ }
615
+ const controller = new AbortController();
616
+ const timeoutId = setTimeout(() => controller.abort(), config.timeoutMs);
617
+ try {
618
+ const response = await fetch(joinUrl2(config.baseUrl, "audio/transcriptions"), {
619
+ method: "POST",
620
+ headers: {
621
+ Authorization: `Bearer ${config.apiKey}`
622
+ },
623
+ body: form,
624
+ signal: controller.signal
625
+ });
626
+ const text = await response.text();
627
+ let data = null;
628
+ if (text) {
629
+ try {
630
+ data = JSON.parse(text);
631
+ } catch {
632
+ data = text;
633
+ }
634
+ }
635
+ if (!response.ok) {
636
+ const record = data;
637
+ const errorMessage = (typeof record?.error === "object" ? record.error?.message : record?.error) || record?.message || `STT \u8BF7\u6C42\u5931\u8D25 (${response.status})`;
638
+ throw new Error(String(errorMessage));
639
+ }
640
+ if (typeof data === "string") {
641
+ return data.trim();
642
+ }
643
+ const parsed = data;
644
+ return (parsed.text ?? "").trim();
645
+ } finally {
646
+ clearTimeout(timeoutId);
647
+ }
648
+ }
649
+ async function transcribeAudios(audios, config, model) {
650
+ const results = [];
651
+ for (const audio of audios) {
652
+ results.push(await transcribeAudio({ audio, config, model }));
653
+ }
654
+ return results;
655
+ }
656
+
657
+ // src/common/aiApi/callMultimodalChat.ts
658
+ function joinUrl3(baseUrl, path) {
659
+ return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
660
+ }
661
+ async function requestMultimodalChat(options) {
662
+ const payload = {
663
+ model: options.model,
664
+ messages: options.messages,
665
+ temperature: options.temperature ?? 0.2
666
+ };
667
+ if (options.maxTokens !== void 0) {
668
+ payload.max_tokens = options.maxTokens;
669
+ }
670
+ if (options.jsonMode) {
671
+ payload.response_format = { type: "json_object" };
672
+ }
673
+ const raw = await requestJson({
674
+ url: joinUrl3(options.config.baseUrl, "chat/completions"),
675
+ method: "POST",
676
+ headers: {
677
+ "Content-Type": "application/json",
678
+ Authorization: `Bearer ${options.config.apiKey}`
679
+ },
680
+ body: payload,
681
+ timeoutMs: options.config.timeoutMs
682
+ });
683
+ return {
684
+ content: raw.choices?.[0]?.message?.content ?? "",
685
+ model: raw.model ?? options.model,
686
+ raw
687
+ };
688
+ }
689
+ function toAudioInputs(audios) {
690
+ return audios.map((audio) => ({ base64: audio.base64, mimeType: audio.mimeType }));
691
+ }
692
+ async function callMultimodalChat(params, clientSettings) {
693
+ const config = requireAiConnectionConfig(params.connection, clientSettings);
694
+ const media = assertValidMultimodalMedia(params.media, {
695
+ maxImageBytes: config.maxImageBytes,
696
+ maxAudioBytes: config.maxAudioBytes
697
+ });
698
+ const { images, audios } = splitMediaByKind(media);
699
+ const hasImages = images.length > 0;
700
+ const hasAudio = audios.length > 0;
701
+ const model = params.model || (hasImages ? config.visionModel : hasAudio ? config.visionModel : config.textModel);
702
+ const strategy = params.audioStrategy ?? config.audioStrategy;
703
+ const format = detectVisionMessageFormat(config.baseUrl);
704
+ let audioHandling = resolveAudioHandling({
705
+ hasAudio,
706
+ strategy,
707
+ model,
708
+ baseUrl: config.baseUrl
709
+ });
710
+ let userPrompt = params.userPrompt;
711
+ let transcriptions;
712
+ let nativeAudios = [];
713
+ if (audioHandling === "stt" && hasAudio) {
714
+ transcriptions = await transcribeAudios(toAudioInputs(audios), config);
715
+ userPrompt = appendTranscriptionsToPrompt(userPrompt, transcriptions);
716
+ } else if (audioHandling === "native" && hasAudio) {
717
+ nativeAudios = audios;
718
+ }
719
+ assertMultimodalCapableModel(model, {
720
+ baseUrl: config.baseUrl,
721
+ hasImages,
722
+ hasNativeAudio: nativeAudios.length > 0
723
+ });
724
+ const messages = buildMultimodalMessages({
725
+ systemPrompt: params.systemPrompt,
726
+ userPrompt,
727
+ images,
728
+ nativeAudios,
729
+ format
730
+ });
731
+ try {
732
+ const result = await requestMultimodalChat({
733
+ config,
734
+ model,
735
+ messages,
736
+ temperature: params.temperature,
737
+ maxTokens: params.maxTokens,
738
+ jsonMode: params.jsonMode
739
+ });
740
+ return {
741
+ ...result,
742
+ audioHandling,
743
+ transcriptions
744
+ };
745
+ } catch (error) {
746
+ const message = error instanceof Error ? error.message : "AI \u8BF7\u6C42\u5931\u8D25";
747
+ if (audioHandling === "native" && hasAudio && strategy === "auto" && isAudioInputError(message)) {
748
+ transcriptions = await transcribeAudios(toAudioInputs(audios), config);
749
+ userPrompt = appendTranscriptionsToPrompt(params.userPrompt, transcriptions);
750
+ audioHandling = "stt";
751
+ const fallbackMessages = buildMultimodalMessages({
752
+ systemPrompt: params.systemPrompt,
753
+ userPrompt,
754
+ images,
755
+ nativeAudios: [],
756
+ format
757
+ });
758
+ const result = await requestMultimodalChat({
759
+ config,
760
+ model,
761
+ messages: fallbackMessages,
762
+ temperature: params.temperature,
763
+ maxTokens: params.maxTokens,
764
+ jsonMode: params.jsonMode
765
+ });
766
+ return {
767
+ ...result,
768
+ audioHandling,
769
+ transcriptions
770
+ };
771
+ }
772
+ throw new Error(toVisionApiErrorMessage(message, model));
773
+ }
774
+ }
775
+
776
+ // src/common/aiApi/jsonUtils.ts
777
+ function extractJsonObject(text) {
778
+ const trimmed = text.trim();
779
+ if (!trimmed) {
780
+ throw new Error("\u6A21\u578B\u8FD4\u56DE\u4E3A\u7A7A");
781
+ }
782
+ try {
783
+ const parsed = JSON.parse(trimmed);
784
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
785
+ return parsed;
786
+ }
787
+ } catch {
788
+ }
789
+ const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
790
+ if (fenced?.[1]) {
791
+ const parsed = JSON.parse(fenced[1].trim());
792
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
793
+ return parsed;
794
+ }
795
+ }
796
+ const start = trimmed.indexOf("{");
797
+ const end = trimmed.lastIndexOf("}");
798
+ if (start >= 0 && end > start) {
799
+ const parsed = JSON.parse(trimmed.slice(start, end + 1));
800
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
801
+ return parsed;
802
+ }
803
+ }
804
+ throw new Error("\u65E0\u6CD5\u89E3\u6790\u6A21\u578B JSON \u8F93\u51FA");
805
+ }
806
+
807
+ // src/common/aiApi/listModels.ts
808
+ function joinUrl4(baseUrl, path) {
809
+ return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
810
+ }
811
+ function parseModelIds(raw) {
812
+ if (Array.isArray(raw.data)) {
813
+ return raw.data.map((item) => item.id).filter((id) => Boolean(id));
814
+ }
815
+ if (Array.isArray(raw.models)) {
816
+ return raw.models.map((item) => typeof item === "string" ? item : item.id).filter((id) => Boolean(id));
817
+ }
818
+ return [];
819
+ }
820
+ async function listOpenAiCompatibleModels(clientSettings, currentVisionModel) {
821
+ const config = resolveAiConnectionConfig(clientSettings);
822
+ if (!config) {
823
+ 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");
824
+ }
825
+ const raw = await requestJson({
826
+ url: joinUrl4(config.baseUrl, "models"),
827
+ method: "GET",
828
+ headers: {
829
+ Authorization: `Bearer ${config.apiKey}`
830
+ },
831
+ timeoutMs: config.timeoutMs
832
+ });
833
+ const modelIds = parseModelIds(raw);
834
+ if (modelIds.length === 0) {
835
+ throw new Error("\u63A5\u53E3\u672A\u8FD4\u56DE\u53EF\u7528\u6A21\u578B");
836
+ }
837
+ const models = filterChatModels(modelIds);
838
+ const visionModels = filterVisionModels(modelIds);
839
+ const suggestedVisionModel = pickDefaultVisionModel(modelIds, currentVisionModel);
840
+ return { models, visionModels, suggestedVisionModel };
841
+ }
842
+
843
+ // src/common/aiApi/taskRegistry.ts
844
+ var registry = /* @__PURE__ */ new Map();
845
+ function registerAiTask(task) {
846
+ if (registry.has(task.id)) {
847
+ console.warn(`[aiApi] task "${task.id}" already registered, skipping duplicate`);
848
+ return;
849
+ }
850
+ registry.set(task.id, task);
851
+ }
852
+ function getAiTask(taskId) {
853
+ return registry.get(taskId);
854
+ }
855
+ function listAiTasks() {
856
+ return Array.from(registry.keys());
857
+ }
858
+ function clearAiTasksForTest() {
859
+ registry.clear();
860
+ }
861
+
862
+ // src/common/aiApi/runTask.ts
863
+ function extractInputConnection(input) {
864
+ if (!input || typeof input !== "object") return void 0;
865
+ const connection = input.connection;
866
+ return connection && typeof connection === "object" ? connection : void 0;
867
+ }
868
+ async function runAiTask(taskId, input, ctx = {}) {
869
+ const started = Date.now();
870
+ if (!resolveAiConnectionConfig(extractInputConnection(input), ctx.clientSettings)) {
871
+ return {
872
+ success: false,
873
+ taskId,
874
+ error: {
875
+ code: "AI_CONFIG_MISSING",
876
+ message: "\u672A\u914D\u7F6E AI API Key\uFF0C\u8BF7\u4F20\u5165 clientSettings.connection \u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF AI_API_KEY"
877
+ }
878
+ };
879
+ }
880
+ const task = getAiTask(taskId);
881
+ if (!task) {
882
+ return {
883
+ success: false,
884
+ taskId,
885
+ error: {
886
+ code: "TASK_NOT_FOUND",
887
+ message: `\u672A\u6CE8\u518C\u7684\u4EFB\u52A1: ${taskId}`
888
+ }
889
+ };
890
+ }
891
+ try {
892
+ const validated = task.validateInput(input);
893
+ const result = await task.execute(validated, ctx);
894
+ return {
895
+ success: true,
896
+ taskId,
897
+ data: result.data,
898
+ meta: {
899
+ model: result.meta?.model ?? "unknown",
900
+ latencyMs: Date.now() - started,
901
+ provider: result.meta?.provider ?? "openai-compatible",
902
+ confidence: result.meta?.confidence,
903
+ rawSummary: result.meta?.rawSummary
904
+ }
905
+ };
906
+ } catch (error) {
907
+ const message = error instanceof Error ? error.message : "AI \u4EFB\u52A1\u6267\u884C\u5931\u8D25";
908
+ const code = classifyError(message);
909
+ return {
910
+ success: false,
911
+ taskId,
912
+ error: { code, message },
913
+ meta: {
914
+ model: "unknown",
915
+ latencyMs: Date.now() - started
916
+ }
917
+ };
918
+ }
919
+ }
920
+ function classifyError(message) {
921
+ if (message.includes("\u672A\u914D\u7F6E") || message.includes("AI_API_KEY") || message.includes("apiKey")) {
922
+ return "AI_CONFIG_MISSING";
923
+ }
924
+ if (message.includes("\u56FE\u7247") || message.includes("\u683C\u5F0F")) {
925
+ return message.includes("\u8FC7\u5927") ? "PAYLOAD_TOO_LARGE" : "UNSUPPORTED_MEDIA";
926
+ }
927
+ if (message.includes("\u89E3\u6790") || message.includes("JSON")) {
928
+ return "AI_PARSE_FAILED";
929
+ }
930
+ if (message.includes("\u5FC5\u586B") || message.includes("\u65E0\u6548")) {
931
+ return "INVALID_INPUT";
932
+ }
933
+ return "AI_REQUEST_FAILED";
934
+ }
935
+
936
+ // src/common/aiApi/tasks/coreLlmCompletion.ts
937
+ function isTextCompletionInput(input) {
938
+ if (!input || typeof input !== "object") return false;
939
+ const value = input;
940
+ return typeof value.userPrompt === "string" && value.userPrompt.trim().length > 0;
941
+ }
942
+ var coreLlmCompletionTask = {
943
+ id: CORE_LLM_COMPLETION_TASK_ID,
944
+ description: "\u901A\u7528\u6587\u672C\u8865\u5168\uFF1Asystem/user \u63D0\u793A\u8BCD \u2192 \u6A21\u578B\u6587\u672C",
945
+ validateInput(input) {
946
+ if (!isTextCompletionInput(input)) {
947
+ throw new Error("userPrompt \u4E3A\u5FC5\u586B");
948
+ }
949
+ return input;
950
+ },
951
+ async execute(input, ctx) {
952
+ const result = await callCompletion(
953
+ {
954
+ systemPrompt: input.systemPrompt,
955
+ userPrompt: input.userPrompt,
956
+ model: input.model,
957
+ temperature: input.temperature,
958
+ maxTokens: input.maxTokens,
959
+ connection: input.connection
960
+ },
961
+ ctx.clientSettings
962
+ );
963
+ return {
964
+ data: {
965
+ content: result.content,
966
+ rawText: result.content
967
+ },
968
+ meta: {
969
+ model: result.model,
970
+ provider: "openai-compatible"
971
+ }
972
+ };
973
+ }
974
+ };
975
+
976
+ // src/common/aiApi/tasks/coreStructuredMultimodal.ts
977
+ function isStructuredMultimodalInput(input) {
978
+ if (!input || typeof input !== "object") return false;
979
+ const value = input;
980
+ return typeof value.systemPrompt === "string" && typeof value.userPrompt === "string";
981
+ }
982
+ var coreStructuredMultimodalTask = {
983
+ id: CORE_STRUCTURED_MULTIMODAL_TASK_ID,
984
+ description: "\u901A\u7528\u7ED3\u6784\u5316\u591A\u6A21\u6001\u4EFB\u52A1\uFF1A\u6587\u672C + \u53EF\u9009\u56FE\u7247/\u8BED\u97F3 \u2192 JSON",
985
+ validateInput(input) {
986
+ if (!isStructuredMultimodalInput(input)) {
987
+ throw new Error("systemPrompt \u4E0E userPrompt \u4E3A\u5FC5\u586B");
988
+ }
989
+ const config = resolveAiConnectionConfig(input.connection);
990
+ assertValidMultimodalMedia(input.media, {
991
+ maxImageBytes: config?.maxImageBytes ?? 5 * 1024 * 1024,
992
+ maxAudioBytes: config?.maxAudioBytes ?? 25 * 1024 * 1024
993
+ });
994
+ return input;
995
+ },
996
+ async execute(input, ctx) {
997
+ const schemaHint = input.jsonSchemaHint ? `
998
+
999
+ \u8BF7\u4E25\u683C\u8F93\u51FA JSON \u5BF9\u8C61\uFF0C\u7ED3\u6784\u53C2\u8003\uFF1A
1000
+ ${input.jsonSchemaHint}` : "\n\n\u8BF7\u4E25\u683C\u8F93\u51FA JSON \u5BF9\u8C61\uFF0C\u4E0D\u8981\u5305\u542B Markdown \u4EE3\u7801\u5757\u3002";
1001
+ const result = await callMultimodalChat(
1002
+ {
1003
+ systemPrompt: input.systemPrompt,
1004
+ userPrompt: `${input.userPrompt}${schemaHint}`,
1005
+ media: input.media,
1006
+ model: input.model,
1007
+ temperature: input.temperature ?? 0.2,
1008
+ maxTokens: input.maxTokens,
1009
+ jsonMode: true,
1010
+ audioStrategy: input.audioStrategy,
1011
+ connection: input.connection
1012
+ },
1013
+ ctx.clientSettings
1014
+ );
1015
+ const json = extractJsonObject(result.content);
1016
+ return {
1017
+ data: {
1018
+ json,
1019
+ rawText: result.content
1020
+ },
1021
+ meta: {
1022
+ model: result.model,
1023
+ provider: "openai-compatible",
1024
+ rawSummary: result.audioHandling ? `audio=${result.audioHandling}` : void 0
1025
+ }
1026
+ };
1027
+ }
1028
+ };
1029
+
1030
+ // src/common/aiApi/tasks/coreConnectivityTest.ts
1031
+ var coreConnectivityTestTask = {
1032
+ id: CORE_CONNECTIVITY_TEST_TASK_ID,
1033
+ description: "\u6D4B\u8BD5 AI API \u8FDE\u901A\u6027\uFF08\u8F7B\u91CF\u6587\u672C\u8BF7\u6C42\uFF09",
1034
+ validateInput() {
1035
+ return {};
1036
+ },
1037
+ async execute(_input, ctx) {
1038
+ const config = resolveAiConnectionConfig(ctx.clientSettings);
1039
+ if (!config) {
1040
+ throw new Error("\u672A\u914D\u7F6E AI API Key");
1041
+ }
1042
+ const result = await callChat({
1043
+ baseUrl: config.baseUrl,
1044
+ apiKey: config.apiKey,
1045
+ model: config.textModel,
1046
+ systemPrompt: "You are a connectivity probe. Reply with JSON only.",
1047
+ userPrompt: 'Return JSON: {"ok":true,"reply":"OK"}',
1048
+ temperature: 0,
1049
+ maxTokens: 32,
1050
+ timeoutMs: config.timeoutMs
1051
+ });
1052
+ let ok = false;
1053
+ let reply = result.content.trim();
1054
+ try {
1055
+ const json = extractJsonObject(result.content);
1056
+ ok = json.ok === true || json.ok === "true";
1057
+ reply = String(json.reply ?? result.content).trim();
1058
+ } catch {
1059
+ ok = /ok/i.test(result.content);
1060
+ }
1061
+ if (!ok && !reply) {
1062
+ throw new Error("\u6A21\u578B\u672A\u8FD4\u56DE\u6709\u6548\u54CD\u5E94");
1063
+ }
1064
+ return {
1065
+ data: { ok: true, reply: reply || "OK" },
1066
+ meta: {
1067
+ model: result.model,
1068
+ provider: "openai-compatible"
1069
+ }
1070
+ };
1071
+ }
1072
+ };
1073
+
1074
+ // src/common/aiApi/registerCoreTasks.ts
1075
+ var registered = false;
1076
+ function registerCoreAiTasks() {
1077
+ if (registered) return;
1078
+ registerAiTask(coreLlmCompletionTask);
1079
+ registerAiTask(coreStructuredMultimodalTask);
1080
+ registerAiTask(coreConnectivityTestTask);
1081
+ registered = true;
1082
+ }
1083
+ var ensureCoreAiTasksRegistered = registerCoreAiTasks;
1084
+ function resetCoreAiTasksForTest() {
1085
+ registered = false;
1086
+ }
1087
+
1088
+ export { CORE_CONNECTIVITY_TEST_TASK_ID, CORE_LLM_COMPLETION_TASK_ID, CORE_STRUCTURED_MULTIMODAL_TASK_ID, DEFAULT_OPENAI_BASE_URL, DEFAULT_TEXT_MODEL2 as DEFAULT_TEXT_MODEL, appendTranscriptionsToPrompt, assertMultimodalCapableModel, assertValidAudioInput, assertValidImageInput, assertValidMultimodalMedia, assertVisionCapableModel, base64ToBlob, buildMultimodalMessages, callChat, callCompletion, callMultimodalChat, clearAiTasksForTest, coreConnectivityTestTask, coreLlmCompletionTask, coreStructuredMultimodalTask, detectVisionMessageFormat, ensureCoreAiTasksRegistered, extractJsonObject, fileToAiAudioInput, fileToAiImageInput, filterChatModels, filterSttModels, filterVisionModels, getAiTask, isAudioInputError, isImageUrlVariantError, isKnownTextOnlyModel, isLikelyNativeAudioChatModel, isLikelySttModel, isLikelyVisionModel, listAiTasks, listOpenAiCompatibleModels, mimeToAudioFormat, pickDefaultSttModel, pickDefaultVisionModel, registerAiTask, registerCoreAiTasks, requestJson, requireAiConnectionConfig, resetCoreAiTasksForTest, resolveAiConnectionConfig, resolveAudioHandling, runAiTask, splitMediaByKind, toVisionApiErrorMessage, transcribeAudio, transcribeAudios };
1089
+ //# sourceMappingURL=chunk-ZJLS5JU5.mjs.map
1090
+ //# sourceMappingURL=chunk-ZJLS5JU5.mjs.map