@aituber-onair/chat 0.32.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +62 -4
- package/README.md +63 -4
- package/dist/cjs/constants/deepseek.d.ts +11 -0
- package/dist/cjs/constants/deepseek.d.ts.map +1 -0
- package/dist/cjs/constants/deepseek.js +22 -0
- package/dist/cjs/constants/deepseek.js.map +1 -0
- package/dist/cjs/constants/gemini.d.ts +11 -0
- package/dist/cjs/constants/gemini.d.ts.map +1 -1
- package/dist/cjs/constants/gemini.js +24 -7
- package/dist/cjs/constants/gemini.js.map +1 -1
- package/dist/cjs/constants/index.d.ts +2 -0
- package/dist/cjs/constants/index.d.ts.map +1 -1
- package/dist/cjs/constants/index.js +2 -0
- package/dist/cjs/constants/index.js.map +1 -1
- package/dist/cjs/constants/mistral.d.ts +16 -0
- package/dist/cjs/constants/mistral.d.ts.map +1 -0
- package/dist/cjs/constants/mistral.js +45 -0
- package/dist/cjs/constants/mistral.js.map +1 -0
- package/dist/cjs/index.d.ts +5 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +11 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/services/providers/ChatServiceProvider.d.ts +9 -1
- package/dist/cjs/services/providers/ChatServiceProvider.d.ts.map +1 -1
- package/dist/cjs/services/providers/deepseek/DeepSeekChatService.d.ts +7 -0
- package/dist/cjs/services/providers/deepseek/DeepSeekChatService.d.ts.map +1 -0
- package/dist/cjs/services/providers/deepseek/DeepSeekChatService.js +12 -0
- package/dist/cjs/services/providers/deepseek/DeepSeekChatService.js.map +1 -0
- package/dist/cjs/services/providers/deepseek/DeepSeekChatServiceProvider.d.ts +16 -0
- package/dist/cjs/services/providers/deepseek/DeepSeekChatServiceProvider.d.ts.map +1 -0
- package/dist/cjs/services/providers/deepseek/DeepSeekChatServiceProvider.js +57 -0
- package/dist/cjs/services/providers/deepseek/DeepSeekChatServiceProvider.js.map +1 -0
- package/dist/cjs/services/providers/gemini/GeminiChatService.js +1 -1
- package/dist/cjs/services/providers/gemini/GeminiChatServiceProvider.d.ts.map +1 -1
- package/dist/cjs/services/providers/gemini/GeminiChatServiceProvider.js +2 -15
- package/dist/cjs/services/providers/gemini/GeminiChatServiceProvider.js.map +1 -1
- package/dist/cjs/services/providers/index.d.ts +3 -1
- package/dist/cjs/services/providers/index.d.ts.map +1 -1
- package/dist/cjs/services/providers/index.js +4 -0
- package/dist/cjs/services/providers/index.js.map +1 -1
- package/dist/cjs/services/providers/mistral/MistralChatService.d.ts +8 -0
- package/dist/cjs/services/providers/mistral/MistralChatService.d.ts.map +1 -0
- package/dist/cjs/services/providers/mistral/MistralChatService.js +12 -0
- package/dist/cjs/services/providers/mistral/MistralChatService.js.map +1 -0
- package/dist/cjs/services/providers/mistral/MistralChatServiceProvider.d.ts +17 -0
- package/dist/cjs/services/providers/mistral/MistralChatServiceProvider.d.ts.map +1 -0
- package/dist/cjs/services/providers/mistral/MistralChatServiceProvider.js +72 -0
- package/dist/cjs/services/providers/mistral/MistralChatServiceProvider.js.map +1 -0
- package/dist/cjs/services/providers/openai/OpenAIChatService.d.ts +2 -0
- package/dist/cjs/services/providers/openai/OpenAIChatService.d.ts.map +1 -1
- package/dist/cjs/services/providers/openai/OpenAIChatService.js +42 -4
- package/dist/cjs/services/providers/openai/OpenAIChatService.js.map +1 -1
- package/dist/cjs/utils/openaiCompatibleSse.d.ts.map +1 -1
- package/dist/cjs/utils/openaiCompatibleSse.js +32 -6
- package/dist/cjs/utils/openaiCompatibleSse.js.map +1 -1
- package/dist/esm/constants/deepseek.d.ts +11 -0
- package/dist/esm/constants/deepseek.d.ts.map +1 -0
- package/dist/esm/constants/deepseek.js +19 -0
- package/dist/esm/constants/deepseek.js.map +1 -0
- package/dist/esm/constants/gemini.d.ts +11 -0
- package/dist/esm/constants/gemini.d.ts.map +1 -1
- package/dist/esm/constants/gemini.js +23 -6
- package/dist/esm/constants/gemini.js.map +1 -1
- package/dist/esm/constants/index.d.ts +2 -0
- package/dist/esm/constants/index.d.ts.map +1 -1
- package/dist/esm/constants/index.js +2 -0
- package/dist/esm/constants/index.js.map +1 -1
- package/dist/esm/constants/mistral.d.ts +16 -0
- package/dist/esm/constants/mistral.d.ts.map +1 -0
- package/dist/esm/constants/mistral.js +39 -0
- package/dist/esm/constants/mistral.js.map +1 -0
- package/dist/esm/index.d.ts +5 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/services/providers/ChatServiceProvider.d.ts +9 -1
- package/dist/esm/services/providers/ChatServiceProvider.d.ts.map +1 -1
- package/dist/esm/services/providers/deepseek/DeepSeekChatService.d.ts +7 -0
- package/dist/esm/services/providers/deepseek/DeepSeekChatService.d.ts.map +1 -0
- package/dist/esm/services/providers/deepseek/DeepSeekChatService.js +8 -0
- package/dist/esm/services/providers/deepseek/DeepSeekChatService.js.map +1 -0
- package/dist/esm/services/providers/deepseek/DeepSeekChatServiceProvider.d.ts +16 -0
- package/dist/esm/services/providers/deepseek/DeepSeekChatServiceProvider.d.ts.map +1 -0
- package/dist/esm/services/providers/deepseek/DeepSeekChatServiceProvider.js +53 -0
- package/dist/esm/services/providers/deepseek/DeepSeekChatServiceProvider.js.map +1 -0
- package/dist/esm/services/providers/gemini/GeminiChatService.js +2 -2
- package/dist/esm/services/providers/gemini/GeminiChatServiceProvider.d.ts.map +1 -1
- package/dist/esm/services/providers/gemini/GeminiChatServiceProvider.js +3 -16
- package/dist/esm/services/providers/gemini/GeminiChatServiceProvider.js.map +1 -1
- package/dist/esm/services/providers/index.d.ts +3 -1
- package/dist/esm/services/providers/index.d.ts.map +1 -1
- package/dist/esm/services/providers/index.js +4 -0
- package/dist/esm/services/providers/index.js.map +1 -1
- package/dist/esm/services/providers/mistral/MistralChatService.d.ts +8 -0
- package/dist/esm/services/providers/mistral/MistralChatService.d.ts.map +1 -0
- package/dist/esm/services/providers/mistral/MistralChatService.js +8 -0
- package/dist/esm/services/providers/mistral/MistralChatService.js.map +1 -0
- package/dist/esm/services/providers/mistral/MistralChatServiceProvider.d.ts +17 -0
- package/dist/esm/services/providers/mistral/MistralChatServiceProvider.d.ts.map +1 -0
- package/dist/esm/services/providers/mistral/MistralChatServiceProvider.js +68 -0
- package/dist/esm/services/providers/mistral/MistralChatServiceProvider.js.map +1 -0
- package/dist/esm/services/providers/openai/OpenAIChatService.d.ts +2 -0
- package/dist/esm/services/providers/openai/OpenAIChatService.d.ts.map +1 -1
- package/dist/esm/services/providers/openai/OpenAIChatService.js +43 -5
- package/dist/esm/services/providers/openai/OpenAIChatService.js.map +1 -1
- package/dist/esm/utils/openaiCompatibleSse.d.ts.map +1 -1
- package/dist/esm/utils/openaiCompatibleSse.js +32 -6
- package/dist/esm/utils/openaiCompatibleSse.js.map +1 -1
- package/dist/umd/aituber-onair-chat.js +1738 -1435
- package/dist/umd/aituber-onair-chat.min.js +10 -10
- package/package.json +1 -1
|
@@ -27,21 +27,30 @@ var AITuberOnAirChat = (() => {
|
|
|
27
27
|
ChatServiceHttpClient: () => ChatServiceHttpClient,
|
|
28
28
|
ClaudeChatService: () => ClaudeChatService,
|
|
29
29
|
ClaudeChatServiceProvider: () => ClaudeChatServiceProvider,
|
|
30
|
+
DEEPSEEK_API_BASE_URL: () => DEEPSEEK_API_BASE_URL,
|
|
31
|
+
DEEPSEEK_DEPRECATED_MODELS: () => DEEPSEEK_DEPRECATED_MODELS,
|
|
32
|
+
DEEPSEEK_SUPPORTED_MODELS: () => DEEPSEEK_SUPPORTED_MODELS,
|
|
30
33
|
DEFAULT_MAX_TOKENS: () => DEFAULT_MAX_TOKENS,
|
|
31
34
|
DEFAULT_SUMMARY_PROMPT_TEMPLATE: () => DEFAULT_SUMMARY_PROMPT_TEMPLATE,
|
|
32
35
|
DEFAULT_VISION_PROMPT: () => DEFAULT_VISION_PROMPT,
|
|
36
|
+
DeepSeekChatService: () => DeepSeekChatService,
|
|
37
|
+
DeepSeekChatServiceProvider: () => DeepSeekChatServiceProvider,
|
|
33
38
|
EMOTION_TAG_CLEANUP_REGEX: () => EMOTION_TAG_CLEANUP_REGEX,
|
|
34
39
|
EMOTION_TAG_REGEX: () => EMOTION_TAG_REGEX,
|
|
35
40
|
ENDPOINT_CLAUDE_API: () => ENDPOINT_CLAUDE_API,
|
|
41
|
+
ENDPOINT_DEEPSEEK_CHAT_COMPLETIONS_API: () => ENDPOINT_DEEPSEEK_CHAT_COMPLETIONS_API,
|
|
36
42
|
ENDPOINT_GEMINI_API: () => ENDPOINT_GEMINI_API,
|
|
37
43
|
ENDPOINT_KIMI_CHAT_COMPLETIONS_API: () => ENDPOINT_KIMI_CHAT_COMPLETIONS_API,
|
|
44
|
+
ENDPOINT_MISTRAL_CHAT_COMPLETIONS_API: () => ENDPOINT_MISTRAL_CHAT_COMPLETIONS_API,
|
|
38
45
|
ENDPOINT_OPENAI_CHAT_COMPLETIONS_API: () => ENDPOINT_OPENAI_CHAT_COMPLETIONS_API,
|
|
39
46
|
ENDPOINT_OPENAI_RESPONSES_API: () => ENDPOINT_OPENAI_RESPONSES_API,
|
|
40
47
|
ENDPOINT_OPENROUTER_API: () => ENDPOINT_OPENROUTER_API,
|
|
41
48
|
ENDPOINT_XAI_CHAT_COMPLETIONS_API: () => ENDPOINT_XAI_CHAT_COMPLETIONS_API,
|
|
42
49
|
ENDPOINT_ZAI_CHAT_COMPLETIONS_API: () => ENDPOINT_ZAI_CHAT_COMPLETIONS_API,
|
|
43
50
|
EmotionParser: () => EmotionParser,
|
|
51
|
+
GEMINI_DEPRECATED_MODELS: () => GEMINI_DEPRECATED_MODELS,
|
|
44
52
|
GEMINI_NANO_MAX_CONTEXT_MESSAGES: () => GEMINI_NANO_MAX_CONTEXT_MESSAGES,
|
|
53
|
+
GEMINI_RECOMMENDED_MODELS: () => GEMINI_RECOMMENDED_MODELS,
|
|
45
54
|
GEMINI_VISION_SUPPORTED_MODELS: () => GEMINI_VISION_SUPPORTED_MODELS,
|
|
46
55
|
GPT5_PRESETS: () => GPT5_PRESETS,
|
|
47
56
|
GPT_5_MODELS: () => GPT_5_MODELS,
|
|
@@ -54,6 +63,10 @@ var AITuberOnAirChat = (() => {
|
|
|
54
63
|
KimiChatService: () => KimiChatService,
|
|
55
64
|
KimiChatServiceProvider: () => KimiChatServiceProvider,
|
|
56
65
|
MAX_TOKENS_BY_LENGTH: () => MAX_TOKENS_BY_LENGTH,
|
|
66
|
+
MISTRAL_API_BASE_URL: () => MISTRAL_API_BASE_URL,
|
|
67
|
+
MISTRAL_REASONING_EFFORT_SUPPORTED_MODELS: () => MISTRAL_REASONING_EFFORT_SUPPORTED_MODELS,
|
|
68
|
+
MISTRAL_SUPPORTED_MODELS: () => MISTRAL_SUPPORTED_MODELS,
|
|
69
|
+
MISTRAL_VISION_SUPPORTED_MODELS: () => MISTRAL_VISION_SUPPORTED_MODELS,
|
|
57
70
|
MODEL_ANTHROPIC_CLAUDE_3_5_SONNET: () => MODEL_ANTHROPIC_CLAUDE_3_5_SONNET,
|
|
58
71
|
MODEL_ANTHROPIC_CLAUDE_3_7_SONNET: () => MODEL_ANTHROPIC_CLAUDE_3_7_SONNET,
|
|
59
72
|
MODEL_ANTHROPIC_CLAUDE_4_5_HAIKU: () => MODEL_ANTHROPIC_CLAUDE_4_5_HAIKU,
|
|
@@ -73,12 +86,17 @@ var AITuberOnAirChat = (() => {
|
|
|
73
86
|
MODEL_CLAUDE_4_7_OPUS: () => MODEL_CLAUDE_4_7_OPUS,
|
|
74
87
|
MODEL_CLAUDE_4_OPUS: () => MODEL_CLAUDE_4_OPUS,
|
|
75
88
|
MODEL_CLAUDE_4_SONNET: () => MODEL_CLAUDE_4_SONNET,
|
|
89
|
+
MODEL_DEEPSEEK_CHAT: () => MODEL_DEEPSEEK_CHAT,
|
|
90
|
+
MODEL_DEEPSEEK_REASONER: () => MODEL_DEEPSEEK_REASONER,
|
|
91
|
+
MODEL_DEEPSEEK_V4_FLASH: () => MODEL_DEEPSEEK_V4_FLASH,
|
|
92
|
+
MODEL_DEEPSEEK_V4_PRO: () => MODEL_DEEPSEEK_V4_PRO,
|
|
76
93
|
MODEL_GEMINI_2_0_FLASH: () => MODEL_GEMINI_2_0_FLASH,
|
|
77
94
|
MODEL_GEMINI_2_0_FLASH_LITE: () => MODEL_GEMINI_2_0_FLASH_LITE,
|
|
78
95
|
MODEL_GEMINI_2_5_FLASH: () => MODEL_GEMINI_2_5_FLASH,
|
|
79
96
|
MODEL_GEMINI_2_5_FLASH_LITE: () => MODEL_GEMINI_2_5_FLASH_LITE,
|
|
80
97
|
MODEL_GEMINI_2_5_FLASH_LITE_PREVIEW_06_17: () => MODEL_GEMINI_2_5_FLASH_LITE_PREVIEW_06_17,
|
|
81
98
|
MODEL_GEMINI_2_5_PRO: () => MODEL_GEMINI_2_5_PRO,
|
|
99
|
+
MODEL_GEMINI_3_1_FLASH_LITE: () => MODEL_GEMINI_3_1_FLASH_LITE,
|
|
82
100
|
MODEL_GEMINI_3_1_FLASH_LITE_PREVIEW: () => MODEL_GEMINI_3_1_FLASH_LITE_PREVIEW,
|
|
83
101
|
MODEL_GEMINI_3_1_PRO_PREVIEW: () => MODEL_GEMINI_3_1_PRO_PREVIEW,
|
|
84
102
|
MODEL_GEMINI_3_FLASH_PREVIEW: () => MODEL_GEMINI_3_FLASH_PREVIEW,
|
|
@@ -122,6 +140,12 @@ var AITuberOnAirChat = (() => {
|
|
|
122
140
|
MODEL_GROK_4_3: () => MODEL_GROK_4_3,
|
|
123
141
|
MODEL_KIMI_K2_5: () => MODEL_KIMI_K2_5,
|
|
124
142
|
MODEL_KIMI_K2_6: () => MODEL_KIMI_K2_6,
|
|
143
|
+
MODEL_MISTRAL_LARGE_2512: () => MODEL_MISTRAL_LARGE_2512,
|
|
144
|
+
MODEL_MISTRAL_LARGE_LATEST: () => MODEL_MISTRAL_LARGE_LATEST,
|
|
145
|
+
MODEL_MISTRAL_MEDIUM_2508: () => MODEL_MISTRAL_MEDIUM_2508,
|
|
146
|
+
MODEL_MISTRAL_MEDIUM_3_5: () => MODEL_MISTRAL_MEDIUM_3_5,
|
|
147
|
+
MODEL_MISTRAL_SMALL_2603: () => MODEL_MISTRAL_SMALL_2603,
|
|
148
|
+
MODEL_MISTRAL_SMALL_LATEST: () => MODEL_MISTRAL_SMALL_LATEST,
|
|
125
149
|
MODEL_MOONSHOTAI_KIMI_K2_5: () => MODEL_MOONSHOTAI_KIMI_K2_5,
|
|
126
150
|
MODEL_MOONSHOTAI_KIMI_LATEST: () => MODEL_MOONSHOTAI_KIMI_LATEST,
|
|
127
151
|
MODEL_O1: () => MODEL_O1,
|
|
@@ -142,6 +166,8 @@ var AITuberOnAirChat = (() => {
|
|
|
142
166
|
MODEL_ZAI_GLM_4_5_AIR: () => MODEL_ZAI_GLM_4_5_AIR,
|
|
143
167
|
MODEL_ZAI_GLM_4_5_AIR_FREE: () => MODEL_ZAI_GLM_4_5_AIR_FREE,
|
|
144
168
|
MODEL_ZAI_GLM_4_7_FLASH: () => MODEL_ZAI_GLM_4_7_FLASH,
|
|
169
|
+
MistralChatService: () => MistralChatService,
|
|
170
|
+
MistralChatServiceProvider: () => MistralChatServiceProvider,
|
|
145
171
|
OPENROUTER_CREDITS_THRESHOLD: () => OPENROUTER_CREDITS_THRESHOLD,
|
|
146
172
|
OPENROUTER_FREE_DAILY_LIMIT_HIGH_CREDITS: () => OPENROUTER_FREE_DAILY_LIMIT_HIGH_CREDITS,
|
|
147
173
|
OPENROUTER_FREE_DAILY_LIMIT_LOW_CREDITS: () => OPENROUTER_FREE_DAILY_LIMIT_LOW_CREDITS,
|
|
@@ -171,6 +197,9 @@ var AITuberOnAirChat = (() => {
|
|
|
171
197
|
installGASFetch: () => installGASFetch,
|
|
172
198
|
isGPT5Model: () => isGPT5Model,
|
|
173
199
|
isKimiVisionModel: () => isKimiVisionModel,
|
|
200
|
+
isMistralReasoningEffort: () => isMistralReasoningEffort,
|
|
201
|
+
isMistralReasoningEffortModel: () => isMistralReasoningEffortModel,
|
|
202
|
+
isMistralVisionModel: () => isMistralVisionModel,
|
|
174
203
|
isOpenRouterFreeModel: () => isOpenRouterFreeModel,
|
|
175
204
|
isOpenRouterVisionModel: () => isOpenRouterVisionModel,
|
|
176
205
|
isResponsesOnlyGPT5Model: () => isResponsesOnlyGPT5Model,
|
|
@@ -268,6 +297,7 @@ var AITuberOnAirChat = (() => {
|
|
|
268
297
|
var MODEL_GEMMA_4_31B_IT = "gemma-4-31b-it";
|
|
269
298
|
var MODEL_GEMMA_4_26B_A4B_IT = "gemma-4-26b-a4b-it";
|
|
270
299
|
var MODEL_GEMINI_3_1_PRO_PREVIEW = "gemini-3.1-pro-preview";
|
|
300
|
+
var MODEL_GEMINI_3_1_FLASH_LITE = "gemini-3.1-flash-lite";
|
|
271
301
|
var MODEL_GEMINI_3_1_FLASH_LITE_PREVIEW = "gemini-3.1-flash-lite-preview";
|
|
272
302
|
var MODEL_GEMINI_3_PRO_PREVIEW = "gemini-3-pro-preview";
|
|
273
303
|
var MODEL_GEMINI_3_FLASH_PREVIEW = "gemini-3-flash-preview";
|
|
@@ -277,20 +307,27 @@ var AITuberOnAirChat = (() => {
|
|
|
277
307
|
var MODEL_GEMINI_2_5_FLASH_LITE_PREVIEW_06_17 = "gemini-2.5-flash-lite-preview-06-17";
|
|
278
308
|
var MODEL_GEMINI_2_0_FLASH = "gemini-2.0-flash";
|
|
279
309
|
var MODEL_GEMINI_2_0_FLASH_LITE = "gemini-2.0-flash-lite";
|
|
280
|
-
var
|
|
281
|
-
|
|
282
|
-
MODEL_GEMMA_4_26B_A4B_IT,
|
|
310
|
+
var GEMINI_RECOMMENDED_MODELS = [
|
|
311
|
+
MODEL_GEMINI_3_1_FLASH_LITE,
|
|
283
312
|
MODEL_GEMINI_3_1_PRO_PREVIEW,
|
|
284
|
-
MODEL_GEMINI_3_1_FLASH_LITE_PREVIEW,
|
|
285
|
-
MODEL_GEMINI_3_PRO_PREVIEW,
|
|
286
313
|
MODEL_GEMINI_3_FLASH_PREVIEW,
|
|
287
314
|
MODEL_GEMINI_2_5_PRO,
|
|
288
315
|
MODEL_GEMINI_2_5_FLASH,
|
|
289
316
|
MODEL_GEMINI_2_5_FLASH_LITE,
|
|
317
|
+
MODEL_GEMMA_4_31B_IT,
|
|
318
|
+
MODEL_GEMMA_4_26B_A4B_IT
|
|
319
|
+
];
|
|
320
|
+
var GEMINI_DEPRECATED_MODELS = [
|
|
321
|
+
MODEL_GEMINI_3_1_FLASH_LITE_PREVIEW,
|
|
322
|
+
MODEL_GEMINI_3_PRO_PREVIEW,
|
|
290
323
|
MODEL_GEMINI_2_5_FLASH_LITE_PREVIEW_06_17,
|
|
291
324
|
MODEL_GEMINI_2_0_FLASH,
|
|
292
325
|
MODEL_GEMINI_2_0_FLASH_LITE
|
|
293
326
|
];
|
|
327
|
+
var GEMINI_VISION_SUPPORTED_MODELS = [
|
|
328
|
+
...GEMINI_RECOMMENDED_MODELS,
|
|
329
|
+
...GEMINI_DEPRECATED_MODELS
|
|
330
|
+
];
|
|
294
331
|
|
|
295
332
|
// src/constants/claude.ts
|
|
296
333
|
var ENDPOINT_CLAUDE_API = "https://api.anthropic.com/v1/messages";
|
|
@@ -443,6 +480,61 @@ var AITuberOnAirChat = (() => {
|
|
|
443
480
|
return KIMI_VISION_SUPPORTED_MODELS.includes(model);
|
|
444
481
|
}
|
|
445
482
|
|
|
483
|
+
// src/constants/deepseek.ts
|
|
484
|
+
var DEEPSEEK_API_BASE_URL = "https://api.deepseek.com";
|
|
485
|
+
var ENDPOINT_DEEPSEEK_CHAT_COMPLETIONS_API = `${DEEPSEEK_API_BASE_URL}/chat/completions`;
|
|
486
|
+
var MODEL_DEEPSEEK_V4_FLASH = "deepseek-v4-flash";
|
|
487
|
+
var MODEL_DEEPSEEK_V4_PRO = "deepseek-v4-pro";
|
|
488
|
+
var MODEL_DEEPSEEK_CHAT = "deepseek-chat";
|
|
489
|
+
var MODEL_DEEPSEEK_REASONER = "deepseek-reasoner";
|
|
490
|
+
var DEEPSEEK_SUPPORTED_MODELS = [
|
|
491
|
+
MODEL_DEEPSEEK_V4_FLASH,
|
|
492
|
+
MODEL_DEEPSEEK_V4_PRO
|
|
493
|
+
];
|
|
494
|
+
var DEEPSEEK_DEPRECATED_MODELS = [
|
|
495
|
+
MODEL_DEEPSEEK_CHAT,
|
|
496
|
+
MODEL_DEEPSEEK_REASONER
|
|
497
|
+
];
|
|
498
|
+
|
|
499
|
+
// src/constants/mistral.ts
|
|
500
|
+
var MISTRAL_API_BASE_URL = "https://api.mistral.ai/v1";
|
|
501
|
+
var ENDPOINT_MISTRAL_CHAT_COMPLETIONS_API = `${MISTRAL_API_BASE_URL}/chat/completions`;
|
|
502
|
+
var MODEL_MISTRAL_SMALL_LATEST = "mistral-small-latest";
|
|
503
|
+
var MODEL_MISTRAL_SMALL_2603 = "mistral-small-2603";
|
|
504
|
+
var MODEL_MISTRAL_MEDIUM_3_5 = "mistral-medium-3-5";
|
|
505
|
+
var MODEL_MISTRAL_MEDIUM_2508 = "mistral-medium-2508";
|
|
506
|
+
var MODEL_MISTRAL_LARGE_LATEST = "mistral-large-latest";
|
|
507
|
+
var MODEL_MISTRAL_LARGE_2512 = "mistral-large-2512";
|
|
508
|
+
var MISTRAL_SUPPORTED_MODELS = [
|
|
509
|
+
MODEL_MISTRAL_SMALL_LATEST,
|
|
510
|
+
MODEL_MISTRAL_MEDIUM_3_5,
|
|
511
|
+
MODEL_MISTRAL_LARGE_LATEST,
|
|
512
|
+
MODEL_MISTRAL_LARGE_2512,
|
|
513
|
+
MODEL_MISTRAL_SMALL_2603,
|
|
514
|
+
MODEL_MISTRAL_MEDIUM_2508
|
|
515
|
+
];
|
|
516
|
+
var MISTRAL_REASONING_EFFORT_SUPPORTED_MODELS = [
|
|
517
|
+
MODEL_MISTRAL_SMALL_LATEST,
|
|
518
|
+
MODEL_MISTRAL_MEDIUM_3_5
|
|
519
|
+
];
|
|
520
|
+
var MISTRAL_VISION_SUPPORTED_MODELS = [
|
|
521
|
+
MODEL_MISTRAL_SMALL_LATEST,
|
|
522
|
+
MODEL_MISTRAL_SMALL_2603,
|
|
523
|
+
MODEL_MISTRAL_MEDIUM_3_5,
|
|
524
|
+
MODEL_MISTRAL_MEDIUM_2508,
|
|
525
|
+
MODEL_MISTRAL_LARGE_LATEST,
|
|
526
|
+
MODEL_MISTRAL_LARGE_2512
|
|
527
|
+
];
|
|
528
|
+
function isMistralReasoningEffortModel(model) {
|
|
529
|
+
return MISTRAL_REASONING_EFFORT_SUPPORTED_MODELS.includes(model);
|
|
530
|
+
}
|
|
531
|
+
function isMistralReasoningEffort(effort) {
|
|
532
|
+
return effort === "none" || effort === "high";
|
|
533
|
+
}
|
|
534
|
+
function isMistralVisionModel(model) {
|
|
535
|
+
return MISTRAL_VISION_SUPPORTED_MODELS.includes(model);
|
|
536
|
+
}
|
|
537
|
+
|
|
446
538
|
// src/constants/chat.ts
|
|
447
539
|
var CHAT_RESPONSE_LENGTH = {
|
|
448
540
|
VERY_SHORT: "veryShort",
|
|
@@ -756,6 +848,23 @@ If it's in another language, summarize in that language.
|
|
|
756
848
|
throw error;
|
|
757
849
|
}
|
|
758
850
|
};
|
|
851
|
+
var extractTextContent = (content) => {
|
|
852
|
+
if (typeof content === "string") {
|
|
853
|
+
return content;
|
|
854
|
+
}
|
|
855
|
+
if (!Array.isArray(content)) {
|
|
856
|
+
return "";
|
|
857
|
+
}
|
|
858
|
+
return content.map((chunk) => {
|
|
859
|
+
if (typeof chunk === "string") {
|
|
860
|
+
return chunk;
|
|
861
|
+
}
|
|
862
|
+
if (chunk && typeof chunk === "object" && chunk.type === "text" && typeof chunk.text === "string") {
|
|
863
|
+
return chunk.text;
|
|
864
|
+
}
|
|
865
|
+
return "";
|
|
866
|
+
}).join("");
|
|
867
|
+
};
|
|
759
868
|
var forEachSsePayload = async (res, onPayload) => {
|
|
760
869
|
const reader = res.body?.getReader();
|
|
761
870
|
if (!reader) {
|
|
@@ -788,7 +897,7 @@ If it's in another language, summarize in that language.
|
|
|
788
897
|
await forEachSsePayload(res, (payload) => {
|
|
789
898
|
const json = parseJsonPayload(payload, options.onJsonError);
|
|
790
899
|
if (!json) return;
|
|
791
|
-
const content = json.choices?.[0]?.delta?.content
|
|
900
|
+
const content = extractTextContent(json.choices?.[0]?.delta?.content);
|
|
792
901
|
if (content) {
|
|
793
902
|
onPartial(content);
|
|
794
903
|
full += content;
|
|
@@ -813,9 +922,10 @@ If it's in another language, summarize in that language.
|
|
|
813
922
|
usage = json.usage;
|
|
814
923
|
}
|
|
815
924
|
const delta = choice?.delta;
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
925
|
+
const content = extractTextContent(delta?.content);
|
|
926
|
+
if (content) {
|
|
927
|
+
onPartial(content);
|
|
928
|
+
appendTextBlock(textBlocks, content);
|
|
819
929
|
}
|
|
820
930
|
if (delta?.tool_calls) {
|
|
821
931
|
delta.tool_calls.forEach((c) => {
|
|
@@ -856,8 +966,11 @@ If it's in another language, summarize in that language.
|
|
|
856
966
|
input: JSON.parse(c.function?.arguments || "{}")
|
|
857
967
|
})
|
|
858
968
|
);
|
|
859
|
-
} else
|
|
860
|
-
|
|
969
|
+
} else {
|
|
970
|
+
const content = extractTextContent(choice?.message?.content);
|
|
971
|
+
if (content) {
|
|
972
|
+
blocks.push({ type: "text", text: content });
|
|
973
|
+
}
|
|
861
974
|
}
|
|
862
975
|
return {
|
|
863
976
|
blocks,
|
|
@@ -1660,171 +1773,210 @@ If it's in another language, summarize in that language.
|
|
|
1660
1773
|
}
|
|
1661
1774
|
};
|
|
1662
1775
|
|
|
1663
|
-
// src/
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
);
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
parameters: {
|
|
1700
|
-
type: "object",
|
|
1701
|
-
properties: {
|
|
1702
|
-
query: {
|
|
1703
|
-
type: "string",
|
|
1704
|
-
description: "Search query"
|
|
1776
|
+
// src/services/providers/openai/responsesParser.ts
|
|
1777
|
+
async function parseOpenAIResponsesStream(res, onPartial) {
|
|
1778
|
+
const reader = res.body.getReader();
|
|
1779
|
+
const dec = new TextDecoder();
|
|
1780
|
+
const textBlocks = [];
|
|
1781
|
+
const toolCallsMap = /* @__PURE__ */ new Map();
|
|
1782
|
+
let responseStatus;
|
|
1783
|
+
let incompleteDetails;
|
|
1784
|
+
let usage;
|
|
1785
|
+
let buf = "";
|
|
1786
|
+
while (true) {
|
|
1787
|
+
const { done, value } = await reader.read();
|
|
1788
|
+
if (done) break;
|
|
1789
|
+
buf += dec.decode(value, { stream: true });
|
|
1790
|
+
let eventType = "";
|
|
1791
|
+
let eventData = "";
|
|
1792
|
+
const lines = buf.split("\n");
|
|
1793
|
+
buf = lines.pop() || "";
|
|
1794
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1795
|
+
const line = lines[i].trim();
|
|
1796
|
+
if (line.startsWith("event:")) {
|
|
1797
|
+
eventType = line.slice(6).trim();
|
|
1798
|
+
} else if (line.startsWith("data:")) {
|
|
1799
|
+
eventData = line.slice(5).trim();
|
|
1800
|
+
} else if (line === "" && eventType && eventData) {
|
|
1801
|
+
try {
|
|
1802
|
+
const json = JSON.parse(eventData);
|
|
1803
|
+
handleResponsesSSEEvent(
|
|
1804
|
+
eventType,
|
|
1805
|
+
json,
|
|
1806
|
+
onPartial,
|
|
1807
|
+
textBlocks,
|
|
1808
|
+
toolCallsMap,
|
|
1809
|
+
(metadata) => {
|
|
1810
|
+
if (metadata.responseStatus !== void 0) {
|
|
1811
|
+
responseStatus = metadata.responseStatus;
|
|
1705
1812
|
}
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
|
-
];
|
|
1711
|
-
} catch (error) {
|
|
1712
|
-
console.warn(
|
|
1713
|
-
`Failed to fetch MCP schemas from ${serverConfig.name}:`,
|
|
1714
|
-
error
|
|
1715
|
-
);
|
|
1716
|
-
return [
|
|
1717
|
-
{
|
|
1718
|
-
name: `mcp_${serverConfig.name}_search`,
|
|
1719
|
-
description: `Search using ${serverConfig.name} MCP server (schema fetch failed)`,
|
|
1720
|
-
parameters: {
|
|
1721
|
-
type: "object",
|
|
1722
|
-
properties: {
|
|
1723
|
-
query: {
|
|
1724
|
-
type: "string",
|
|
1725
|
-
description: "Search query"
|
|
1813
|
+
if (metadata.incompleteDetails !== void 0) {
|
|
1814
|
+
incompleteDetails = metadata.incompleteDetails;
|
|
1726
1815
|
}
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1816
|
+
if (metadata.usage !== void 0) {
|
|
1817
|
+
usage = metadata.usage;
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
);
|
|
1821
|
+
} catch {
|
|
1822
|
+
console.warn("Failed to parse SSE data:", eventData);
|
|
1730
1823
|
}
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
}
|
|
1734
|
-
/**
|
|
1735
|
-
* Fetch all tool schemas from multiple MCP servers
|
|
1736
|
-
* @param mcpServers Array of MCP server configurations
|
|
1737
|
-
* @returns Array of all tool definitions
|
|
1738
|
-
*/
|
|
1739
|
-
static async fetchAllToolSchemas(mcpServers) {
|
|
1740
|
-
const allSchemas = [];
|
|
1741
|
-
for (const server of mcpServers) {
|
|
1742
|
-
try {
|
|
1743
|
-
const schemas = await this.fetchToolSchemas(server);
|
|
1744
|
-
allSchemas.push(...schemas);
|
|
1745
|
-
} catch (error) {
|
|
1746
|
-
console.error(`Failed to fetch schemas from ${server.name}:`, error);
|
|
1824
|
+
eventType = "";
|
|
1825
|
+
eventData = "";
|
|
1747
1826
|
}
|
|
1748
1827
|
}
|
|
1749
|
-
return allSchemas;
|
|
1750
1828
|
}
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1829
|
+
const toolBlocks = Array.from(toolCallsMap.values()).map(
|
|
1830
|
+
(tool) => ({
|
|
1831
|
+
type: "tool_use",
|
|
1832
|
+
id: tool.id,
|
|
1833
|
+
name: tool.name,
|
|
1834
|
+
input: tool.input || {}
|
|
1835
|
+
})
|
|
1836
|
+
);
|
|
1837
|
+
return {
|
|
1838
|
+
blocks: [...textBlocks, ...toolBlocks],
|
|
1839
|
+
stop_reason: toolBlocks.length ? "tool_use" : "end",
|
|
1840
|
+
truncated: responseStatus === "incomplete",
|
|
1841
|
+
response_status: responseStatus,
|
|
1842
|
+
incomplete_details: incompleteDetails,
|
|
1843
|
+
usage
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
function handleResponsesSSEEvent(eventType, data, onPartial, textBlocks, toolCallsMap, onMetadata) {
|
|
1847
|
+
switch (eventType) {
|
|
1848
|
+
case "response.output_item.added":
|
|
1849
|
+
if (data.item?.type === "message" && Array.isArray(data.item.content)) {
|
|
1850
|
+
data.item.content.forEach((c) => {
|
|
1851
|
+
if (c.type === "output_text" && c.text) {
|
|
1852
|
+
onPartial(c.text);
|
|
1853
|
+
StreamTextAccumulator.append(textBlocks, c.text);
|
|
1854
|
+
}
|
|
1855
|
+
});
|
|
1856
|
+
} else if (data.item?.type === "function_call") {
|
|
1857
|
+
toolCallsMap.set(data.item.id, {
|
|
1858
|
+
id: data.item.id,
|
|
1859
|
+
name: data.item.name,
|
|
1860
|
+
input: data.item.arguments ? JSON.parse(data.item.arguments) : {}
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
break;
|
|
1864
|
+
case "response.content_part.added":
|
|
1865
|
+
if (data.part?.type === "output_text" && typeof data.part.text === "string") {
|
|
1866
|
+
onPartial(data.part.text);
|
|
1867
|
+
StreamTextAccumulator.append(textBlocks, data.part.text);
|
|
1868
|
+
}
|
|
1869
|
+
break;
|
|
1870
|
+
case "response.output_text.delta":
|
|
1871
|
+
case "response.content_part.delta": {
|
|
1872
|
+
const deltaText = typeof data.delta === "string" ? data.delta : data.delta?.text ?? "";
|
|
1873
|
+
if (deltaText) {
|
|
1874
|
+
onPartial(deltaText);
|
|
1875
|
+
StreamTextAccumulator.append(textBlocks, deltaText);
|
|
1876
|
+
}
|
|
1877
|
+
break;
|
|
1777
1878
|
}
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1879
|
+
case "response.output_text.done":
|
|
1880
|
+
case "response.content_part.done":
|
|
1881
|
+
case "response.reasoning.started":
|
|
1882
|
+
case "response.reasoning.delta":
|
|
1883
|
+
case "response.reasoning.done":
|
|
1884
|
+
break;
|
|
1885
|
+
case "response.completed":
|
|
1886
|
+
onMetadata(extractResponsesMetadata(data, "completed"));
|
|
1887
|
+
break;
|
|
1888
|
+
case "response.incomplete":
|
|
1889
|
+
onMetadata(extractResponsesMetadata(data, "incomplete"));
|
|
1890
|
+
break;
|
|
1891
|
+
default:
|
|
1892
|
+
break;
|
|
1781
1893
|
}
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1894
|
+
}
|
|
1895
|
+
function extractResponsesMetadata(data, fallbackStatus) {
|
|
1896
|
+
const response = data?.response ?? data;
|
|
1897
|
+
return {
|
|
1898
|
+
responseStatus: response?.status ?? fallbackStatus,
|
|
1899
|
+
incompleteDetails: response?.incomplete_details ?? null,
|
|
1900
|
+
usage: response?.usage
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
function parseOpenAIResponsesOneShot(data) {
|
|
1904
|
+
const blocks = [];
|
|
1905
|
+
if (data.output && Array.isArray(data.output)) {
|
|
1906
|
+
data.output.forEach((outputItem) => {
|
|
1907
|
+
if (outputItem.type === "message" && outputItem.content) {
|
|
1908
|
+
outputItem.content.forEach((content) => {
|
|
1909
|
+
if (content.type === "output_text" && content.text) {
|
|
1910
|
+
blocks.push({ type: "text", text: content.text });
|
|
1911
|
+
}
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
if (outputItem.type === "function_call") {
|
|
1915
|
+
blocks.push({
|
|
1916
|
+
type: "tool_use",
|
|
1917
|
+
id: outputItem.id,
|
|
1918
|
+
name: outputItem.name,
|
|
1919
|
+
input: outputItem.arguments ? JSON.parse(outputItem.arguments) : {}
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
});
|
|
1806
1923
|
}
|
|
1924
|
+
return {
|
|
1925
|
+
blocks,
|
|
1926
|
+
stop_reason: blocks.some((b) => b.type === "tool_use") ? "tool_use" : "end",
|
|
1927
|
+
truncated: data?.status === "incomplete",
|
|
1928
|
+
response_status: data?.status,
|
|
1929
|
+
incomplete_details: data?.incomplete_details ?? null,
|
|
1930
|
+
usage: data?.usage
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
// src/services/providers/openai/OpenAIChatService.ts
|
|
1935
|
+
var GPT5_RESPONSE_LENGTH_MIN_TOKENS = {
|
|
1936
|
+
[CHAT_RESPONSE_LENGTH.VERY_SHORT]: 800,
|
|
1937
|
+
[CHAT_RESPONSE_LENGTH.SHORT]: 1200,
|
|
1938
|
+
[CHAT_RESPONSE_LENGTH.MEDIUM]: 2e3,
|
|
1939
|
+
[CHAT_RESPONSE_LENGTH.LONG]: 3e3,
|
|
1940
|
+
[CHAT_RESPONSE_LENGTH.VERY_LONG]: 8e3,
|
|
1941
|
+
[CHAT_RESPONSE_LENGTH.DEEP]: 25e3
|
|
1942
|
+
};
|
|
1943
|
+
var GPT5_REASONING_MIN_TOKENS = {
|
|
1944
|
+
none: 1200,
|
|
1945
|
+
minimal: 1600,
|
|
1946
|
+
low: 2500,
|
|
1947
|
+
medium: 4e3,
|
|
1948
|
+
high: 8e3,
|
|
1949
|
+
xhigh: 12e3
|
|
1950
|
+
};
|
|
1951
|
+
var OPENAI_COMPATIBLE_CHAT_COMPLETIONS_PROVIDERS = /* @__PURE__ */ new Set([
|
|
1952
|
+
"openai-compatible",
|
|
1953
|
+
"deepseek",
|
|
1954
|
+
"mistral"
|
|
1955
|
+
]);
|
|
1956
|
+
var OpenAIChatService = class {
|
|
1807
1957
|
/**
|
|
1808
|
-
*
|
|
1958
|
+
* Constructor
|
|
1959
|
+
* @param apiKey OpenAI API key
|
|
1960
|
+
* @param model Name of the model to use
|
|
1961
|
+
* @param visionModel Name of the vision model
|
|
1809
1962
|
*/
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
])
|
|
1963
|
+
constructor(apiKey, model = MODEL_GPT_4O_MINI, visionModel = MODEL_GPT_4O_MINI, tools, endpoint = ENDPOINT_OPENAI_CHAT_COMPLETIONS_API, mcpServers = [], responseLength, verbosity, reasoning_effort, enableReasoningSummary = false, provider = "openai", validateVisionModel = true) {
|
|
1964
|
+
this.provider = provider;
|
|
1965
|
+
this.apiKey = apiKey;
|
|
1966
|
+
this.model = model;
|
|
1967
|
+
this.tools = tools || [];
|
|
1968
|
+
this.endpoint = endpoint;
|
|
1969
|
+
this.mcpServers = mcpServers;
|
|
1970
|
+
this.responseLength = responseLength;
|
|
1971
|
+
this.verbosity = verbosity;
|
|
1972
|
+
this.reasoning_effort = reasoning_effort;
|
|
1973
|
+
this.enableReasoningSummary = enableReasoningSummary;
|
|
1974
|
+
if (validateVisionModel && !VISION_SUPPORTED_MODELS.includes(visionModel)) {
|
|
1975
|
+
throw new Error(
|
|
1976
|
+
`Model ${visionModel} does not support vision capabilities.`
|
|
1825
1977
|
);
|
|
1826
1978
|
}
|
|
1827
|
-
|
|
1979
|
+
this.visionModel = visionModel;
|
|
1828
1980
|
}
|
|
1829
1981
|
/**
|
|
1830
1982
|
* Get the current model name
|
|
@@ -1841,1262 +1993,1383 @@ If it's in another language, summarize in that language.
|
|
|
1841
1993
|
return this.visionModel;
|
|
1842
1994
|
}
|
|
1843
1995
|
/**
|
|
1844
|
-
*
|
|
1845
|
-
* @
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
return this.mcpServers;
|
|
1849
|
-
}
|
|
1850
|
-
/**
|
|
1851
|
-
* Add MCP server configuration
|
|
1852
|
-
* @param serverConfig MCP server configuration
|
|
1853
|
-
*/
|
|
1854
|
-
addMCPServer(serverConfig) {
|
|
1855
|
-
this.mcpServers.push(serverConfig);
|
|
1856
|
-
this.mcpSchemasInitialized = false;
|
|
1857
|
-
}
|
|
1858
|
-
/**
|
|
1859
|
-
* Remove MCP server by name
|
|
1860
|
-
* @param serverName Name of the server to remove
|
|
1861
|
-
*/
|
|
1862
|
-
removeMCPServer(serverName) {
|
|
1863
|
-
this.mcpServers = this.mcpServers.filter(
|
|
1864
|
-
(server) => server.name !== serverName
|
|
1865
|
-
);
|
|
1866
|
-
this.mcpSchemasInitialized = false;
|
|
1867
|
-
}
|
|
1868
|
-
/**
|
|
1869
|
-
* Check if MCP servers are configured
|
|
1870
|
-
* @returns True if MCP servers are configured
|
|
1871
|
-
*/
|
|
1872
|
-
hasMCPServers() {
|
|
1873
|
-
return this.mcpServers.length > 0;
|
|
1874
|
-
}
|
|
1875
|
-
/**
|
|
1876
|
-
* Initialize MCP tool schemas by fetching from servers
|
|
1877
|
-
* @private
|
|
1996
|
+
* Process chat messages
|
|
1997
|
+
* @param messages Array of messages to send
|
|
1998
|
+
* @param onPartialResponse Callback to receive each part of streaming response
|
|
1999
|
+
* @param onCompleteResponse Callback to execute when response is complete
|
|
1878
2000
|
*/
|
|
1879
|
-
async
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
this.mcpToolSchemas = this.mcpServers.map((server) => ({
|
|
1898
|
-
name: `mcp_${server.name}_search`,
|
|
1899
|
-
description: `Search using ${server.name} MCP server (fallback)`,
|
|
1900
|
-
parameters: {
|
|
1901
|
-
type: "object",
|
|
1902
|
-
properties: {
|
|
1903
|
-
query: {
|
|
1904
|
-
type: "string",
|
|
1905
|
-
description: "Search query"
|
|
1906
|
-
}
|
|
1907
|
-
},
|
|
1908
|
-
required: ["query"]
|
|
2001
|
+
async processChat(messages, onPartialResponse, onCompleteResponse) {
|
|
2002
|
+
await processChatWithOptionalTools({
|
|
2003
|
+
hasTools: this.tools.length > 0,
|
|
2004
|
+
runWithoutTools: async () => {
|
|
2005
|
+
const res = await this.callOpenAI(messages, this.model, true);
|
|
2006
|
+
const isResponsesAPI = this.endpoint === ENDPOINT_OPENAI_RESPONSES_API;
|
|
2007
|
+
try {
|
|
2008
|
+
if (isResponsesAPI) {
|
|
2009
|
+
const result = await parseOpenAIResponsesStream(
|
|
2010
|
+
res,
|
|
2011
|
+
onPartialResponse
|
|
2012
|
+
);
|
|
2013
|
+
return StreamTextAccumulator.getFullText(result.blocks);
|
|
2014
|
+
}
|
|
2015
|
+
return this.handleStream(res, onPartialResponse);
|
|
2016
|
+
} catch (error) {
|
|
2017
|
+
console.error("[processChat] Error in streaming/completion:", error);
|
|
2018
|
+
throw error;
|
|
1909
2019
|
}
|
|
1910
|
-
}
|
|
1911
|
-
this.
|
|
1912
|
-
|
|
2020
|
+
},
|
|
2021
|
+
runWithTools: () => this.chatOnce(messages, true, onPartialResponse),
|
|
2022
|
+
onCompleteResponse,
|
|
2023
|
+
toolErrorMessage: "processChat received tool_calls. ChatProcessor must use chatOnce() loop when tools are enabled."
|
|
2024
|
+
});
|
|
1913
2025
|
}
|
|
1914
2026
|
/**
|
|
1915
|
-
* Process chat messages
|
|
1916
|
-
* @param messages Array of messages to send
|
|
2027
|
+
* Process chat messages with images
|
|
2028
|
+
* @param messages Array of messages to send (including images)
|
|
1917
2029
|
* @param onPartialResponse Callback to receive each part of streaming response
|
|
1918
2030
|
* @param onCompleteResponse Callback to execute when response is complete
|
|
2031
|
+
* @throws Error if the selected model doesn't support vision
|
|
1919
2032
|
*/
|
|
1920
|
-
async
|
|
2033
|
+
async processVisionChat(messages, onPartialResponse, onCompleteResponse) {
|
|
1921
2034
|
try {
|
|
1922
2035
|
await processChatWithOptionalTools({
|
|
1923
|
-
hasTools: this.tools.length > 0
|
|
2036
|
+
hasTools: this.tools.length > 0,
|
|
1924
2037
|
runWithoutTools: async () => {
|
|
1925
|
-
const res = await this.
|
|
1926
|
-
const
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
2038
|
+
const res = await this.callOpenAI(messages, this.visionModel, true);
|
|
2039
|
+
const isResponsesAPI = this.endpoint === ENDPOINT_OPENAI_RESPONSES_API;
|
|
2040
|
+
try {
|
|
2041
|
+
if (isResponsesAPI) {
|
|
2042
|
+
const result = await parseOpenAIResponsesStream(
|
|
2043
|
+
res,
|
|
2044
|
+
onPartialResponse
|
|
2045
|
+
);
|
|
2046
|
+
return StreamTextAccumulator.getFullText(result.blocks);
|
|
2047
|
+
}
|
|
2048
|
+
return this.handleStream(res, onPartialResponse);
|
|
2049
|
+
} catch (streamError) {
|
|
2050
|
+
console.error(
|
|
2051
|
+
"[processVisionChat] Error in streaming/completion:",
|
|
2052
|
+
streamError
|
|
2053
|
+
);
|
|
2054
|
+
throw streamError;
|
|
2055
|
+
}
|
|
1932
2056
|
},
|
|
1933
|
-
runWithTools: () => this.
|
|
2057
|
+
runWithTools: () => this.visionChatOnce(messages, true, onPartialResponse),
|
|
1934
2058
|
onCompleteResponse,
|
|
1935
|
-
toolErrorMessage: "
|
|
2059
|
+
toolErrorMessage: "processVisionChat received tool_calls. ChatProcessor must use visionChatOnce() loop when tools are enabled."
|
|
1936
2060
|
});
|
|
1937
|
-
} catch (
|
|
1938
|
-
console.error("Error in
|
|
1939
|
-
throw
|
|
2061
|
+
} catch (error) {
|
|
2062
|
+
console.error("Error in processVisionChat:", error);
|
|
2063
|
+
throw error;
|
|
1940
2064
|
}
|
|
1941
2065
|
}
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
2066
|
+
/**
|
|
2067
|
+
* Process chat messages with tools (text only)
|
|
2068
|
+
* @param messages Array of messages to send
|
|
2069
|
+
* @param stream Whether to use streaming
|
|
2070
|
+
* @param onPartialResponse Callback for partial responses
|
|
2071
|
+
* @param maxTokens Maximum tokens for response (optional)
|
|
2072
|
+
* @returns Tool chat completion
|
|
2073
|
+
*/
|
|
2074
|
+
async chatOnce(messages, stream = true, onPartialResponse = () => {
|
|
2075
|
+
}, maxTokens) {
|
|
2076
|
+
const res = await this.callOpenAI(messages, this.model, stream, maxTokens);
|
|
2077
|
+
return this.parseResponse(res, stream, onPartialResponse);
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Process vision chat messages with tools
|
|
2081
|
+
* @param messages Array of messages to send (including images)
|
|
2082
|
+
* @param stream Whether to use streaming
|
|
2083
|
+
* @param onPartialResponse Callback for partial responses
|
|
2084
|
+
* @param maxTokens Maximum tokens for response (optional)
|
|
2085
|
+
* @returns Tool chat completion
|
|
2086
|
+
*/
|
|
2087
|
+
async visionChatOnce(messages, stream = false, onPartialResponse = () => {
|
|
2088
|
+
}, maxTokens) {
|
|
2089
|
+
const res = await this.callOpenAI(
|
|
2090
|
+
messages,
|
|
2091
|
+
this.visionModel,
|
|
2092
|
+
stream,
|
|
2093
|
+
maxTokens
|
|
2094
|
+
);
|
|
2095
|
+
return this.parseResponse(res, stream, onPartialResponse);
|
|
2096
|
+
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Parse response based on endpoint type
|
|
2099
|
+
*/
|
|
2100
|
+
async parseResponse(res, stream, onPartialResponse) {
|
|
2101
|
+
const isResponsesAPI = this.endpoint === ENDPOINT_OPENAI_RESPONSES_API;
|
|
2102
|
+
if (isResponsesAPI) {
|
|
2103
|
+
return stream ? parseOpenAIResponsesStream(res, onPartialResponse) : parseOpenAIResponsesOneShot(await res.json());
|
|
1967
2104
|
}
|
|
2105
|
+
return stream ? this.parseStream(res, onPartialResponse) : this.parseOneShot(await res.json());
|
|
1968
2106
|
}
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
let currentParts = [];
|
|
1976
|
-
const pushCurrent = () => {
|
|
1977
|
-
if (currentRole && currentParts.length) {
|
|
1978
|
-
gemini.push({ role: currentRole, parts: [...currentParts] });
|
|
1979
|
-
currentParts = [];
|
|
1980
|
-
}
|
|
1981
|
-
};
|
|
1982
|
-
for (const msg of messages) {
|
|
1983
|
-
const role = this.mapRoleToGemini(msg.role);
|
|
1984
|
-
if (msg.tool_calls) {
|
|
1985
|
-
pushCurrent();
|
|
1986
|
-
for (const call of msg.tool_calls) {
|
|
1987
|
-
this.callIdMap.set(call.id, call.function.name);
|
|
1988
|
-
gemini.push({
|
|
1989
|
-
role: "model",
|
|
1990
|
-
parts: [
|
|
1991
|
-
{
|
|
1992
|
-
functionCall: {
|
|
1993
|
-
name: call.function.name,
|
|
1994
|
-
args: JSON.parse(call.function.arguments || "{}")
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
]
|
|
1998
|
-
});
|
|
1999
|
-
}
|
|
2000
|
-
continue;
|
|
2001
|
-
}
|
|
2002
|
-
if (msg.role === "tool") {
|
|
2003
|
-
pushCurrent();
|
|
2004
|
-
const funcName = msg.name ?? this.callIdMap.get(msg.tool_call_id) ?? "result";
|
|
2005
|
-
gemini.push({
|
|
2006
|
-
role: "user",
|
|
2007
|
-
parts: [
|
|
2008
|
-
{
|
|
2009
|
-
functionResponse: {
|
|
2010
|
-
name: funcName,
|
|
2011
|
-
response: this.normalizeToolResult(
|
|
2012
|
-
this.safeJsonParse(msg.content)
|
|
2013
|
-
)
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
]
|
|
2017
|
-
});
|
|
2018
|
-
continue;
|
|
2019
|
-
}
|
|
2020
|
-
if (role !== currentRole) pushCurrent();
|
|
2021
|
-
currentRole = role;
|
|
2022
|
-
currentParts.push({ text: msg.content });
|
|
2107
|
+
async callOpenAI(messages, model, stream = false, maxTokens) {
|
|
2108
|
+
const body = this.buildRequestBody(messages, model, stream, maxTokens);
|
|
2109
|
+
const headers = {};
|
|
2110
|
+
const shouldSendAuthorization = this.provider !== "openai-compatible" || this.apiKey.trim() !== "";
|
|
2111
|
+
if (shouldSendAuthorization) {
|
|
2112
|
+
headers.Authorization = `Bearer ${this.apiKey}`;
|
|
2023
2113
|
}
|
|
2024
|
-
|
|
2025
|
-
return
|
|
2114
|
+
const res = await ChatServiceHttpClient.post(this.endpoint, body, headers);
|
|
2115
|
+
return res;
|
|
2026
2116
|
}
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
const
|
|
2032
|
-
|
|
2033
|
-
(b) => b?.type === "image_url" || b?.inlineData
|
|
2034
|
-
)
|
|
2035
|
-
);
|
|
2036
|
-
const contents = hasVision ? await this.convertVisionMessagesToGeminiFormat(
|
|
2037
|
-
messages
|
|
2038
|
-
) : this.convertMessagesToGeminiFormat(messages);
|
|
2117
|
+
/**
|
|
2118
|
+
* Build request body based on the endpoint type
|
|
2119
|
+
*/
|
|
2120
|
+
buildRequestBody(messages, model, stream, maxTokens) {
|
|
2121
|
+
const isResponsesAPI = this.endpoint === ENDPOINT_OPENAI_RESPONSES_API;
|
|
2122
|
+
this.validateMCPCompatibility();
|
|
2039
2123
|
const body = {
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
maxOutputTokens: maxTokens !== void 0 ? maxTokens : getMaxTokensForResponseLength(this.responseLength)
|
|
2043
|
-
}
|
|
2124
|
+
model,
|
|
2125
|
+
stream
|
|
2044
2126
|
};
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
};
|
|
2050
|
-
}
|
|
2051
|
-
const allToolDeclarations = [];
|
|
2052
|
-
if (this.tools.length > 0) {
|
|
2053
|
-
allToolDeclarations.push(
|
|
2054
|
-
...this.tools.map((t) => ({
|
|
2055
|
-
name: t.name,
|
|
2056
|
-
description: t.description,
|
|
2057
|
-
parameters: t.parameters
|
|
2058
|
-
}))
|
|
2059
|
-
);
|
|
2060
|
-
}
|
|
2061
|
-
if (this.mcpServers.length > 0) {
|
|
2062
|
-
try {
|
|
2063
|
-
await this.initializeMCPSchemas();
|
|
2064
|
-
allToolDeclarations.push(
|
|
2065
|
-
...this.mcpToolSchemas.map((t) => ({
|
|
2066
|
-
name: t.name,
|
|
2067
|
-
description: t.description,
|
|
2068
|
-
parameters: t.parameters
|
|
2069
|
-
}))
|
|
2070
|
-
);
|
|
2071
|
-
} catch (error) {
|
|
2072
|
-
console.warn("MCP initialization failed, skipping MCP tools:", error);
|
|
2127
|
+
const tokenLimit = this.resolveTokenLimit(model, maxTokens);
|
|
2128
|
+
if (isResponsesAPI) {
|
|
2129
|
+
if (tokenLimit !== void 0) {
|
|
2130
|
+
body.max_output_tokens = tokenLimit;
|
|
2073
2131
|
}
|
|
2074
|
-
}
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2132
|
+
} else {
|
|
2133
|
+
if (tokenLimit !== void 0) {
|
|
2134
|
+
if (this.usesCompatibleChatCompletions()) {
|
|
2135
|
+
body.max_tokens = tokenLimit;
|
|
2136
|
+
} else {
|
|
2137
|
+
body.max_completion_tokens = tokenLimit;
|
|
2079
2138
|
}
|
|
2080
|
-
|
|
2081
|
-
body.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
|
|
2139
|
+
}
|
|
2082
2140
|
}
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2141
|
+
if (isResponsesAPI) {
|
|
2142
|
+
body.input = this.cleanMessagesForResponsesAPI(messages);
|
|
2143
|
+
} else {
|
|
2144
|
+
body.messages = this.provider === "mistral" ? this.cleanMessagesForMistralChatCompletions(messages) : messages;
|
|
2145
|
+
}
|
|
2146
|
+
if (isGPT5Model(model)) {
|
|
2147
|
+
if (isResponsesAPI) {
|
|
2148
|
+
if (this.reasoning_effort) {
|
|
2149
|
+
body.reasoning = {
|
|
2150
|
+
...body.reasoning,
|
|
2151
|
+
effort: this.reasoning_effort
|
|
2152
|
+
};
|
|
2153
|
+
if (this.enableReasoningSummary) {
|
|
2154
|
+
body.reasoning.summary = "auto";
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
if (this.verbosity) {
|
|
2158
|
+
body.text = {
|
|
2159
|
+
...body.text,
|
|
2160
|
+
format: { type: "text" },
|
|
2161
|
+
verbosity: this.verbosity
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
} else {
|
|
2165
|
+
if (this.reasoning_effort) {
|
|
2166
|
+
body.reasoning_effort = this.reasoning_effort;
|
|
2167
|
+
}
|
|
2168
|
+
if (this.verbosity) {
|
|
2169
|
+
body.verbosity = this.verbosity;
|
|
2103
2170
|
}
|
|
2104
|
-
throw e;
|
|
2105
2171
|
}
|
|
2106
|
-
}
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2172
|
+
}
|
|
2173
|
+
if (this.provider === "mistral" && isMistralReasoningEffortModel(model) && this.reasoning_effort && isMistralReasoningEffort(this.reasoning_effort)) {
|
|
2174
|
+
body.reasoning_effort = this.reasoning_effort;
|
|
2175
|
+
}
|
|
2176
|
+
const tools = this.buildToolsDefinition();
|
|
2177
|
+
if (tools.length > 0) {
|
|
2178
|
+
body.tools = tools;
|
|
2179
|
+
if (!isResponsesAPI) {
|
|
2180
|
+
body.tool_choice = "auto";
|
|
2114
2181
|
}
|
|
2115
|
-
throw error;
|
|
2116
2182
|
}
|
|
2183
|
+
return body;
|
|
2117
2184
|
}
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2185
|
+
resolveTokenLimit(model, maxTokens) {
|
|
2186
|
+
if (maxTokens !== void 0) {
|
|
2187
|
+
return maxTokens;
|
|
2188
|
+
}
|
|
2189
|
+
const baseTokenLimit = this.usesCompatibleChatCompletions() ? this.responseLength !== void 0 ? getMaxTokensForResponseLength(this.responseLength) : void 0 : getMaxTokensForResponseLength(this.responseLength);
|
|
2190
|
+
if (this.provider !== "openai" || !isGPT5Model(model) || this.responseLength === void 0) {
|
|
2191
|
+
return baseTokenLimit;
|
|
2192
|
+
}
|
|
2193
|
+
const effectiveReasoningEffort = this.reasoning_effort ?? getDefaultReasoningEffortForGPT5Model(model);
|
|
2194
|
+
return Math.max(
|
|
2195
|
+
baseTokenLimit ?? 0,
|
|
2196
|
+
GPT5_RESPONSE_LENGTH_MIN_TOKENS[this.responseLength],
|
|
2197
|
+
GPT5_REASONING_MIN_TOKENS[effectiveReasoningEffort]
|
|
2198
|
+
);
|
|
2199
|
+
}
|
|
2200
|
+
/**
|
|
2201
|
+
* Validate MCP servers compatibility with the current endpoint
|
|
2122
2202
|
*/
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
}
|
|
2140
|
-
]
|
|
2141
|
-
});
|
|
2142
|
-
}
|
|
2143
|
-
continue;
|
|
2144
|
-
}
|
|
2145
|
-
if (msg.role === "tool") {
|
|
2146
|
-
const funcName = msg.name ?? this.callIdMap.get(msg.tool_call_id) ?? "result";
|
|
2147
|
-
geminiMessages.push({
|
|
2148
|
-
role: "user",
|
|
2149
|
-
parts: [
|
|
2150
|
-
{
|
|
2151
|
-
functionResponse: {
|
|
2152
|
-
name: funcName,
|
|
2153
|
-
response: this.normalizeToolResult(
|
|
2154
|
-
this.safeJsonParse(msg.content)
|
|
2155
|
-
)
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
]
|
|
2159
|
-
});
|
|
2160
|
-
continue;
|
|
2161
|
-
}
|
|
2162
|
-
if (role !== currentRole && currentParts.length > 0) {
|
|
2163
|
-
geminiMessages.push({
|
|
2164
|
-
role: currentRole,
|
|
2165
|
-
parts: [...currentParts]
|
|
2166
|
-
});
|
|
2167
|
-
currentParts = [];
|
|
2168
|
-
}
|
|
2169
|
-
currentRole = role;
|
|
2203
|
+
validateMCPCompatibility() {
|
|
2204
|
+
if (this.mcpServers.length > 0 && this.endpoint === ENDPOINT_OPENAI_CHAT_COMPLETIONS_API) {
|
|
2205
|
+
throw new Error(
|
|
2206
|
+
`MCP servers are not supported with Chat Completions API. Current endpoint: ${this.endpoint}. Please use OpenAI Responses API endpoint: ${ENDPOINT_OPENAI_RESPONSES_API}. MCP tools are only available in the Responses API endpoint.`
|
|
2207
|
+
);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
/**
|
|
2211
|
+
* Clean messages for Responses API (remove timestamp and other extra properties)
|
|
2212
|
+
*/
|
|
2213
|
+
cleanMessagesForResponsesAPI(messages) {
|
|
2214
|
+
return messages.map((msg) => {
|
|
2215
|
+
const role = msg.role === "tool" ? "user" : msg.role;
|
|
2216
|
+
const cleanMsg = {
|
|
2217
|
+
role
|
|
2218
|
+
};
|
|
2170
2219
|
if (typeof msg.content === "string") {
|
|
2171
|
-
|
|
2220
|
+
cleanMsg.content = msg.content;
|
|
2172
2221
|
} else if (Array.isArray(msg.content)) {
|
|
2173
|
-
|
|
2222
|
+
cleanMsg.content = msg.content.map((block) => {
|
|
2174
2223
|
if (block.type === "text") {
|
|
2175
|
-
|
|
2224
|
+
return {
|
|
2225
|
+
type: "input_text",
|
|
2226
|
+
text: block.text
|
|
2227
|
+
};
|
|
2176
2228
|
} else if (block.type === "image_url") {
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
const base64Data = await this.blobToBase64(imageBlob);
|
|
2183
|
-
currentParts.push({
|
|
2184
|
-
inlineData: {
|
|
2185
|
-
mimeType: imageBlob.type || "image/jpeg",
|
|
2186
|
-
data: base64Data.split(",")[1]
|
|
2187
|
-
// Remove the "data:image/jpeg;base64," prefix
|
|
2188
|
-
}
|
|
2189
|
-
});
|
|
2190
|
-
} catch (error) {
|
|
2191
|
-
console.error("Error processing image:", error);
|
|
2192
|
-
throw new Error(`Failed to process image: ${error.message}`);
|
|
2193
|
-
}
|
|
2229
|
+
return {
|
|
2230
|
+
type: "input_image",
|
|
2231
|
+
image_url: block.image_url.url
|
|
2232
|
+
// Extract the URL string directly
|
|
2233
|
+
};
|
|
2194
2234
|
}
|
|
2195
|
-
|
|
2235
|
+
return block;
|
|
2236
|
+
});
|
|
2237
|
+
} else {
|
|
2238
|
+
cleanMsg.content = msg.content;
|
|
2196
2239
|
}
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
geminiMessages.push({
|
|
2200
|
-
role: currentRole,
|
|
2201
|
-
parts: [...currentParts]
|
|
2202
|
-
});
|
|
2203
|
-
}
|
|
2204
|
-
return geminiMessages;
|
|
2240
|
+
return cleanMsg;
|
|
2241
|
+
});
|
|
2205
2242
|
}
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2243
|
+
cleanMessagesForMistralChatCompletions(messages) {
|
|
2244
|
+
return messages.map((msg) => {
|
|
2245
|
+
if (!Array.isArray(msg.content)) {
|
|
2246
|
+
return msg;
|
|
2247
|
+
}
|
|
2248
|
+
return {
|
|
2249
|
+
...msg,
|
|
2250
|
+
content: msg.content.map((block) => {
|
|
2251
|
+
if (block.type === "image_url" && typeof block.image_url === "object" && typeof block.image_url?.url === "string") {
|
|
2252
|
+
return {
|
|
2253
|
+
type: "image_url",
|
|
2254
|
+
image_url: block.image_url.url
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
2257
|
+
return block;
|
|
2258
|
+
})
|
|
2259
|
+
};
|
|
2217
2260
|
});
|
|
2218
2261
|
}
|
|
2219
2262
|
/**
|
|
2220
|
-
*
|
|
2221
|
-
* @param role AITuber OnAir role
|
|
2222
|
-
* @returns Gemini role
|
|
2263
|
+
* Build tools definition based on the endpoint type
|
|
2223
2264
|
*/
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2265
|
+
buildToolsDefinition() {
|
|
2266
|
+
const isResponsesAPI = this.endpoint === ENDPOINT_OPENAI_RESPONSES_API;
|
|
2267
|
+
const toolDefs = [];
|
|
2268
|
+
if (this.tools.length > 0) {
|
|
2269
|
+
toolDefs.push(
|
|
2270
|
+
...buildOpenAICompatibleTools(
|
|
2271
|
+
this.tools,
|
|
2272
|
+
isResponsesAPI ? "responses" : "chat-completions"
|
|
2273
|
+
)
|
|
2274
|
+
);
|
|
2275
|
+
}
|
|
2276
|
+
if (this.mcpServers.length > 0 && isResponsesAPI) {
|
|
2277
|
+
toolDefs.push(...this.buildMCPToolsDefinition());
|
|
2235
2278
|
}
|
|
2279
|
+
return toolDefs;
|
|
2236
2280
|
}
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
return;
|
|
2281
|
+
/**
|
|
2282
|
+
* Build MCP tools definition for Responses API
|
|
2283
|
+
*/
|
|
2284
|
+
buildMCPToolsDefinition() {
|
|
2285
|
+
return this.mcpServers.map((server) => {
|
|
2286
|
+
const mcpDef = {
|
|
2287
|
+
type: "mcp",
|
|
2288
|
+
// Using 'mcp' as indicated by the error message
|
|
2289
|
+
server_label: server.name,
|
|
2290
|
+
// Use server_label as required by API
|
|
2291
|
+
server_url: server.url
|
|
2292
|
+
// Use server_url instead of url
|
|
2293
|
+
};
|
|
2294
|
+
if (server.require_approval) {
|
|
2295
|
+
mcpDef.require_approval = server.require_approval;
|
|
2253
2296
|
}
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
if (this.shouldExposeTextPart(part, model)) {
|
|
2257
|
-
onPartial(part.text);
|
|
2258
|
-
StreamTextAccumulator.addTextBlock(textBlocks, part.text);
|
|
2259
|
-
}
|
|
2260
|
-
if (part.functionCall) {
|
|
2261
|
-
toolBlocks.push({
|
|
2262
|
-
type: "tool_use",
|
|
2263
|
-
id: this.genUUID(),
|
|
2264
|
-
name: part.functionCall.name,
|
|
2265
|
-
input: part.functionCall.args ?? {}
|
|
2266
|
-
});
|
|
2267
|
-
}
|
|
2268
|
-
if (part.functionResponse) {
|
|
2269
|
-
toolBlocks.push({
|
|
2270
|
-
type: "tool_result",
|
|
2271
|
-
tool_use_id: part.functionResponse.name,
|
|
2272
|
-
content: JSON.stringify(part.functionResponse.response)
|
|
2273
|
-
});
|
|
2274
|
-
}
|
|
2275
|
-
}
|
|
2297
|
+
if (server.tool_configuration?.allowed_tools) {
|
|
2298
|
+
mcpDef.allowed_tools = server.tool_configuration.allowed_tools;
|
|
2276
2299
|
}
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
buf += dec.decode(value, { stream: true });
|
|
2282
|
-
let nl;
|
|
2283
|
-
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
2284
|
-
let line = buf.slice(0, nl);
|
|
2285
|
-
buf = buf.slice(nl + 1);
|
|
2286
|
-
if (line.endsWith("\r")) line = line.slice(0, -1);
|
|
2287
|
-
if (!line.trim()) {
|
|
2288
|
-
flush("");
|
|
2289
|
-
continue;
|
|
2290
|
-
}
|
|
2291
|
-
if (line.startsWith("data:")) line = line.slice(5).trim();
|
|
2292
|
-
if (!line) continue;
|
|
2293
|
-
flush(line);
|
|
2300
|
+
if (server.authorization_token) {
|
|
2301
|
+
mcpDef.headers = {
|
|
2302
|
+
Authorization: `Bearer ${server.authorization_token}`
|
|
2303
|
+
};
|
|
2294
2304
|
}
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
const blocks = [...textBlocks, ...toolBlocks];
|
|
2298
|
-
return {
|
|
2299
|
-
blocks,
|
|
2300
|
-
stop_reason: toolBlocks.some((b) => b.type === "tool_use") ? "tool_use" : "end"
|
|
2301
|
-
};
|
|
2305
|
+
return mcpDef;
|
|
2306
|
+
});
|
|
2302
2307
|
}
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
/* ────────────────────────────────────────────────────────── */
|
|
2306
|
-
parseOneShot(data, model) {
|
|
2307
|
-
const textBlocks = [];
|
|
2308
|
-
const toolBlocks = [];
|
|
2309
|
-
for (const cand of data.candidates ?? []) {
|
|
2310
|
-
for (const part of cand.content?.parts ?? []) {
|
|
2311
|
-
if (this.shouldExposeTextPart(part, model)) {
|
|
2312
|
-
textBlocks.push({ type: "text", text: part.text });
|
|
2313
|
-
}
|
|
2314
|
-
if (part.functionCall) {
|
|
2315
|
-
toolBlocks.push({
|
|
2316
|
-
type: "tool_use",
|
|
2317
|
-
id: this.genUUID(),
|
|
2318
|
-
name: part.functionCall.name,
|
|
2319
|
-
input: part.functionCall.args ?? {}
|
|
2320
|
-
});
|
|
2321
|
-
}
|
|
2322
|
-
if (part.functionResponse) {
|
|
2323
|
-
toolBlocks.push({
|
|
2324
|
-
type: "tool_result",
|
|
2325
|
-
tool_use_id: part.functionResponse.name,
|
|
2326
|
-
content: JSON.stringify(part.functionResponse.response)
|
|
2327
|
-
});
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
}
|
|
2331
|
-
const blocks = [...textBlocks, ...toolBlocks];
|
|
2332
|
-
return {
|
|
2333
|
-
blocks,
|
|
2334
|
-
stop_reason: toolBlocks.some((b) => b.type === "tool_use") ? "tool_use" : "end"
|
|
2335
|
-
};
|
|
2308
|
+
async handleStream(res, onPartial) {
|
|
2309
|
+
return parseOpenAICompatibleTextStream(res, onPartial);
|
|
2336
2310
|
}
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
}, maxTokens) {
|
|
2342
|
-
const res = await this.callGemini(messages, this.model, stream, maxTokens);
|
|
2343
|
-
return stream ? this.parseStream(res, onPartialResponse, this.model) : this.parseOneShot(await res.json(), this.model);
|
|
2311
|
+
async parseStream(res, onPartial) {
|
|
2312
|
+
return parseOpenAICompatibleToolStream(res, onPartial, {
|
|
2313
|
+
appendTextBlock: StreamTextAccumulator.addTextBlock
|
|
2314
|
+
});
|
|
2344
2315
|
}
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
/* ────────────────────────────────────────────────────────── */
|
|
2348
|
-
async visionChatOnce(messages, stream = false, onPartialResponse = () => {
|
|
2349
|
-
}, maxTokens) {
|
|
2350
|
-
const res = await this.callGemini(
|
|
2351
|
-
messages,
|
|
2352
|
-
this.visionModel,
|
|
2353
|
-
stream,
|
|
2354
|
-
maxTokens
|
|
2355
|
-
);
|
|
2356
|
-
return stream ? this.parseStream(res, onPartialResponse, this.visionModel) : this.parseOneShot(await res.json(), this.visionModel);
|
|
2316
|
+
parseOneShot(data) {
|
|
2317
|
+
return parseOpenAICompatibleOneShot(data);
|
|
2357
2318
|
}
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
/* ────────────────────────────────────────────────────────── */
|
|
2361
|
-
genUUID() {
|
|
2362
|
-
return typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
2363
|
-
const r = Math.random() * 16 | 0;
|
|
2364
|
-
const v = c === "x" ? r : r & 3 | 8;
|
|
2365
|
-
return v.toString(16);
|
|
2366
|
-
});
|
|
2319
|
+
usesCompatibleChatCompletions() {
|
|
2320
|
+
return OPENAI_COMPATIBLE_CHAT_COMPLETIONS_PROVIDERS.has(this.provider);
|
|
2367
2321
|
}
|
|
2368
2322
|
};
|
|
2369
2323
|
|
|
2370
|
-
// src/services/providers/
|
|
2371
|
-
var
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2324
|
+
// src/services/providers/deepseek/DeepSeekChatService.ts
|
|
2325
|
+
var DeepSeekChatService = class extends OpenAIChatService {
|
|
2326
|
+
constructor(apiKey, model = MODEL_DEEPSEEK_V4_FLASH, visionModel = model, tools, endpoint = ENDPOINT_DEEPSEEK_CHAT_COMPLETIONS_API, responseLength) {
|
|
2327
|
+
super(
|
|
2328
|
+
apiKey,
|
|
2329
|
+
model,
|
|
2330
|
+
visionModel,
|
|
2331
|
+
tools,
|
|
2332
|
+
endpoint,
|
|
2333
|
+
[],
|
|
2334
|
+
responseLength,
|
|
2335
|
+
void 0,
|
|
2336
|
+
void 0,
|
|
2337
|
+
false,
|
|
2338
|
+
"deepseek",
|
|
2339
|
+
false
|
|
2340
|
+
);
|
|
2341
|
+
}
|
|
2342
|
+
};
|
|
2343
|
+
|
|
2344
|
+
// src/services/providers/deepseek/DeepSeekChatServiceProvider.ts
|
|
2345
|
+
var DeepSeekChatServiceProvider = class {
|
|
2377
2346
|
createChatService(options) {
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
defaultVisionModel: this.getDefaultModel(),
|
|
2383
|
-
supportsVisionForModel: (model) => this.supportsVisionForModel(model),
|
|
2384
|
-
validate: "resolved"
|
|
2385
|
-
});
|
|
2386
|
-
return new GeminiChatService(
|
|
2347
|
+
this.validateRequiredOptions(options);
|
|
2348
|
+
const model = options.model || this.getDefaultModel();
|
|
2349
|
+
const tools = options.tools;
|
|
2350
|
+
return new DeepSeekChatService(
|
|
2387
2351
|
options.apiKey,
|
|
2388
|
-
|
|
2389
|
-
visionModel,
|
|
2390
|
-
|
|
2391
|
-
options
|
|
2352
|
+
model,
|
|
2353
|
+
options.visionModel ?? model,
|
|
2354
|
+
tools,
|
|
2355
|
+
this.resolveEndpoint(options),
|
|
2392
2356
|
options.responseLength
|
|
2393
2357
|
);
|
|
2394
2358
|
}
|
|
2395
|
-
/**
|
|
2396
|
-
* Get the provider name
|
|
2397
|
-
* @returns Provider name ('gemini')
|
|
2398
|
-
*/
|
|
2399
2359
|
getProviderName() {
|
|
2400
|
-
return "
|
|
2360
|
+
return "deepseek";
|
|
2401
2361
|
}
|
|
2402
|
-
/**
|
|
2403
|
-
* Get the list of supported models
|
|
2404
|
-
* @returns Array of supported model names
|
|
2405
|
-
*/
|
|
2406
2362
|
getSupportedModels() {
|
|
2407
|
-
return [
|
|
2408
|
-
MODEL_GEMMA_4_31B_IT,
|
|
2409
|
-
MODEL_GEMMA_4_26B_A4B_IT,
|
|
2410
|
-
MODEL_GEMINI_3_1_PRO_PREVIEW,
|
|
2411
|
-
MODEL_GEMINI_3_1_FLASH_LITE_PREVIEW,
|
|
2412
|
-
MODEL_GEMINI_3_PRO_PREVIEW,
|
|
2413
|
-
MODEL_GEMINI_3_FLASH_PREVIEW,
|
|
2414
|
-
MODEL_GEMINI_2_5_PRO,
|
|
2415
|
-
MODEL_GEMINI_2_5_FLASH,
|
|
2416
|
-
MODEL_GEMINI_2_5_FLASH_LITE,
|
|
2417
|
-
MODEL_GEMINI_2_5_FLASH_LITE_PREVIEW_06_17,
|
|
2418
|
-
MODEL_GEMINI_2_0_FLASH,
|
|
2419
|
-
MODEL_GEMINI_2_0_FLASH_LITE
|
|
2420
|
-
];
|
|
2363
|
+
return [...DEEPSEEK_SUPPORTED_MODELS];
|
|
2421
2364
|
}
|
|
2422
|
-
/**
|
|
2423
|
-
* Get the default model
|
|
2424
|
-
* @returns Default model name
|
|
2425
|
-
*/
|
|
2426
2365
|
getDefaultModel() {
|
|
2427
|
-
return
|
|
2366
|
+
return MODEL_DEEPSEEK_V4_FLASH;
|
|
2428
2367
|
}
|
|
2429
|
-
/**
|
|
2430
|
-
* Check if this provider supports vision (image processing)
|
|
2431
|
-
* @returns Vision support status (true)
|
|
2432
|
-
*/
|
|
2433
2368
|
supportsVision() {
|
|
2434
|
-
return
|
|
2369
|
+
return false;
|
|
2435
2370
|
}
|
|
2436
2371
|
getVisionSupportLevel() {
|
|
2437
|
-
return "
|
|
2438
|
-
}
|
|
2439
|
-
/**
|
|
2440
|
-
* Check if a specific model supports vision capabilities
|
|
2441
|
-
* @param model The model name to check
|
|
2442
|
-
* @returns True if the model supports vision, false otherwise
|
|
2443
|
-
*/
|
|
2444
|
-
supportsVisionForModel(model) {
|
|
2445
|
-
return GEMINI_VISION_SUPPORTED_MODELS.includes(model);
|
|
2446
|
-
}
|
|
2447
|
-
getVisionSupportLevelForModel(model) {
|
|
2448
|
-
return this.supportsVisionForModel(model) ? "supported" : "unsupported";
|
|
2449
|
-
}
|
|
2450
|
-
};
|
|
2451
|
-
|
|
2452
|
-
// src/services/providers/geminiNano/GeminiNanoChatService.ts
|
|
2453
|
-
function getLanguageModelAPI() {
|
|
2454
|
-
if (typeof globalThis !== "undefined" && "LanguageModel" in globalThis) {
|
|
2455
|
-
return globalThis.LanguageModel;
|
|
2456
|
-
}
|
|
2457
|
-
return void 0;
|
|
2458
|
-
}
|
|
2459
|
-
var GeminiNanoChatService = class {
|
|
2460
|
-
constructor(options = {}) {
|
|
2461
|
-
this.provider = "gemini-nano";
|
|
2462
|
-
this.expectedInputLanguages = options.expectedInputLanguages ?? ["ja"];
|
|
2463
|
-
this.expectedOutputLanguages = options.expectedOutputLanguages ?? ["ja"];
|
|
2464
|
-
this._responseLength = options.responseLength;
|
|
2465
|
-
}
|
|
2466
|
-
getModel() {
|
|
2467
|
-
return MODEL_GEMINI_NANO;
|
|
2468
|
-
}
|
|
2469
|
-
getVisionModel() {
|
|
2470
|
-
return MODEL_GEMINI_NANO;
|
|
2471
|
-
}
|
|
2472
|
-
/**
|
|
2473
|
-
* Process chat messages using Gemini Nano.
|
|
2474
|
-
* Non-streaming: calls onPartialResponse once with the full response,
|
|
2475
|
-
* then calls onCompleteResponse.
|
|
2476
|
-
*/
|
|
2477
|
-
async processChat(messages, onPartialResponse, onCompleteResponse) {
|
|
2478
|
-
const response = await this.generateResponse(messages);
|
|
2479
|
-
onPartialResponse(response);
|
|
2480
|
-
await onCompleteResponse(response);
|
|
2481
|
-
}
|
|
2482
|
-
async processVisionChat(_messages, _onPartialResponse, _onCompleteResponse) {
|
|
2483
|
-
throw new Error("Gemini Nano does not support vision capabilities.");
|
|
2372
|
+
return "unsupported";
|
|
2484
2373
|
}
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
const response = await this.generateResponse(messages);
|
|
2488
|
-
onPartialResponse(response);
|
|
2489
|
-
return {
|
|
2490
|
-
blocks: [{ type: "text", text: response }],
|
|
2491
|
-
stop_reason: "end"
|
|
2492
|
-
};
|
|
2374
|
+
supportsVisionForModel(_model) {
|
|
2375
|
+
return false;
|
|
2493
2376
|
}
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
throw new Error("Gemini Nano does not support vision capabilities.");
|
|
2377
|
+
getVisionSupportLevelForModel(_model) {
|
|
2378
|
+
return "unsupported";
|
|
2497
2379
|
}
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
async generateResponse(messages) {
|
|
2502
|
-
const api = getLanguageModelAPI();
|
|
2503
|
-
if (!api) {
|
|
2504
|
-
throw new Error(
|
|
2505
|
-
"Gemini Nano is not available in this environment. Chrome 138+ with Prompt API enabled is required."
|
|
2506
|
-
);
|
|
2507
|
-
}
|
|
2508
|
-
const availability = await api.availability();
|
|
2509
|
-
if (availability !== "available" && availability !== "downloadable") {
|
|
2510
|
-
throw new Error(
|
|
2511
|
-
`Gemini Nano Prompt API is not ready in this environment. LanguageModel.availability() returned "${availability}". Expected "available" or "downloadable".`
|
|
2512
|
-
);
|
|
2380
|
+
validateRequiredOptions(options) {
|
|
2381
|
+
if (!options.apiKey?.trim()) {
|
|
2382
|
+
throw new Error("deepseek provider requires apiKey.");
|
|
2513
2383
|
}
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
if (!lastUserMessage) {
|
|
2519
|
-
throw new Error("No user message found in the provided messages.");
|
|
2384
|
+
}
|
|
2385
|
+
resolveEndpoint(options) {
|
|
2386
|
+
if (options.endpoint) {
|
|
2387
|
+
return this.normalizeUrl(options.endpoint);
|
|
2520
2388
|
}
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
);
|
|
2526
|
-
try {
|
|
2527
|
-
return await session.prompt(lastUserMessage.content);
|
|
2528
|
-
} finally {
|
|
2529
|
-
try {
|
|
2530
|
-
session.destroy();
|
|
2531
|
-
} catch {
|
|
2389
|
+
if (options.baseUrl) {
|
|
2390
|
+
const baseUrl = this.normalizeUrl(options.baseUrl);
|
|
2391
|
+
if (baseUrl.endsWith("/chat/completions")) {
|
|
2392
|
+
return baseUrl;
|
|
2532
2393
|
}
|
|
2394
|
+
return `${baseUrl}/chat/completions`;
|
|
2533
2395
|
}
|
|
2396
|
+
return ENDPOINT_DEEPSEEK_CHAT_COMPLETIONS_API;
|
|
2534
2397
|
}
|
|
2398
|
+
normalizeUrl(value) {
|
|
2399
|
+
return value.trim().replace(/\/+$/, "");
|
|
2400
|
+
}
|
|
2401
|
+
};
|
|
2402
|
+
|
|
2403
|
+
// src/utils/mcpSchemaFetcher.ts
|
|
2404
|
+
var MCPSchemaFetcher = class {
|
|
2535
2405
|
/**
|
|
2536
|
-
*
|
|
2537
|
-
*
|
|
2406
|
+
* Fetch tool schemas from MCP server
|
|
2407
|
+
* @param serverConfig MCP server configuration
|
|
2408
|
+
* @returns Array of tool definitions
|
|
2538
2409
|
*/
|
|
2539
|
-
async
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
{
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2410
|
+
static async fetchToolSchemas(serverConfig) {
|
|
2411
|
+
try {
|
|
2412
|
+
const headers = {
|
|
2413
|
+
"Content-Type": "application/json"
|
|
2414
|
+
};
|
|
2415
|
+
if (serverConfig.authorization_token) {
|
|
2416
|
+
headers["Authorization"] = `Bearer ${serverConfig.authorization_token}`;
|
|
2417
|
+
}
|
|
2418
|
+
const response = await ChatServiceHttpClient.post(
|
|
2419
|
+
`${serverConfig.url}/tools`,
|
|
2420
|
+
{},
|
|
2421
|
+
headers
|
|
2422
|
+
);
|
|
2423
|
+
const toolsData = await response.json();
|
|
2424
|
+
if (Array.isArray(toolsData.tools)) {
|
|
2425
|
+
return toolsData.tools.map((tool) => ({
|
|
2426
|
+
name: `mcp_${serverConfig.name}_${tool.name}`,
|
|
2427
|
+
description: tool.description || `Tool from ${serverConfig.name} MCP server`,
|
|
2428
|
+
parameters: tool.inputSchema || {
|
|
2429
|
+
type: "object",
|
|
2430
|
+
properties: {},
|
|
2431
|
+
required: []
|
|
2432
|
+
}
|
|
2433
|
+
}));
|
|
2434
|
+
}
|
|
2435
|
+
return [
|
|
2436
|
+
{
|
|
2437
|
+
name: `mcp_${serverConfig.name}_search`,
|
|
2438
|
+
description: `Search using ${serverConfig.name} MCP server`,
|
|
2439
|
+
parameters: {
|
|
2440
|
+
type: "object",
|
|
2441
|
+
properties: {
|
|
2442
|
+
query: {
|
|
2443
|
+
type: "string",
|
|
2444
|
+
description: "Search query"
|
|
2445
|
+
}
|
|
2446
|
+
},
|
|
2447
|
+
required: ["query"]
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
];
|
|
2451
|
+
} catch (error) {
|
|
2452
|
+
console.warn(
|
|
2453
|
+
`Failed to fetch MCP schemas from ${serverConfig.name}:`,
|
|
2454
|
+
error
|
|
2455
|
+
);
|
|
2456
|
+
return [
|
|
2457
|
+
{
|
|
2458
|
+
name: `mcp_${serverConfig.name}_search`,
|
|
2459
|
+
description: `Search using ${serverConfig.name} MCP server (schema fetch failed)`,
|
|
2460
|
+
parameters: {
|
|
2461
|
+
type: "object",
|
|
2462
|
+
properties: {
|
|
2463
|
+
query: {
|
|
2464
|
+
type: "string",
|
|
2465
|
+
description: "Search query"
|
|
2466
|
+
}
|
|
2467
|
+
},
|
|
2468
|
+
required: ["query"]
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
];
|
|
2564
2472
|
}
|
|
2565
|
-
return promptParts.join("\n\n");
|
|
2566
2473
|
}
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2474
|
+
/**
|
|
2475
|
+
* Fetch all tool schemas from multiple MCP servers
|
|
2476
|
+
* @param mcpServers Array of MCP server configurations
|
|
2477
|
+
* @returns Array of all tool definitions
|
|
2478
|
+
*/
|
|
2479
|
+
static async fetchAllToolSchemas(mcpServers) {
|
|
2480
|
+
const allSchemas = [];
|
|
2481
|
+
for (const server of mcpServers) {
|
|
2482
|
+
try {
|
|
2483
|
+
const schemas = await this.fetchToolSchemas(server);
|
|
2484
|
+
allSchemas.push(...schemas);
|
|
2485
|
+
} catch (error) {
|
|
2486
|
+
console.error(`Failed to fetch schemas from ${server.name}:`, error);
|
|
2487
|
+
}
|
|
2574
2488
|
}
|
|
2575
|
-
return
|
|
2576
|
-
}
|
|
2577
|
-
};
|
|
2578
|
-
|
|
2579
|
-
// src/services/providers/geminiNano/GeminiNanoChatServiceProvider.ts
|
|
2580
|
-
var GeminiNanoChatServiceProvider = class {
|
|
2581
|
-
createChatService(options) {
|
|
2582
|
-
return new GeminiNanoChatService({
|
|
2583
|
-
expectedInputLanguages: options.expectedInputLanguages,
|
|
2584
|
-
expectedOutputLanguages: options.expectedOutputLanguages,
|
|
2585
|
-
responseLength: options.responseLength
|
|
2586
|
-
});
|
|
2587
|
-
}
|
|
2588
|
-
getProviderName() {
|
|
2589
|
-
return "gemini-nano";
|
|
2590
|
-
}
|
|
2591
|
-
getSupportedModels() {
|
|
2592
|
-
return [MODEL_GEMINI_NANO];
|
|
2593
|
-
}
|
|
2594
|
-
getDefaultModel() {
|
|
2595
|
-
return MODEL_GEMINI_NANO;
|
|
2596
|
-
}
|
|
2597
|
-
supportsVision() {
|
|
2598
|
-
return false;
|
|
2599
|
-
}
|
|
2600
|
-
getVisionSupportLevel() {
|
|
2601
|
-
return "unsupported";
|
|
2489
|
+
return allSchemas;
|
|
2602
2490
|
}
|
|
2603
2491
|
};
|
|
2604
2492
|
|
|
2605
|
-
// src/services/providers/
|
|
2606
|
-
var
|
|
2493
|
+
// src/services/providers/gemini/GeminiChatService.ts
|
|
2494
|
+
var GeminiChatService = class {
|
|
2607
2495
|
/**
|
|
2608
2496
|
* Constructor
|
|
2609
|
-
* @param apiKey
|
|
2497
|
+
* @param apiKey Google API key
|
|
2610
2498
|
* @param model Name of the model to use
|
|
2611
2499
|
* @param visionModel Name of the vision model
|
|
2500
|
+
* @param tools Array of tool definitions
|
|
2501
|
+
* @param mcpServers Array of MCP server configurations
|
|
2612
2502
|
*/
|
|
2613
|
-
constructor(apiKey, model =
|
|
2503
|
+
constructor(apiKey, model = MODEL_GEMINI_3_1_FLASH_LITE, visionModel = MODEL_GEMINI_3_1_FLASH_LITE, tools = [], mcpServers = [], responseLength) {
|
|
2614
2504
|
/** Provider name */
|
|
2615
|
-
this.provider = "
|
|
2505
|
+
this.provider = "gemini";
|
|
2506
|
+
this.mcpToolSchemas = [];
|
|
2507
|
+
this.mcpSchemasInitialized = false;
|
|
2508
|
+
/** id(OpenAI) → name(Gemini) mapping */
|
|
2509
|
+
this.callIdMap = /* @__PURE__ */ new Map();
|
|
2616
2510
|
this.apiKey = apiKey;
|
|
2617
2511
|
this.model = model;
|
|
2618
|
-
this.tools = tools || [];
|
|
2619
|
-
this.endpoint = endpoint;
|
|
2620
2512
|
this.responseLength = responseLength;
|
|
2621
|
-
|
|
2622
|
-
|
|
2513
|
+
if (!GEMINI_VISION_SUPPORTED_MODELS.includes(visionModel)) {
|
|
2514
|
+
throw new Error(
|
|
2515
|
+
`Model ${visionModel} does not support vision capabilities.`
|
|
2516
|
+
);
|
|
2517
|
+
}
|
|
2623
2518
|
this.visionModel = visionModel;
|
|
2519
|
+
this.tools = tools;
|
|
2520
|
+
this.mcpServers = mcpServers;
|
|
2521
|
+
}
|
|
2522
|
+
/* ────────────────────────────────── */
|
|
2523
|
+
/* Utilities */
|
|
2524
|
+
/* ────────────────────────────────── */
|
|
2525
|
+
safeJsonParse(str) {
|
|
2526
|
+
try {
|
|
2527
|
+
return JSON.parse(str);
|
|
2528
|
+
} catch {
|
|
2529
|
+
return str;
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
normalizeToolResult(val) {
|
|
2533
|
+
if (val === null) return { content: null };
|
|
2534
|
+
if (typeof val === "object") return val;
|
|
2535
|
+
return { content: val };
|
|
2536
|
+
}
|
|
2537
|
+
isGemma4Model(model) {
|
|
2538
|
+
return /^gemma-4-/.test(model);
|
|
2539
|
+
}
|
|
2540
|
+
shouldExposeTextPart(part, model) {
|
|
2541
|
+
if (!part.text) return false;
|
|
2542
|
+
if (this.isGemma4Model(model) && part.thought === true) {
|
|
2543
|
+
return false;
|
|
2544
|
+
}
|
|
2545
|
+
return true;
|
|
2546
|
+
}
|
|
2547
|
+
/**
|
|
2548
|
+
* camelCase → snake_case conversion (v1beta)
|
|
2549
|
+
*/
|
|
2550
|
+
adaptKeysForApi(obj) {
|
|
2551
|
+
const map = {
|
|
2552
|
+
toolConfig: "tool_config",
|
|
2553
|
+
functionCallingConfig: "function_calling_config",
|
|
2554
|
+
functionDeclarations: "function_declarations",
|
|
2555
|
+
functionCall: "function_call",
|
|
2556
|
+
functionResponse: "function_response"
|
|
2557
|
+
};
|
|
2558
|
+
if (Array.isArray(obj)) return obj.map((v) => this.adaptKeysForApi(v));
|
|
2559
|
+
if (obj && typeof obj === "object") {
|
|
2560
|
+
return Object.fromEntries(
|
|
2561
|
+
Object.entries(obj).map(([k, v]) => [
|
|
2562
|
+
map[k] ?? k,
|
|
2563
|
+
this.adaptKeysForApi(v)
|
|
2564
|
+
])
|
|
2565
|
+
);
|
|
2566
|
+
}
|
|
2567
|
+
return obj;
|
|
2624
2568
|
}
|
|
2625
2569
|
/**
|
|
2626
2570
|
* Get the current model name
|
|
2571
|
+
* @returns Model name
|
|
2627
2572
|
*/
|
|
2628
2573
|
getModel() {
|
|
2629
2574
|
return this.model;
|
|
2630
2575
|
}
|
|
2631
2576
|
/**
|
|
2632
2577
|
* Get the current vision model name
|
|
2578
|
+
* @returns Vision model name
|
|
2633
2579
|
*/
|
|
2634
2580
|
getVisionModel() {
|
|
2635
2581
|
return this.visionModel;
|
|
2636
2582
|
}
|
|
2637
2583
|
/**
|
|
2638
|
-
*
|
|
2584
|
+
* Get configured MCP servers
|
|
2585
|
+
* @returns Array of MCP server configurations
|
|
2639
2586
|
*/
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
hasTools: this.tools.length > 0,
|
|
2643
|
-
runWithoutTools: async () => {
|
|
2644
|
-
const res = await this.callKimi(messages, this.model, true);
|
|
2645
|
-
return this.handleStream(res, onPartialResponse);
|
|
2646
|
-
},
|
|
2647
|
-
runWithTools: () => this.chatOnce(messages, true, onPartialResponse),
|
|
2648
|
-
onCompleteResponse,
|
|
2649
|
-
toolErrorMessage: "processChat received tool_calls. ChatProcessor must use chatOnce() loop when tools are enabled."
|
|
2650
|
-
});
|
|
2587
|
+
getMCPServers() {
|
|
2588
|
+
return this.mcpServers;
|
|
2651
2589
|
}
|
|
2652
2590
|
/**
|
|
2653
|
-
*
|
|
2591
|
+
* Add MCP server configuration
|
|
2592
|
+
* @param serverConfig MCP server configuration
|
|
2654
2593
|
*/
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2594
|
+
addMCPServer(serverConfig) {
|
|
2595
|
+
this.mcpServers.push(serverConfig);
|
|
2596
|
+
this.mcpSchemasInitialized = false;
|
|
2597
|
+
}
|
|
2598
|
+
/**
|
|
2599
|
+
* Remove MCP server by name
|
|
2600
|
+
* @param serverName Name of the server to remove
|
|
2601
|
+
*/
|
|
2602
|
+
removeMCPServer(serverName) {
|
|
2603
|
+
this.mcpServers = this.mcpServers.filter(
|
|
2604
|
+
(server) => server.name !== serverName
|
|
2605
|
+
);
|
|
2606
|
+
this.mcpSchemasInitialized = false;
|
|
2607
|
+
}
|
|
2608
|
+
/**
|
|
2609
|
+
* Check if MCP servers are configured
|
|
2610
|
+
* @returns True if MCP servers are configured
|
|
2611
|
+
*/
|
|
2612
|
+
hasMCPServers() {
|
|
2613
|
+
return this.mcpServers.length > 0;
|
|
2614
|
+
}
|
|
2615
|
+
/**
|
|
2616
|
+
* Initialize MCP tool schemas by fetching from servers
|
|
2617
|
+
* @private
|
|
2618
|
+
*/
|
|
2619
|
+
async initializeMCPSchemas() {
|
|
2620
|
+
if (this.mcpSchemasInitialized || this.mcpServers.length === 0) {
|
|
2621
|
+
return;
|
|
2622
|
+
}
|
|
2623
|
+
try {
|
|
2624
|
+
const timeoutPromise = new Promise(
|
|
2625
|
+
(_, reject) => setTimeout(() => reject(new Error("MCP schema fetch timeout")), 5e3)
|
|
2626
|
+
);
|
|
2627
|
+
const schemasPromise = MCPSchemaFetcher.fetchAllToolSchemas(
|
|
2628
|
+
this.mcpServers
|
|
2629
|
+
);
|
|
2630
|
+
this.mcpToolSchemas = await Promise.race([
|
|
2631
|
+
schemasPromise,
|
|
2632
|
+
timeoutPromise
|
|
2633
|
+
]);
|
|
2634
|
+
this.mcpSchemasInitialized = true;
|
|
2635
|
+
} catch (error) {
|
|
2636
|
+
console.warn("Failed to initialize MCP schemas, using fallback:", error);
|
|
2637
|
+
this.mcpToolSchemas = this.mcpServers.map((server) => ({
|
|
2638
|
+
name: `mcp_${server.name}_search`,
|
|
2639
|
+
description: `Search using ${server.name} MCP server (fallback)`,
|
|
2640
|
+
parameters: {
|
|
2641
|
+
type: "object",
|
|
2642
|
+
properties: {
|
|
2643
|
+
query: {
|
|
2644
|
+
type: "string",
|
|
2645
|
+
description: "Search query"
|
|
2646
|
+
}
|
|
2647
|
+
},
|
|
2648
|
+
required: ["query"]
|
|
2649
|
+
}
|
|
2650
|
+
}));
|
|
2651
|
+
this.mcpSchemasInitialized = true;
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
/**
|
|
2655
|
+
* Process chat messages
|
|
2656
|
+
* @param messages Array of messages to send
|
|
2657
|
+
* @param onPartialResponse Callback to receive each part of streaming response
|
|
2658
|
+
* @param onCompleteResponse Callback to execute when response is complete
|
|
2659
|
+
*/
|
|
2660
|
+
async processChat(messages, onPartialResponse, onCompleteResponse) {
|
|
2661
|
+
try {
|
|
2662
|
+
await processChatWithOptionalTools({
|
|
2663
|
+
hasTools: this.tools.length > 0 || this.mcpServers.length > 0,
|
|
2664
|
+
runWithoutTools: async () => {
|
|
2665
|
+
const res = await this.callGemini(messages, this.model, true);
|
|
2666
|
+
const { blocks } = await this.parseStream(
|
|
2667
|
+
res,
|
|
2668
|
+
onPartialResponse,
|
|
2669
|
+
this.model
|
|
2670
|
+
);
|
|
2671
|
+
return StreamTextAccumulator.getFullText(blocks);
|
|
2672
|
+
},
|
|
2673
|
+
runWithTools: () => this.chatOnce(messages, true, onPartialResponse),
|
|
2674
|
+
onCompleteResponse,
|
|
2675
|
+
toolErrorMessage: "Received functionCall. Use chatOnce() loop when tools are enabled."
|
|
2676
|
+
});
|
|
2677
|
+
} catch (err) {
|
|
2678
|
+
console.error("Error in processChat:", err);
|
|
2679
|
+
throw err;
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
async processVisionChat(messages, onPartialResponse, onCompleteResponse) {
|
|
2683
|
+
try {
|
|
2684
|
+
await processChatWithOptionalTools({
|
|
2685
|
+
hasTools: this.tools.length > 0 || this.mcpServers.length > 0,
|
|
2686
|
+
runWithoutTools: async () => {
|
|
2687
|
+
const res = await this.callGemini(messages, this.visionModel, true);
|
|
2688
|
+
const { blocks } = await this.parseStream(
|
|
2689
|
+
res,
|
|
2690
|
+
onPartialResponse,
|
|
2691
|
+
this.visionModel
|
|
2692
|
+
);
|
|
2693
|
+
return StreamTextAccumulator.getFullText(blocks);
|
|
2694
|
+
},
|
|
2695
|
+
runWithTools: () => this.visionChatOnce(messages),
|
|
2696
|
+
onToolBlocks: (blocks) => {
|
|
2697
|
+
blocks.filter(
|
|
2698
|
+
(b) => b.type === "text"
|
|
2699
|
+
).forEach((b) => onPartialResponse(b.text));
|
|
2700
|
+
},
|
|
2701
|
+
onCompleteResponse,
|
|
2702
|
+
toolErrorMessage: "Received functionCall. Use visionChatOnce() loop when tools are enabled."
|
|
2703
|
+
});
|
|
2704
|
+
} catch (err) {
|
|
2705
|
+
console.error("Error in processVisionChat:", err);
|
|
2706
|
+
throw err;
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
/* ────────────────────────────────── */
|
|
2710
|
+
/* OpenAI → Gemini conversion */
|
|
2711
|
+
/* ────────────────────────────────── */
|
|
2712
|
+
convertMessagesToGeminiFormat(messages) {
|
|
2713
|
+
const gemini = [];
|
|
2714
|
+
let currentRole = null;
|
|
2715
|
+
let currentParts = [];
|
|
2716
|
+
const pushCurrent = () => {
|
|
2717
|
+
if (currentRole && currentParts.length) {
|
|
2718
|
+
gemini.push({ role: currentRole, parts: [...currentParts] });
|
|
2719
|
+
currentParts = [];
|
|
2720
|
+
}
|
|
2721
|
+
};
|
|
2722
|
+
for (const msg of messages) {
|
|
2723
|
+
const role = this.mapRoleToGemini(msg.role);
|
|
2724
|
+
if (msg.tool_calls) {
|
|
2725
|
+
pushCurrent();
|
|
2726
|
+
for (const call of msg.tool_calls) {
|
|
2727
|
+
this.callIdMap.set(call.id, call.function.name);
|
|
2728
|
+
gemini.push({
|
|
2729
|
+
role: "model",
|
|
2730
|
+
parts: [
|
|
2731
|
+
{
|
|
2732
|
+
functionCall: {
|
|
2733
|
+
name: call.function.name,
|
|
2734
|
+
args: JSON.parse(call.function.arguments || "{}")
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
]
|
|
2738
|
+
});
|
|
2739
|
+
}
|
|
2740
|
+
continue;
|
|
2741
|
+
}
|
|
2742
|
+
if (msg.role === "tool") {
|
|
2743
|
+
pushCurrent();
|
|
2744
|
+
const funcName = msg.name ?? this.callIdMap.get(msg.tool_call_id) ?? "result";
|
|
2745
|
+
gemini.push({
|
|
2746
|
+
role: "user",
|
|
2747
|
+
parts: [
|
|
2748
|
+
{
|
|
2749
|
+
functionResponse: {
|
|
2750
|
+
name: funcName,
|
|
2751
|
+
response: this.normalizeToolResult(
|
|
2752
|
+
this.safeJsonParse(msg.content)
|
|
2753
|
+
)
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
]
|
|
2757
|
+
});
|
|
2758
|
+
continue;
|
|
2759
|
+
}
|
|
2760
|
+
if (role !== currentRole) pushCurrent();
|
|
2761
|
+
currentRole = role;
|
|
2762
|
+
currentParts.push({ text: msg.content });
|
|
2763
|
+
}
|
|
2764
|
+
pushCurrent();
|
|
2765
|
+
return gemini;
|
|
2766
|
+
}
|
|
2767
|
+
/* ────────────────────────────────── */
|
|
2768
|
+
/* HTTP call */
|
|
2769
|
+
/* ────────────────────────────────── */
|
|
2770
|
+
async callGemini(messages, model, stream = false, maxTokens) {
|
|
2771
|
+
const hasVision = messages.some(
|
|
2772
|
+
(m) => Array.isArray(m.content) && m.content.some(
|
|
2773
|
+
(b) => b?.type === "image_url" || b?.inlineData
|
|
2774
|
+
)
|
|
2775
|
+
);
|
|
2776
|
+
const contents = hasVision ? await this.convertVisionMessagesToGeminiFormat(
|
|
2777
|
+
messages
|
|
2778
|
+
) : this.convertMessagesToGeminiFormat(messages);
|
|
2779
|
+
const body = {
|
|
2780
|
+
contents,
|
|
2781
|
+
generationConfig: {
|
|
2782
|
+
maxOutputTokens: maxTokens !== void 0 ? maxTokens : getMaxTokensForResponseLength(this.responseLength)
|
|
2783
|
+
}
|
|
2784
|
+
};
|
|
2785
|
+
if (this.isGemma4Model(model)) {
|
|
2786
|
+
body.generationConfig.thinkingConfig = {
|
|
2787
|
+
includeThoughts: false,
|
|
2788
|
+
thinkingLevel: "minimal"
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
const allToolDeclarations = [];
|
|
2792
|
+
if (this.tools.length > 0) {
|
|
2793
|
+
allToolDeclarations.push(
|
|
2794
|
+
...this.tools.map((t) => ({
|
|
2795
|
+
name: t.name,
|
|
2796
|
+
description: t.description,
|
|
2797
|
+
parameters: t.parameters
|
|
2798
|
+
}))
|
|
2799
|
+
);
|
|
2800
|
+
}
|
|
2801
|
+
if (this.mcpServers.length > 0) {
|
|
2802
|
+
try {
|
|
2803
|
+
await this.initializeMCPSchemas();
|
|
2804
|
+
allToolDeclarations.push(
|
|
2805
|
+
...this.mcpToolSchemas.map((t) => ({
|
|
2806
|
+
name: t.name,
|
|
2807
|
+
description: t.description,
|
|
2808
|
+
parameters: t.parameters
|
|
2809
|
+
}))
|
|
2810
|
+
);
|
|
2811
|
+
} catch (error) {
|
|
2812
|
+
console.warn("MCP initialization failed, skipping MCP tools:", error);
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
if (allToolDeclarations.length > 0) {
|
|
2816
|
+
body.tools = [
|
|
2817
|
+
{
|
|
2818
|
+
functionDeclarations: allToolDeclarations
|
|
2819
|
+
}
|
|
2820
|
+
];
|
|
2821
|
+
body.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
|
|
2822
|
+
}
|
|
2823
|
+
const fetchOnce = async (ver, payload) => {
|
|
2824
|
+
const fn = stream ? "streamGenerateContent" : "generateContent";
|
|
2825
|
+
const alt = stream ? "?alt=sse" : "";
|
|
2826
|
+
const url = `${ENDPOINT_GEMINI_API}/${ver}/models/${model}:${fn}${alt}${alt ? "&" : "?"}key=${this.apiKey}`;
|
|
2827
|
+
return ChatServiceHttpClient.post(url, payload);
|
|
2828
|
+
};
|
|
2829
|
+
const isLite = /flash[-_]lite/.test(model);
|
|
2830
|
+
const isGemma4 = this.isGemma4Model(model);
|
|
2831
|
+
const isGemini25 = /gemini-2\.5/.test(model);
|
|
2832
|
+
const isGemini3Preview = /^gemini-3(?:\.[0-9]+)?-.*preview/.test(model);
|
|
2833
|
+
const requiresV1beta = isLite || isGemma4 || isGemini25 || isGemini3Preview;
|
|
2834
|
+
const firstVer = requiresV1beta ? "v1beta" : "v1";
|
|
2835
|
+
const tryApi = async () => {
|
|
2836
|
+
try {
|
|
2837
|
+
const payload = firstVer === "v1" ? body : this.adaptKeysForApi(body);
|
|
2838
|
+
return await fetchOnce(firstVer, payload);
|
|
2839
|
+
} catch (e) {
|
|
2840
|
+
const looksLikeVersionMismatch = /Unknown name|Cannot find field|404/.test(e?.message || "") || e?.status === 404;
|
|
2841
|
+
if (!requiresV1beta && looksLikeVersionMismatch) {
|
|
2842
|
+
return await fetchOnce("v1beta", this.adaptKeysForApi(body));
|
|
2843
|
+
}
|
|
2844
|
+
throw e;
|
|
2845
|
+
}
|
|
2846
|
+
};
|
|
2847
|
+
try {
|
|
2848
|
+
const res = await tryApi();
|
|
2849
|
+
return res;
|
|
2850
|
+
} catch (error) {
|
|
2851
|
+
if (error.body) {
|
|
2852
|
+
console.error("Gemini API Error Details:", error.body);
|
|
2853
|
+
console.error("Request Body:", JSON.stringify(body, null, 2));
|
|
2854
|
+
}
|
|
2855
|
+
throw error;
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
/**
|
|
2859
|
+
* Convert AITuber OnAir vision messages to Gemini format
|
|
2860
|
+
* @param messages Array of vision messages
|
|
2861
|
+
* @returns Gemini formatted vision messages
|
|
2862
|
+
*/
|
|
2863
|
+
async convertVisionMessagesToGeminiFormat(messages) {
|
|
2864
|
+
const geminiMessages = [];
|
|
2865
|
+
let currentRole = null;
|
|
2866
|
+
let currentParts = [];
|
|
2867
|
+
for (const msg of messages) {
|
|
2868
|
+
const role = this.mapRoleToGemini(msg.role);
|
|
2869
|
+
if (msg.tool_calls) {
|
|
2870
|
+
for (const call of msg.tool_calls) {
|
|
2871
|
+
geminiMessages.push({
|
|
2872
|
+
role: "model",
|
|
2873
|
+
parts: [
|
|
2874
|
+
{
|
|
2875
|
+
functionCall: {
|
|
2876
|
+
name: call.function.name,
|
|
2877
|
+
args: JSON.parse(call.function.arguments || "{}")
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
]
|
|
2881
|
+
});
|
|
2882
|
+
}
|
|
2883
|
+
continue;
|
|
2884
|
+
}
|
|
2885
|
+
if (msg.role === "tool") {
|
|
2886
|
+
const funcName = msg.name ?? this.callIdMap.get(msg.tool_call_id) ?? "result";
|
|
2887
|
+
geminiMessages.push({
|
|
2888
|
+
role: "user",
|
|
2889
|
+
parts: [
|
|
2890
|
+
{
|
|
2891
|
+
functionResponse: {
|
|
2892
|
+
name: funcName,
|
|
2893
|
+
response: this.normalizeToolResult(
|
|
2894
|
+
this.safeJsonParse(msg.content)
|
|
2895
|
+
)
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
]
|
|
2899
|
+
});
|
|
2900
|
+
continue;
|
|
2901
|
+
}
|
|
2902
|
+
if (role !== currentRole && currentParts.length > 0) {
|
|
2903
|
+
geminiMessages.push({
|
|
2904
|
+
role: currentRole,
|
|
2905
|
+
parts: [...currentParts]
|
|
2906
|
+
});
|
|
2907
|
+
currentParts = [];
|
|
2908
|
+
}
|
|
2909
|
+
currentRole = role;
|
|
2910
|
+
if (typeof msg.content === "string") {
|
|
2911
|
+
currentParts.push({ text: msg.content });
|
|
2912
|
+
} else if (Array.isArray(msg.content)) {
|
|
2913
|
+
for (const block of msg.content) {
|
|
2914
|
+
if (block.type === "text") {
|
|
2915
|
+
currentParts.push({ text: block.text });
|
|
2916
|
+
} else if (block.type === "image_url") {
|
|
2917
|
+
try {
|
|
2918
|
+
const imageResponse = await ChatServiceHttpClient.get(
|
|
2919
|
+
block.image_url.url
|
|
2920
|
+
);
|
|
2921
|
+
const imageBlob = await imageResponse.blob();
|
|
2922
|
+
const base64Data = await this.blobToBase64(imageBlob);
|
|
2923
|
+
currentParts.push({
|
|
2924
|
+
inlineData: {
|
|
2925
|
+
mimeType: imageBlob.type || "image/jpeg",
|
|
2926
|
+
data: base64Data.split(",")[1]
|
|
2927
|
+
// Remove the "data:image/jpeg;base64," prefix
|
|
2928
|
+
}
|
|
2929
|
+
});
|
|
2930
|
+
} catch (error) {
|
|
2931
|
+
console.error("Error processing image:", error);
|
|
2932
|
+
throw new Error(`Failed to process image: ${error.message}`);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
if (currentRole && currentParts.length > 0) {
|
|
2939
|
+
geminiMessages.push({
|
|
2940
|
+
role: currentRole,
|
|
2941
|
+
parts: [...currentParts]
|
|
2942
|
+
});
|
|
2943
|
+
}
|
|
2944
|
+
return geminiMessages;
|
|
2945
|
+
}
|
|
2946
|
+
/**
|
|
2947
|
+
* Convert Blob to Base64 string
|
|
2948
|
+
* @param blob Image blob
|
|
2949
|
+
* @returns Promise with base64 encoded string
|
|
2950
|
+
*/
|
|
2951
|
+
blobToBase64(blob) {
|
|
2952
|
+
return new Promise((resolve, reject) => {
|
|
2953
|
+
const reader = new FileReader();
|
|
2954
|
+
reader.onloadend = () => resolve(reader.result);
|
|
2955
|
+
reader.onerror = reject;
|
|
2956
|
+
reader.readAsDataURL(blob);
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
2959
|
+
/**
|
|
2960
|
+
* Map AITuber OnAir roles to Gemini roles
|
|
2961
|
+
* @param role AITuber OnAir role
|
|
2962
|
+
* @returns Gemini role
|
|
2963
|
+
*/
|
|
2964
|
+
mapRoleToGemini(role) {
|
|
2965
|
+
switch (role) {
|
|
2966
|
+
case "system":
|
|
2967
|
+
return "model";
|
|
2968
|
+
// Gemini uses 'model' for system messages
|
|
2969
|
+
case "user":
|
|
2970
|
+
return "user";
|
|
2971
|
+
case "assistant":
|
|
2972
|
+
return "model";
|
|
2973
|
+
default:
|
|
2974
|
+
return "user";
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
/* ────────────────────────────────────────────────────────── */
|
|
2978
|
+
/* Convert NDJSON stream to common format */
|
|
2979
|
+
/* ────────────────────────────────────────────────────────── */
|
|
2980
|
+
async parseStream(res, onPartial, model) {
|
|
2981
|
+
const reader = res.body.getReader();
|
|
2982
|
+
const dec = new TextDecoder();
|
|
2983
|
+
const textBlocks = [];
|
|
2984
|
+
const toolBlocks = [];
|
|
2985
|
+
let buf = "";
|
|
2986
|
+
const flush = (payload) => {
|
|
2987
|
+
if (!payload || payload === "[DONE]") return;
|
|
2988
|
+
let obj;
|
|
2989
|
+
try {
|
|
2990
|
+
obj = JSON.parse(payload);
|
|
2991
|
+
} catch {
|
|
2992
|
+
return;
|
|
2993
|
+
}
|
|
2994
|
+
for (const cand of obj.candidates ?? []) {
|
|
2995
|
+
for (const part of cand.content?.parts ?? []) {
|
|
2996
|
+
if (this.shouldExposeTextPart(part, model)) {
|
|
2997
|
+
onPartial(part.text);
|
|
2998
|
+
StreamTextAccumulator.addTextBlock(textBlocks, part.text);
|
|
2999
|
+
}
|
|
3000
|
+
if (part.functionCall) {
|
|
3001
|
+
toolBlocks.push({
|
|
3002
|
+
type: "tool_use",
|
|
3003
|
+
id: this.genUUID(),
|
|
3004
|
+
name: part.functionCall.name,
|
|
3005
|
+
input: part.functionCall.args ?? {}
|
|
3006
|
+
});
|
|
3007
|
+
}
|
|
3008
|
+
if (part.functionResponse) {
|
|
3009
|
+
toolBlocks.push({
|
|
3010
|
+
type: "tool_result",
|
|
3011
|
+
tool_use_id: part.functionResponse.name,
|
|
3012
|
+
content: JSON.stringify(part.functionResponse.response)
|
|
3013
|
+
});
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
};
|
|
3018
|
+
while (true) {
|
|
3019
|
+
const { done, value } = await reader.read();
|
|
3020
|
+
if (done) break;
|
|
3021
|
+
buf += dec.decode(value, { stream: true });
|
|
3022
|
+
let nl;
|
|
3023
|
+
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
3024
|
+
let line = buf.slice(0, nl);
|
|
3025
|
+
buf = buf.slice(nl + 1);
|
|
3026
|
+
if (line.endsWith("\r")) line = line.slice(0, -1);
|
|
3027
|
+
if (!line.trim()) {
|
|
3028
|
+
flush("");
|
|
3029
|
+
continue;
|
|
3030
|
+
}
|
|
3031
|
+
if (line.startsWith("data:")) line = line.slice(5).trim();
|
|
3032
|
+
if (!line) continue;
|
|
3033
|
+
flush(line);
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
if (buf) flush(buf);
|
|
3037
|
+
const blocks = [...textBlocks, ...toolBlocks];
|
|
3038
|
+
return {
|
|
3039
|
+
blocks,
|
|
3040
|
+
stop_reason: toolBlocks.some((b) => b.type === "tool_use") ? "tool_use" : "end"
|
|
3041
|
+
};
|
|
3042
|
+
}
|
|
3043
|
+
/* ────────────────────────────────────────────────────────── */
|
|
3044
|
+
/* Convert JSON of non-stream (= generateContent) */
|
|
3045
|
+
/* ────────────────────────────────────────────────────────── */
|
|
3046
|
+
parseOneShot(data, model) {
|
|
3047
|
+
const textBlocks = [];
|
|
3048
|
+
const toolBlocks = [];
|
|
3049
|
+
for (const cand of data.candidates ?? []) {
|
|
3050
|
+
for (const part of cand.content?.parts ?? []) {
|
|
3051
|
+
if (this.shouldExposeTextPart(part, model)) {
|
|
3052
|
+
textBlocks.push({ type: "text", text: part.text });
|
|
3053
|
+
}
|
|
3054
|
+
if (part.functionCall) {
|
|
3055
|
+
toolBlocks.push({
|
|
3056
|
+
type: "tool_use",
|
|
3057
|
+
id: this.genUUID(),
|
|
3058
|
+
name: part.functionCall.name,
|
|
3059
|
+
input: part.functionCall.args ?? {}
|
|
3060
|
+
});
|
|
3061
|
+
}
|
|
3062
|
+
if (part.functionResponse) {
|
|
3063
|
+
toolBlocks.push({
|
|
3064
|
+
type: "tool_result",
|
|
3065
|
+
tool_use_id: part.functionResponse.name,
|
|
3066
|
+
content: JSON.stringify(part.functionResponse.response)
|
|
3067
|
+
});
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
2660
3070
|
}
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
},
|
|
2667
|
-
runWithTools: () => this.visionChatOnce(messages, true, onPartialResponse),
|
|
2668
|
-
onCompleteResponse,
|
|
2669
|
-
toolErrorMessage: "processVisionChat received tool_calls. ChatProcessor must use visionChatOnce() loop when tools are enabled."
|
|
2670
|
-
});
|
|
3071
|
+
const blocks = [...textBlocks, ...toolBlocks];
|
|
3072
|
+
return {
|
|
3073
|
+
blocks,
|
|
3074
|
+
stop_reason: toolBlocks.some((b) => b.type === "tool_use") ? "tool_use" : "end"
|
|
3075
|
+
};
|
|
2671
3076
|
}
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
3077
|
+
/* ────────────────────────────────────────────────────────── */
|
|
3078
|
+
/* chatOnce (text) */
|
|
3079
|
+
/* ────────────────────────────────────────────────────────── */
|
|
2675
3080
|
async chatOnce(messages, stream = true, onPartialResponse = () => {
|
|
2676
3081
|
}, maxTokens) {
|
|
2677
|
-
const res = await this.
|
|
2678
|
-
return this.
|
|
3082
|
+
const res = await this.callGemini(messages, this.model, stream, maxTokens);
|
|
3083
|
+
return stream ? this.parseStream(res, onPartialResponse, this.model) : this.parseOneShot(await res.json(), this.model);
|
|
2679
3084
|
}
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
3085
|
+
/* ────────────────────────────────────────────────────────── */
|
|
3086
|
+
/* visionChatOnce (images) */
|
|
3087
|
+
/* ────────────────────────────────────────────────────────── */
|
|
2683
3088
|
async visionChatOnce(messages, stream = false, onPartialResponse = () => {
|
|
2684
3089
|
}, maxTokens) {
|
|
2685
|
-
|
|
2686
|
-
throw new Error(
|
|
2687
|
-
`Model ${this.visionModel} does not support vision capabilities.`
|
|
2688
|
-
);
|
|
2689
|
-
}
|
|
2690
|
-
const res = await this.callKimi(
|
|
3090
|
+
const res = await this.callGemini(
|
|
2691
3091
|
messages,
|
|
2692
3092
|
this.visionModel,
|
|
2693
3093
|
stream,
|
|
2694
3094
|
maxTokens
|
|
2695
3095
|
);
|
|
2696
|
-
return this.
|
|
2697
|
-
}
|
|
2698
|
-
async parseResponse(res, stream, onPartialResponse) {
|
|
2699
|
-
return stream ? this.parseStream(res, onPartialResponse) : this.parseOneShot(await res.json());
|
|
2700
|
-
}
|
|
2701
|
-
async callKimi(messages, model, stream = false, maxTokens) {
|
|
2702
|
-
const body = this.buildRequestBody(messages, model, stream, maxTokens);
|
|
2703
|
-
const res = await ChatServiceHttpClient.post(this.endpoint, body, {
|
|
2704
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
2705
|
-
});
|
|
2706
|
-
return res;
|
|
2707
|
-
}
|
|
2708
|
-
/**
|
|
2709
|
-
* Build request body (OpenAI-compatible Chat Completions)
|
|
2710
|
-
*/
|
|
2711
|
-
buildRequestBody(messages, model, stream, maxTokens) {
|
|
2712
|
-
const body = {
|
|
2713
|
-
model,
|
|
2714
|
-
stream,
|
|
2715
|
-
messages
|
|
2716
|
-
};
|
|
2717
|
-
const tokenLimit = maxTokens !== void 0 ? maxTokens : getMaxTokensForResponseLength(this.responseLength);
|
|
2718
|
-
if (tokenLimit !== void 0) {
|
|
2719
|
-
body.max_tokens = tokenLimit;
|
|
2720
|
-
}
|
|
2721
|
-
if (this.responseFormat) {
|
|
2722
|
-
body.response_format = this.responseFormat;
|
|
2723
|
-
}
|
|
2724
|
-
const effectiveThinking = this.tools.length > 0 ? { type: "disabled" } : this.thinking;
|
|
2725
|
-
if (effectiveThinking) {
|
|
2726
|
-
if (this.isSelfHostedEndpoint()) {
|
|
2727
|
-
if (effectiveThinking.type === "disabled") {
|
|
2728
|
-
body.chat_template_kwargs = { thinking: false };
|
|
2729
|
-
}
|
|
2730
|
-
} else {
|
|
2731
|
-
body.thinking = effectiveThinking;
|
|
2732
|
-
}
|
|
2733
|
-
}
|
|
2734
|
-
const tools = this.buildToolsDefinition();
|
|
2735
|
-
if (tools.length > 0) {
|
|
2736
|
-
body.tools = tools;
|
|
2737
|
-
body.tool_choice = "auto";
|
|
2738
|
-
}
|
|
2739
|
-
return body;
|
|
2740
|
-
}
|
|
2741
|
-
isSelfHostedEndpoint() {
|
|
2742
|
-
return this.normalizeEndpoint(this.endpoint) !== this.normalizeEndpoint(ENDPOINT_KIMI_CHAT_COMPLETIONS_API);
|
|
2743
|
-
}
|
|
2744
|
-
normalizeEndpoint(value) {
|
|
2745
|
-
return value.replace(/\/+$/, "");
|
|
2746
|
-
}
|
|
2747
|
-
buildToolsDefinition() {
|
|
2748
|
-
return buildOpenAICompatibleTools(this.tools, "chat-completions");
|
|
2749
|
-
}
|
|
2750
|
-
async handleStream(res, onPartial) {
|
|
2751
|
-
return parseOpenAICompatibleTextStream(res, onPartial, {
|
|
2752
|
-
onJsonError: (payload) => console.debug("Failed to parse SSE data:", payload)
|
|
2753
|
-
});
|
|
3096
|
+
return stream ? this.parseStream(res, onPartialResponse, this.visionModel) : this.parseOneShot(await res.json(), this.visionModel);
|
|
2754
3097
|
}
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
return
|
|
2760
|
-
|
|
3098
|
+
/* ────────────────────────────────────────────────────────── */
|
|
3099
|
+
/* UUID helper */
|
|
3100
|
+
/* ────────────────────────────────────────────────────────── */
|
|
3101
|
+
genUUID() {
|
|
3102
|
+
return typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
3103
|
+
const r = Math.random() * 16 | 0;
|
|
3104
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
3105
|
+
return v.toString(16);
|
|
2761
3106
|
});
|
|
2762
3107
|
}
|
|
2763
|
-
/**
|
|
2764
|
-
* Parse non-streaming response
|
|
2765
|
-
*/
|
|
2766
|
-
parseOneShot(data) {
|
|
2767
|
-
return parseOpenAICompatibleOneShot(data);
|
|
2768
|
-
}
|
|
2769
3108
|
};
|
|
2770
3109
|
|
|
2771
|
-
// src/services/providers/
|
|
2772
|
-
var
|
|
3110
|
+
// src/services/providers/gemini/GeminiChatServiceProvider.ts
|
|
3111
|
+
var GeminiChatServiceProvider = class {
|
|
2773
3112
|
/**
|
|
2774
3113
|
* Create a chat service instance
|
|
3114
|
+
* @param options Service options
|
|
3115
|
+
* @returns GeminiChatService instance
|
|
2775
3116
|
*/
|
|
2776
3117
|
createChatService(options) {
|
|
2777
|
-
const endpoint = this.resolveEndpoint(options);
|
|
2778
|
-
const model = options.model || this.getDefaultModel();
|
|
2779
3118
|
const visionModel = resolveVisionModel({
|
|
2780
|
-
model,
|
|
3119
|
+
model: options.model,
|
|
2781
3120
|
visionModel: options.visionModel,
|
|
2782
3121
|
defaultModel: this.getDefaultModel(),
|
|
2783
|
-
defaultVisionModel: this.
|
|
2784
|
-
supportsVisionForModel: (
|
|
2785
|
-
validate: "
|
|
3122
|
+
defaultVisionModel: this.getDefaultModel(),
|
|
3123
|
+
supportsVisionForModel: (model) => this.supportsVisionForModel(model),
|
|
3124
|
+
validate: "resolved"
|
|
2786
3125
|
});
|
|
2787
|
-
|
|
2788
|
-
const defaultThinking = options.thinking ?? { type: "enabled" };
|
|
2789
|
-
const thinking = tools && tools.length > 0 ? { type: "disabled" } : defaultThinking;
|
|
2790
|
-
return new KimiChatService(
|
|
3126
|
+
return new GeminiChatService(
|
|
2791
3127
|
options.apiKey,
|
|
2792
|
-
model,
|
|
3128
|
+
options.model || this.getDefaultModel(),
|
|
2793
3129
|
visionModel,
|
|
2794
|
-
tools,
|
|
2795
|
-
|
|
2796
|
-
options.responseLength
|
|
2797
|
-
options.responseFormat,
|
|
2798
|
-
thinking
|
|
3130
|
+
options.tools || [],
|
|
3131
|
+
options.mcpServers || [],
|
|
3132
|
+
options.responseLength
|
|
2799
3133
|
);
|
|
2800
3134
|
}
|
|
2801
3135
|
/**
|
|
2802
3136
|
* Get the provider name
|
|
3137
|
+
* @returns Provider name ('gemini')
|
|
2803
3138
|
*/
|
|
2804
3139
|
getProviderName() {
|
|
2805
|
-
return "
|
|
3140
|
+
return "gemini";
|
|
2806
3141
|
}
|
|
2807
3142
|
/**
|
|
2808
3143
|
* Get the list of supported models
|
|
3144
|
+
* @returns Array of supported model names
|
|
2809
3145
|
*/
|
|
2810
3146
|
getSupportedModels() {
|
|
2811
|
-
return [
|
|
3147
|
+
return [...GEMINI_RECOMMENDED_MODELS];
|
|
2812
3148
|
}
|
|
2813
3149
|
/**
|
|
2814
3150
|
* Get the default model
|
|
3151
|
+
* @returns Default model name
|
|
2815
3152
|
*/
|
|
2816
3153
|
getDefaultModel() {
|
|
2817
|
-
return
|
|
3154
|
+
return MODEL_GEMINI_3_1_FLASH_LITE;
|
|
2818
3155
|
}
|
|
2819
3156
|
/**
|
|
2820
|
-
*
|
|
3157
|
+
* Check if this provider supports vision (image processing)
|
|
3158
|
+
* @returns Vision support status (true)
|
|
2821
3159
|
*/
|
|
2822
|
-
|
|
2823
|
-
return
|
|
3160
|
+
supportsVision() {
|
|
3161
|
+
return this.getVisionSupportLevel() !== "unsupported";
|
|
3162
|
+
}
|
|
3163
|
+
getVisionSupportLevel() {
|
|
3164
|
+
return "supported";
|
|
3165
|
+
}
|
|
3166
|
+
/**
|
|
3167
|
+
* Check if a specific model supports vision capabilities
|
|
3168
|
+
* @param model The model name to check
|
|
3169
|
+
* @returns True if the model supports vision, false otherwise
|
|
3170
|
+
*/
|
|
3171
|
+
supportsVisionForModel(model) {
|
|
3172
|
+
return GEMINI_VISION_SUPPORTED_MODELS.includes(model);
|
|
3173
|
+
}
|
|
3174
|
+
getVisionSupportLevelForModel(model) {
|
|
3175
|
+
return this.supportsVisionForModel(model) ? "supported" : "unsupported";
|
|
3176
|
+
}
|
|
3177
|
+
};
|
|
3178
|
+
|
|
3179
|
+
// src/services/providers/geminiNano/GeminiNanoChatService.ts
|
|
3180
|
+
function getLanguageModelAPI() {
|
|
3181
|
+
if (typeof globalThis !== "undefined" && "LanguageModel" in globalThis) {
|
|
3182
|
+
return globalThis.LanguageModel;
|
|
3183
|
+
}
|
|
3184
|
+
return void 0;
|
|
3185
|
+
}
|
|
3186
|
+
var GeminiNanoChatService = class {
|
|
3187
|
+
constructor(options = {}) {
|
|
3188
|
+
this.provider = "gemini-nano";
|
|
3189
|
+
this.expectedInputLanguages = options.expectedInputLanguages ?? ["ja"];
|
|
3190
|
+
this.expectedOutputLanguages = options.expectedOutputLanguages ?? ["ja"];
|
|
3191
|
+
this._responseLength = options.responseLength;
|
|
3192
|
+
}
|
|
3193
|
+
getModel() {
|
|
3194
|
+
return MODEL_GEMINI_NANO;
|
|
3195
|
+
}
|
|
3196
|
+
getVisionModel() {
|
|
3197
|
+
return MODEL_GEMINI_NANO;
|
|
3198
|
+
}
|
|
3199
|
+
/**
|
|
3200
|
+
* Process chat messages using Gemini Nano.
|
|
3201
|
+
* Non-streaming: calls onPartialResponse once with the full response,
|
|
3202
|
+
* then calls onCompleteResponse.
|
|
3203
|
+
*/
|
|
3204
|
+
async processChat(messages, onPartialResponse, onCompleteResponse) {
|
|
3205
|
+
const response = await this.generateResponse(messages);
|
|
3206
|
+
onPartialResponse(response);
|
|
3207
|
+
await onCompleteResponse(response);
|
|
3208
|
+
}
|
|
3209
|
+
async processVisionChat(_messages, _onPartialResponse, _onCompleteResponse) {
|
|
3210
|
+
throw new Error("Gemini Nano does not support vision capabilities.");
|
|
3211
|
+
}
|
|
3212
|
+
async chatOnce(messages, _stream = false, onPartialResponse = () => {
|
|
3213
|
+
}, _maxTokens) {
|
|
3214
|
+
const response = await this.generateResponse(messages);
|
|
3215
|
+
onPartialResponse(response);
|
|
3216
|
+
return {
|
|
3217
|
+
blocks: [{ type: "text", text: response }],
|
|
3218
|
+
stop_reason: "end"
|
|
3219
|
+
};
|
|
3220
|
+
}
|
|
3221
|
+
async visionChatOnce(_messages, _stream = false, _onPartialResponse = () => {
|
|
3222
|
+
}, _maxTokens) {
|
|
3223
|
+
throw new Error("Gemini Nano does not support vision capabilities.");
|
|
2824
3224
|
}
|
|
2825
3225
|
/**
|
|
2826
|
-
*
|
|
3226
|
+
* Core logic: extract system prompt, manage session, call prompt().
|
|
2827
3227
|
*/
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
3228
|
+
async generateResponse(messages) {
|
|
3229
|
+
const api = getLanguageModelAPI();
|
|
3230
|
+
if (!api) {
|
|
3231
|
+
throw new Error(
|
|
3232
|
+
"Gemini Nano is not available in this environment. Chrome 138+ with Prompt API enabled is required."
|
|
3233
|
+
);
|
|
3234
|
+
}
|
|
3235
|
+
const availability = await api.availability();
|
|
3236
|
+
if (availability !== "available" && availability !== "downloadable") {
|
|
3237
|
+
throw new Error(
|
|
3238
|
+
`Gemini Nano Prompt API is not ready in this environment. LanguageModel.availability() returned "${availability}". Expected "available" or "downloadable".`
|
|
3239
|
+
);
|
|
3240
|
+
}
|
|
3241
|
+
const systemMessages = messages.filter((m) => m.role === "system");
|
|
3242
|
+
const systemPrompt = systemMessages.map((m) => m.content).join("\n");
|
|
3243
|
+
const conversationMessages = messages.filter((m) => m.role !== "system").slice(-GEMINI_NANO_MAX_CONTEXT_MESSAGES);
|
|
3244
|
+
const lastUserMessage = [...conversationMessages].reverse().find((m) => m.role === "user");
|
|
3245
|
+
if (!lastUserMessage) {
|
|
3246
|
+
throw new Error("No user message found in the provided messages.");
|
|
3247
|
+
}
|
|
3248
|
+
const session = await this.createSession(
|
|
3249
|
+
api,
|
|
3250
|
+
systemPrompt,
|
|
3251
|
+
conversationMessages
|
|
3252
|
+
);
|
|
3253
|
+
try {
|
|
3254
|
+
return await session.prompt(lastUserMessage.content);
|
|
3255
|
+
} finally {
|
|
3256
|
+
try {
|
|
3257
|
+
session.destroy();
|
|
3258
|
+
} catch {
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
2833
3261
|
}
|
|
2834
3262
|
/**
|
|
2835
|
-
*
|
|
3263
|
+
* Create a new LanguageModel session with system prompt and context history.
|
|
3264
|
+
* Context history (excluding the last user message) is embedded in the system prompt.
|
|
2836
3265
|
*/
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
3266
|
+
async createSession(api, systemPrompt, contextHistory) {
|
|
3267
|
+
let prompt = this.buildSystemPrompt(systemPrompt);
|
|
3268
|
+
const historyMessages = contextHistory.slice(0, -1);
|
|
3269
|
+
if (historyMessages.length > 0) {
|
|
3270
|
+
const history = historyMessages.map((m) => `${m.role === "user" ? "User" : "Assistant"}: ${m.content}`).join("\n");
|
|
3271
|
+
prompt += "\n\nThe following is the prior conversation history. Use it as context for your response:\n" + history;
|
|
3272
|
+
}
|
|
3273
|
+
return api.create({
|
|
3274
|
+
systemPrompt: prompt,
|
|
3275
|
+
expectedInputs: [
|
|
3276
|
+
{ type: "text", languages: this.expectedInputLanguages }
|
|
3277
|
+
],
|
|
3278
|
+
expectedOutputs: [
|
|
3279
|
+
{ type: "text", languages: this.expectedOutputLanguages }
|
|
3280
|
+
]
|
|
3281
|
+
});
|
|
2842
3282
|
}
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
3283
|
+
buildSystemPrompt(systemPrompt) {
|
|
3284
|
+
const promptParts = [];
|
|
3285
|
+
if (systemPrompt) {
|
|
3286
|
+
promptParts.push(systemPrompt);
|
|
2846
3287
|
}
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
return baseUrl;
|
|
2851
|
-
}
|
|
2852
|
-
return `${baseUrl}/chat/completions`;
|
|
3288
|
+
const lengthInstruction = this.getResponseLengthInstruction();
|
|
3289
|
+
if (lengthInstruction) {
|
|
3290
|
+
promptParts.push(lengthInstruction);
|
|
2853
3291
|
}
|
|
2854
|
-
return
|
|
3292
|
+
return promptParts.join("\n\n");
|
|
2855
3293
|
}
|
|
2856
|
-
|
|
2857
|
-
|
|
3294
|
+
getResponseLengthInstruction() {
|
|
3295
|
+
if (!this._responseLength) {
|
|
3296
|
+
return void 0;
|
|
3297
|
+
}
|
|
3298
|
+
const maxTokens = MAX_TOKENS_BY_LENGTH[this._responseLength];
|
|
3299
|
+
if (!maxTokens) {
|
|
3300
|
+
return void 0;
|
|
3301
|
+
}
|
|
3302
|
+
return `Please keep your response concise, within approximately ${maxTokens} tokens.`;
|
|
2858
3303
|
}
|
|
2859
3304
|
};
|
|
2860
3305
|
|
|
2861
|
-
// src/services/providers/
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
let usage;
|
|
2870
|
-
let buf = "";
|
|
2871
|
-
while (true) {
|
|
2872
|
-
const { done, value } = await reader.read();
|
|
2873
|
-
if (done) break;
|
|
2874
|
-
buf += dec.decode(value, { stream: true });
|
|
2875
|
-
let eventType = "";
|
|
2876
|
-
let eventData = "";
|
|
2877
|
-
const lines = buf.split("\n");
|
|
2878
|
-
buf = lines.pop() || "";
|
|
2879
|
-
for (let i = 0; i < lines.length; i++) {
|
|
2880
|
-
const line = lines[i].trim();
|
|
2881
|
-
if (line.startsWith("event:")) {
|
|
2882
|
-
eventType = line.slice(6).trim();
|
|
2883
|
-
} else if (line.startsWith("data:")) {
|
|
2884
|
-
eventData = line.slice(5).trim();
|
|
2885
|
-
} else if (line === "" && eventType && eventData) {
|
|
2886
|
-
try {
|
|
2887
|
-
const json = JSON.parse(eventData);
|
|
2888
|
-
handleResponsesSSEEvent(
|
|
2889
|
-
eventType,
|
|
2890
|
-
json,
|
|
2891
|
-
onPartial,
|
|
2892
|
-
textBlocks,
|
|
2893
|
-
toolCallsMap,
|
|
2894
|
-
(metadata) => {
|
|
2895
|
-
if (metadata.responseStatus !== void 0) {
|
|
2896
|
-
responseStatus = metadata.responseStatus;
|
|
2897
|
-
}
|
|
2898
|
-
if (metadata.incompleteDetails !== void 0) {
|
|
2899
|
-
incompleteDetails = metadata.incompleteDetails;
|
|
2900
|
-
}
|
|
2901
|
-
if (metadata.usage !== void 0) {
|
|
2902
|
-
usage = metadata.usage;
|
|
2903
|
-
}
|
|
2904
|
-
}
|
|
2905
|
-
);
|
|
2906
|
-
} catch {
|
|
2907
|
-
console.warn("Failed to parse SSE data:", eventData);
|
|
2908
|
-
}
|
|
2909
|
-
eventType = "";
|
|
2910
|
-
eventData = "";
|
|
2911
|
-
}
|
|
2912
|
-
}
|
|
3306
|
+
// src/services/providers/geminiNano/GeminiNanoChatServiceProvider.ts
|
|
3307
|
+
var GeminiNanoChatServiceProvider = class {
|
|
3308
|
+
createChatService(options) {
|
|
3309
|
+
return new GeminiNanoChatService({
|
|
3310
|
+
expectedInputLanguages: options.expectedInputLanguages,
|
|
3311
|
+
expectedOutputLanguages: options.expectedOutputLanguages,
|
|
3312
|
+
responseLength: options.responseLength
|
|
3313
|
+
});
|
|
2913
3314
|
}
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
type: "tool_use",
|
|
2917
|
-
id: tool.id,
|
|
2918
|
-
name: tool.name,
|
|
2919
|
-
input: tool.input || {}
|
|
2920
|
-
})
|
|
2921
|
-
);
|
|
2922
|
-
return {
|
|
2923
|
-
blocks: [...textBlocks, ...toolBlocks],
|
|
2924
|
-
stop_reason: toolBlocks.length ? "tool_use" : "end",
|
|
2925
|
-
truncated: responseStatus === "incomplete",
|
|
2926
|
-
response_status: responseStatus,
|
|
2927
|
-
incomplete_details: incompleteDetails,
|
|
2928
|
-
usage
|
|
2929
|
-
};
|
|
2930
|
-
}
|
|
2931
|
-
function handleResponsesSSEEvent(eventType, data, onPartial, textBlocks, toolCallsMap, onMetadata) {
|
|
2932
|
-
switch (eventType) {
|
|
2933
|
-
case "response.output_item.added":
|
|
2934
|
-
if (data.item?.type === "message" && Array.isArray(data.item.content)) {
|
|
2935
|
-
data.item.content.forEach((c) => {
|
|
2936
|
-
if (c.type === "output_text" && c.text) {
|
|
2937
|
-
onPartial(c.text);
|
|
2938
|
-
StreamTextAccumulator.append(textBlocks, c.text);
|
|
2939
|
-
}
|
|
2940
|
-
});
|
|
2941
|
-
} else if (data.item?.type === "function_call") {
|
|
2942
|
-
toolCallsMap.set(data.item.id, {
|
|
2943
|
-
id: data.item.id,
|
|
2944
|
-
name: data.item.name,
|
|
2945
|
-
input: data.item.arguments ? JSON.parse(data.item.arguments) : {}
|
|
2946
|
-
});
|
|
2947
|
-
}
|
|
2948
|
-
break;
|
|
2949
|
-
case "response.content_part.added":
|
|
2950
|
-
if (data.part?.type === "output_text" && typeof data.part.text === "string") {
|
|
2951
|
-
onPartial(data.part.text);
|
|
2952
|
-
StreamTextAccumulator.append(textBlocks, data.part.text);
|
|
2953
|
-
}
|
|
2954
|
-
break;
|
|
2955
|
-
case "response.output_text.delta":
|
|
2956
|
-
case "response.content_part.delta": {
|
|
2957
|
-
const deltaText = typeof data.delta === "string" ? data.delta : data.delta?.text ?? "";
|
|
2958
|
-
if (deltaText) {
|
|
2959
|
-
onPartial(deltaText);
|
|
2960
|
-
StreamTextAccumulator.append(textBlocks, deltaText);
|
|
2961
|
-
}
|
|
2962
|
-
break;
|
|
2963
|
-
}
|
|
2964
|
-
case "response.output_text.done":
|
|
2965
|
-
case "response.content_part.done":
|
|
2966
|
-
case "response.reasoning.started":
|
|
2967
|
-
case "response.reasoning.delta":
|
|
2968
|
-
case "response.reasoning.done":
|
|
2969
|
-
break;
|
|
2970
|
-
case "response.completed":
|
|
2971
|
-
onMetadata(extractResponsesMetadata(data, "completed"));
|
|
2972
|
-
break;
|
|
2973
|
-
case "response.incomplete":
|
|
2974
|
-
onMetadata(extractResponsesMetadata(data, "incomplete"));
|
|
2975
|
-
break;
|
|
2976
|
-
default:
|
|
2977
|
-
break;
|
|
3315
|
+
getProviderName() {
|
|
3316
|
+
return "gemini-nano";
|
|
2978
3317
|
}
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
if (data.output && Array.isArray(data.output)) {
|
|
2991
|
-
data.output.forEach((outputItem) => {
|
|
2992
|
-
if (outputItem.type === "message" && outputItem.content) {
|
|
2993
|
-
outputItem.content.forEach((content) => {
|
|
2994
|
-
if (content.type === "output_text" && content.text) {
|
|
2995
|
-
blocks.push({ type: "text", text: content.text });
|
|
2996
|
-
}
|
|
2997
|
-
});
|
|
2998
|
-
}
|
|
2999
|
-
if (outputItem.type === "function_call") {
|
|
3000
|
-
blocks.push({
|
|
3001
|
-
type: "tool_use",
|
|
3002
|
-
id: outputItem.id,
|
|
3003
|
-
name: outputItem.name,
|
|
3004
|
-
input: outputItem.arguments ? JSON.parse(outputItem.arguments) : {}
|
|
3005
|
-
});
|
|
3006
|
-
}
|
|
3007
|
-
});
|
|
3318
|
+
getSupportedModels() {
|
|
3319
|
+
return [MODEL_GEMINI_NANO];
|
|
3320
|
+
}
|
|
3321
|
+
getDefaultModel() {
|
|
3322
|
+
return MODEL_GEMINI_NANO;
|
|
3323
|
+
}
|
|
3324
|
+
supportsVision() {
|
|
3325
|
+
return false;
|
|
3326
|
+
}
|
|
3327
|
+
getVisionSupportLevel() {
|
|
3328
|
+
return "unsupported";
|
|
3008
3329
|
}
|
|
3009
|
-
return {
|
|
3010
|
-
blocks,
|
|
3011
|
-
stop_reason: blocks.some((b) => b.type === "tool_use") ? "tool_use" : "end",
|
|
3012
|
-
truncated: data?.status === "incomplete",
|
|
3013
|
-
response_status: data?.status,
|
|
3014
|
-
incomplete_details: data?.incomplete_details ?? null,
|
|
3015
|
-
usage: data?.usage
|
|
3016
|
-
};
|
|
3017
|
-
}
|
|
3018
|
-
|
|
3019
|
-
// src/services/providers/openai/OpenAIChatService.ts
|
|
3020
|
-
var GPT5_RESPONSE_LENGTH_MIN_TOKENS = {
|
|
3021
|
-
[CHAT_RESPONSE_LENGTH.VERY_SHORT]: 800,
|
|
3022
|
-
[CHAT_RESPONSE_LENGTH.SHORT]: 1200,
|
|
3023
|
-
[CHAT_RESPONSE_LENGTH.MEDIUM]: 2e3,
|
|
3024
|
-
[CHAT_RESPONSE_LENGTH.LONG]: 3e3,
|
|
3025
|
-
[CHAT_RESPONSE_LENGTH.VERY_LONG]: 8e3,
|
|
3026
|
-
[CHAT_RESPONSE_LENGTH.DEEP]: 25e3
|
|
3027
|
-
};
|
|
3028
|
-
var GPT5_REASONING_MIN_TOKENS = {
|
|
3029
|
-
none: 1200,
|
|
3030
|
-
minimal: 1600,
|
|
3031
|
-
low: 2500,
|
|
3032
|
-
medium: 4e3,
|
|
3033
|
-
high: 8e3,
|
|
3034
|
-
xhigh: 12e3
|
|
3035
3330
|
};
|
|
3036
|
-
|
|
3331
|
+
|
|
3332
|
+
// src/services/providers/kimi/KimiChatService.ts
|
|
3333
|
+
var KimiChatService = class {
|
|
3037
3334
|
/**
|
|
3038
3335
|
* Constructor
|
|
3039
|
-
* @param apiKey
|
|
3336
|
+
* @param apiKey Kimi API key
|
|
3040
3337
|
* @param model Name of the model to use
|
|
3041
3338
|
* @param visionModel Name of the vision model
|
|
3042
3339
|
*/
|
|
3043
|
-
constructor(apiKey, model =
|
|
3044
|
-
|
|
3340
|
+
constructor(apiKey, model = MODEL_KIMI_K2_6, visionModel = MODEL_KIMI_K2_6, tools, endpoint = ENDPOINT_KIMI_CHAT_COMPLETIONS_API, responseLength, responseFormat, thinking) {
|
|
3341
|
+
/** Provider name */
|
|
3342
|
+
this.provider = "kimi";
|
|
3045
3343
|
this.apiKey = apiKey;
|
|
3046
3344
|
this.model = model;
|
|
3047
3345
|
this.tools = tools || [];
|
|
3048
3346
|
this.endpoint = endpoint;
|
|
3049
|
-
this.mcpServers = mcpServers;
|
|
3050
3347
|
this.responseLength = responseLength;
|
|
3051
|
-
this.
|
|
3052
|
-
this.
|
|
3053
|
-
this.enableReasoningSummary = enableReasoningSummary;
|
|
3054
|
-
if (validateVisionModel && !VISION_SUPPORTED_MODELS.includes(visionModel)) {
|
|
3055
|
-
throw new Error(
|
|
3056
|
-
`Model ${visionModel} does not support vision capabilities.`
|
|
3057
|
-
);
|
|
3058
|
-
}
|
|
3348
|
+
this.responseFormat = responseFormat;
|
|
3349
|
+
this.thinking = thinking ?? { type: "enabled" };
|
|
3059
3350
|
this.visionModel = visionModel;
|
|
3060
3351
|
}
|
|
3061
3352
|
/**
|
|
3062
3353
|
* Get the current model name
|
|
3063
|
-
* @returns Model name
|
|
3064
3354
|
*/
|
|
3065
3355
|
getModel() {
|
|
3066
3356
|
return this.model;
|
|
3067
3357
|
}
|
|
3068
3358
|
/**
|
|
3069
3359
|
* Get the current vision model name
|
|
3070
|
-
* @returns Vision model name
|
|
3071
3360
|
*/
|
|
3072
3361
|
getVisionModel() {
|
|
3073
3362
|
return this.visionModel;
|
|
3074
3363
|
}
|
|
3075
3364
|
/**
|
|
3076
3365
|
* Process chat messages
|
|
3077
|
-
* @param messages Array of messages to send
|
|
3078
|
-
* @param onPartialResponse Callback to receive each part of streaming response
|
|
3079
|
-
* @param onCompleteResponse Callback to execute when response is complete
|
|
3080
3366
|
*/
|
|
3081
3367
|
async processChat(messages, onPartialResponse, onCompleteResponse) {
|
|
3082
3368
|
await processChatWithOptionalTools({
|
|
3083
3369
|
hasTools: this.tools.length > 0,
|
|
3084
3370
|
runWithoutTools: async () => {
|
|
3085
|
-
const res = await this.
|
|
3086
|
-
|
|
3087
|
-
try {
|
|
3088
|
-
if (isResponsesAPI) {
|
|
3089
|
-
const result = await parseOpenAIResponsesStream(
|
|
3090
|
-
res,
|
|
3091
|
-
onPartialResponse
|
|
3092
|
-
);
|
|
3093
|
-
return StreamTextAccumulator.getFullText(result.blocks);
|
|
3094
|
-
}
|
|
3095
|
-
return this.handleStream(res, onPartialResponse);
|
|
3096
|
-
} catch (error) {
|
|
3097
|
-
console.error("[processChat] Error in streaming/completion:", error);
|
|
3098
|
-
throw error;
|
|
3099
|
-
}
|
|
3371
|
+
const res = await this.callKimi(messages, this.model, true);
|
|
3372
|
+
return this.handleStream(res, onPartialResponse);
|
|
3100
3373
|
},
|
|
3101
3374
|
runWithTools: () => this.chatOnce(messages, true, onPartialResponse),
|
|
3102
3375
|
onCompleteResponse,
|
|
@@ -3105,68 +3378,43 @@ If it's in another language, summarize in that language.
|
|
|
3105
3378
|
}
|
|
3106
3379
|
/**
|
|
3107
3380
|
* Process chat messages with images
|
|
3108
|
-
* @param messages Array of messages to send (including images)
|
|
3109
|
-
* @param onPartialResponse Callback to receive each part of streaming response
|
|
3110
|
-
* @param onCompleteResponse Callback to execute when response is complete
|
|
3111
|
-
* @throws Error if the selected model doesn't support vision
|
|
3112
3381
|
*/
|
|
3113
3382
|
async processVisionChat(messages, onPartialResponse, onCompleteResponse) {
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
const res = await this.callOpenAI(messages, this.visionModel, true);
|
|
3119
|
-
const isResponsesAPI = this.endpoint === ENDPOINT_OPENAI_RESPONSES_API;
|
|
3120
|
-
try {
|
|
3121
|
-
if (isResponsesAPI) {
|
|
3122
|
-
const result = await parseOpenAIResponsesStream(
|
|
3123
|
-
res,
|
|
3124
|
-
onPartialResponse
|
|
3125
|
-
);
|
|
3126
|
-
return StreamTextAccumulator.getFullText(result.blocks);
|
|
3127
|
-
}
|
|
3128
|
-
return this.handleStream(res, onPartialResponse);
|
|
3129
|
-
} catch (streamError) {
|
|
3130
|
-
console.error(
|
|
3131
|
-
"[processVisionChat] Error in streaming/completion:",
|
|
3132
|
-
streamError
|
|
3133
|
-
);
|
|
3134
|
-
throw streamError;
|
|
3135
|
-
}
|
|
3136
|
-
},
|
|
3137
|
-
runWithTools: () => this.visionChatOnce(messages, true, onPartialResponse),
|
|
3138
|
-
onCompleteResponse,
|
|
3139
|
-
toolErrorMessage: "processVisionChat received tool_calls. ChatProcessor must use visionChatOnce() loop when tools are enabled."
|
|
3140
|
-
});
|
|
3141
|
-
} catch (error) {
|
|
3142
|
-
console.error("Error in processVisionChat:", error);
|
|
3143
|
-
throw error;
|
|
3383
|
+
if (!isKimiVisionModel(this.visionModel)) {
|
|
3384
|
+
throw new Error(
|
|
3385
|
+
`Model ${this.visionModel} does not support vision capabilities.`
|
|
3386
|
+
);
|
|
3144
3387
|
}
|
|
3388
|
+
await processChatWithOptionalTools({
|
|
3389
|
+
hasTools: this.tools.length > 0,
|
|
3390
|
+
runWithoutTools: async () => {
|
|
3391
|
+
const res = await this.callKimi(messages, this.visionModel, true);
|
|
3392
|
+
return this.handleStream(res, onPartialResponse);
|
|
3393
|
+
},
|
|
3394
|
+
runWithTools: () => this.visionChatOnce(messages, true, onPartialResponse),
|
|
3395
|
+
onCompleteResponse,
|
|
3396
|
+
toolErrorMessage: "processVisionChat received tool_calls. ChatProcessor must use visionChatOnce() loop when tools are enabled."
|
|
3397
|
+
});
|
|
3145
3398
|
}
|
|
3146
3399
|
/**
|
|
3147
3400
|
* Process chat messages with tools (text only)
|
|
3148
|
-
* @param messages Array of messages to send
|
|
3149
|
-
* @param stream Whether to use streaming
|
|
3150
|
-
* @param onPartialResponse Callback for partial responses
|
|
3151
|
-
* @param maxTokens Maximum tokens for response (optional)
|
|
3152
|
-
* @returns Tool chat completion
|
|
3153
3401
|
*/
|
|
3154
3402
|
async chatOnce(messages, stream = true, onPartialResponse = () => {
|
|
3155
3403
|
}, maxTokens) {
|
|
3156
|
-
const res = await this.
|
|
3404
|
+
const res = await this.callKimi(messages, this.model, stream, maxTokens);
|
|
3157
3405
|
return this.parseResponse(res, stream, onPartialResponse);
|
|
3158
3406
|
}
|
|
3159
3407
|
/**
|
|
3160
3408
|
* Process vision chat messages with tools
|
|
3161
|
-
* @param messages Array of messages to send (including images)
|
|
3162
|
-
* @param stream Whether to use streaming
|
|
3163
|
-
* @param onPartialResponse Callback for partial responses
|
|
3164
|
-
* @param maxTokens Maximum tokens for response (optional)
|
|
3165
|
-
* @returns Tool chat completion
|
|
3166
3409
|
*/
|
|
3167
3410
|
async visionChatOnce(messages, stream = false, onPartialResponse = () => {
|
|
3168
3411
|
}, maxTokens) {
|
|
3169
|
-
|
|
3412
|
+
if (!isKimiVisionModel(this.visionModel)) {
|
|
3413
|
+
throw new Error(
|
|
3414
|
+
`Model ${this.visionModel} does not support vision capabilities.`
|
|
3415
|
+
);
|
|
3416
|
+
}
|
|
3417
|
+
const res = await this.callKimi(
|
|
3170
3418
|
messages,
|
|
3171
3419
|
this.visionModel,
|
|
3172
3420
|
stream,
|
|
@@ -3174,205 +3422,258 @@ If it's in another language, summarize in that language.
|
|
|
3174
3422
|
);
|
|
3175
3423
|
return this.parseResponse(res, stream, onPartialResponse);
|
|
3176
3424
|
}
|
|
3177
|
-
/**
|
|
3178
|
-
* Parse response based on endpoint type
|
|
3179
|
-
*/
|
|
3180
3425
|
async parseResponse(res, stream, onPartialResponse) {
|
|
3181
|
-
const isResponsesAPI = this.endpoint === ENDPOINT_OPENAI_RESPONSES_API;
|
|
3182
|
-
if (isResponsesAPI) {
|
|
3183
|
-
return stream ? parseOpenAIResponsesStream(res, onPartialResponse) : parseOpenAIResponsesOneShot(await res.json());
|
|
3184
|
-
}
|
|
3185
3426
|
return stream ? this.parseStream(res, onPartialResponse) : this.parseOneShot(await res.json());
|
|
3186
3427
|
}
|
|
3187
|
-
async
|
|
3428
|
+
async callKimi(messages, model, stream = false, maxTokens) {
|
|
3188
3429
|
const body = this.buildRequestBody(messages, model, stream, maxTokens);
|
|
3189
|
-
const
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
headers.Authorization = `Bearer ${this.apiKey}`;
|
|
3193
|
-
}
|
|
3194
|
-
const res = await ChatServiceHttpClient.post(this.endpoint, body, headers);
|
|
3430
|
+
const res = await ChatServiceHttpClient.post(this.endpoint, body, {
|
|
3431
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
3432
|
+
});
|
|
3195
3433
|
return res;
|
|
3196
3434
|
}
|
|
3197
3435
|
/**
|
|
3198
|
-
* Build request body
|
|
3436
|
+
* Build request body (OpenAI-compatible Chat Completions)
|
|
3437
|
+
*/
|
|
3438
|
+
buildRequestBody(messages, model, stream, maxTokens) {
|
|
3439
|
+
const body = {
|
|
3440
|
+
model,
|
|
3441
|
+
stream,
|
|
3442
|
+
messages
|
|
3443
|
+
};
|
|
3444
|
+
const tokenLimit = maxTokens !== void 0 ? maxTokens : getMaxTokensForResponseLength(this.responseLength);
|
|
3445
|
+
if (tokenLimit !== void 0) {
|
|
3446
|
+
body.max_tokens = tokenLimit;
|
|
3447
|
+
}
|
|
3448
|
+
if (this.responseFormat) {
|
|
3449
|
+
body.response_format = this.responseFormat;
|
|
3450
|
+
}
|
|
3451
|
+
const effectiveThinking = this.tools.length > 0 ? { type: "disabled" } : this.thinking;
|
|
3452
|
+
if (effectiveThinking) {
|
|
3453
|
+
if (this.isSelfHostedEndpoint()) {
|
|
3454
|
+
if (effectiveThinking.type === "disabled") {
|
|
3455
|
+
body.chat_template_kwargs = { thinking: false };
|
|
3456
|
+
}
|
|
3457
|
+
} else {
|
|
3458
|
+
body.thinking = effectiveThinking;
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
const tools = this.buildToolsDefinition();
|
|
3462
|
+
if (tools.length > 0) {
|
|
3463
|
+
body.tools = tools;
|
|
3464
|
+
body.tool_choice = "auto";
|
|
3465
|
+
}
|
|
3466
|
+
return body;
|
|
3467
|
+
}
|
|
3468
|
+
isSelfHostedEndpoint() {
|
|
3469
|
+
return this.normalizeEndpoint(this.endpoint) !== this.normalizeEndpoint(ENDPOINT_KIMI_CHAT_COMPLETIONS_API);
|
|
3470
|
+
}
|
|
3471
|
+
normalizeEndpoint(value) {
|
|
3472
|
+
return value.replace(/\/+$/, "");
|
|
3473
|
+
}
|
|
3474
|
+
buildToolsDefinition() {
|
|
3475
|
+
return buildOpenAICompatibleTools(this.tools, "chat-completions");
|
|
3476
|
+
}
|
|
3477
|
+
async handleStream(res, onPartial) {
|
|
3478
|
+
return parseOpenAICompatibleTextStream(res, onPartial, {
|
|
3479
|
+
onJsonError: (payload) => console.debug("Failed to parse SSE data:", payload)
|
|
3480
|
+
});
|
|
3481
|
+
}
|
|
3482
|
+
/**
|
|
3483
|
+
* Parse streaming response with tool support
|
|
3484
|
+
*/
|
|
3485
|
+
async parseStream(res, onPartial) {
|
|
3486
|
+
return parseOpenAICompatibleToolStream(res, onPartial, {
|
|
3487
|
+
onJsonError: (payload) => console.debug("Failed to parse SSE data:", payload)
|
|
3488
|
+
});
|
|
3489
|
+
}
|
|
3490
|
+
/**
|
|
3491
|
+
* Parse non-streaming response
|
|
3492
|
+
*/
|
|
3493
|
+
parseOneShot(data) {
|
|
3494
|
+
return parseOpenAICompatibleOneShot(data);
|
|
3495
|
+
}
|
|
3496
|
+
};
|
|
3497
|
+
|
|
3498
|
+
// src/services/providers/kimi/KimiChatServiceProvider.ts
|
|
3499
|
+
var KimiChatServiceProvider = class {
|
|
3500
|
+
/**
|
|
3501
|
+
* Create a chat service instance
|
|
3502
|
+
*/
|
|
3503
|
+
createChatService(options) {
|
|
3504
|
+
const endpoint = this.resolveEndpoint(options);
|
|
3505
|
+
const model = options.model || this.getDefaultModel();
|
|
3506
|
+
const visionModel = resolveVisionModel({
|
|
3507
|
+
model,
|
|
3508
|
+
visionModel: options.visionModel,
|
|
3509
|
+
defaultModel: this.getDefaultModel(),
|
|
3510
|
+
defaultVisionModel: this.getDefaultVisionModel(),
|
|
3511
|
+
supportsVisionForModel: (visionModel2) => this.supportsVisionForModel(visionModel2),
|
|
3512
|
+
validate: "explicit"
|
|
3513
|
+
});
|
|
3514
|
+
const tools = options.tools;
|
|
3515
|
+
const defaultThinking = options.thinking ?? { type: "enabled" };
|
|
3516
|
+
const thinking = tools && tools.length > 0 ? { type: "disabled" } : defaultThinking;
|
|
3517
|
+
return new KimiChatService(
|
|
3518
|
+
options.apiKey,
|
|
3519
|
+
model,
|
|
3520
|
+
visionModel,
|
|
3521
|
+
tools,
|
|
3522
|
+
endpoint,
|
|
3523
|
+
options.responseLength,
|
|
3524
|
+
options.responseFormat,
|
|
3525
|
+
thinking
|
|
3526
|
+
);
|
|
3527
|
+
}
|
|
3528
|
+
/**
|
|
3529
|
+
* Get the provider name
|
|
3530
|
+
*/
|
|
3531
|
+
getProviderName() {
|
|
3532
|
+
return "kimi";
|
|
3533
|
+
}
|
|
3534
|
+
/**
|
|
3535
|
+
* Get the list of supported models
|
|
3199
3536
|
*/
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
this.validateMCPCompatibility();
|
|
3203
|
-
const body = {
|
|
3204
|
-
model,
|
|
3205
|
-
stream
|
|
3206
|
-
};
|
|
3207
|
-
const tokenLimit = this.resolveTokenLimit(model, maxTokens);
|
|
3208
|
-
if (isResponsesAPI) {
|
|
3209
|
-
if (tokenLimit !== void 0) {
|
|
3210
|
-
body.max_output_tokens = tokenLimit;
|
|
3211
|
-
}
|
|
3212
|
-
} else {
|
|
3213
|
-
if (tokenLimit !== void 0) {
|
|
3214
|
-
if (this.provider === "openai-compatible") {
|
|
3215
|
-
body.max_tokens = tokenLimit;
|
|
3216
|
-
} else {
|
|
3217
|
-
body.max_completion_tokens = tokenLimit;
|
|
3218
|
-
}
|
|
3219
|
-
}
|
|
3220
|
-
}
|
|
3221
|
-
if (isResponsesAPI) {
|
|
3222
|
-
body.input = this.cleanMessagesForResponsesAPI(messages);
|
|
3223
|
-
} else {
|
|
3224
|
-
body.messages = messages;
|
|
3225
|
-
}
|
|
3226
|
-
if (isGPT5Model(model)) {
|
|
3227
|
-
if (isResponsesAPI) {
|
|
3228
|
-
if (this.reasoning_effort) {
|
|
3229
|
-
body.reasoning = {
|
|
3230
|
-
...body.reasoning,
|
|
3231
|
-
effort: this.reasoning_effort
|
|
3232
|
-
};
|
|
3233
|
-
if (this.enableReasoningSummary) {
|
|
3234
|
-
body.reasoning.summary = "auto";
|
|
3235
|
-
}
|
|
3236
|
-
}
|
|
3237
|
-
if (this.verbosity) {
|
|
3238
|
-
body.text = {
|
|
3239
|
-
...body.text,
|
|
3240
|
-
format: { type: "text" },
|
|
3241
|
-
verbosity: this.verbosity
|
|
3242
|
-
};
|
|
3243
|
-
}
|
|
3244
|
-
} else {
|
|
3245
|
-
if (this.reasoning_effort) {
|
|
3246
|
-
body.reasoning_effort = this.reasoning_effort;
|
|
3247
|
-
}
|
|
3248
|
-
if (this.verbosity) {
|
|
3249
|
-
body.verbosity = this.verbosity;
|
|
3250
|
-
}
|
|
3251
|
-
}
|
|
3252
|
-
}
|
|
3253
|
-
const tools = this.buildToolsDefinition();
|
|
3254
|
-
if (tools.length > 0) {
|
|
3255
|
-
body.tools = tools;
|
|
3256
|
-
if (!isResponsesAPI) {
|
|
3257
|
-
body.tool_choice = "auto";
|
|
3258
|
-
}
|
|
3259
|
-
}
|
|
3260
|
-
return body;
|
|
3261
|
-
}
|
|
3262
|
-
resolveTokenLimit(model, maxTokens) {
|
|
3263
|
-
if (maxTokens !== void 0) {
|
|
3264
|
-
return maxTokens;
|
|
3265
|
-
}
|
|
3266
|
-
const baseTokenLimit = this.provider === "openai-compatible" ? this.responseLength !== void 0 ? getMaxTokensForResponseLength(this.responseLength) : void 0 : getMaxTokensForResponseLength(this.responseLength);
|
|
3267
|
-
if (this.provider !== "openai" || !isGPT5Model(model) || this.responseLength === void 0) {
|
|
3268
|
-
return baseTokenLimit;
|
|
3269
|
-
}
|
|
3270
|
-
const effectiveReasoningEffort = this.reasoning_effort ?? getDefaultReasoningEffortForGPT5Model(model);
|
|
3271
|
-
return Math.max(
|
|
3272
|
-
baseTokenLimit ?? 0,
|
|
3273
|
-
GPT5_RESPONSE_LENGTH_MIN_TOKENS[this.responseLength],
|
|
3274
|
-
GPT5_REASONING_MIN_TOKENS[effectiveReasoningEffort]
|
|
3275
|
-
);
|
|
3537
|
+
getSupportedModels() {
|
|
3538
|
+
return [MODEL_KIMI_K2_6, MODEL_KIMI_K2_5];
|
|
3276
3539
|
}
|
|
3277
3540
|
/**
|
|
3278
|
-
*
|
|
3541
|
+
* Get the default model
|
|
3279
3542
|
*/
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
throw new Error(
|
|
3283
|
-
`MCP servers are not supported with Chat Completions API. Current endpoint: ${this.endpoint}. Please use OpenAI Responses API endpoint: ${ENDPOINT_OPENAI_RESPONSES_API}. MCP tools are only available in the Responses API endpoint.`
|
|
3284
|
-
);
|
|
3285
|
-
}
|
|
3543
|
+
getDefaultModel() {
|
|
3544
|
+
return MODEL_KIMI_K2_6;
|
|
3286
3545
|
}
|
|
3287
3546
|
/**
|
|
3288
|
-
*
|
|
3547
|
+
* Get the default vision model
|
|
3289
3548
|
*/
|
|
3290
|
-
|
|
3291
|
-
return
|
|
3292
|
-
const role = msg.role === "tool" ? "user" : msg.role;
|
|
3293
|
-
const cleanMsg = {
|
|
3294
|
-
role
|
|
3295
|
-
};
|
|
3296
|
-
if (typeof msg.content === "string") {
|
|
3297
|
-
cleanMsg.content = msg.content;
|
|
3298
|
-
} else if (Array.isArray(msg.content)) {
|
|
3299
|
-
cleanMsg.content = msg.content.map((block) => {
|
|
3300
|
-
if (block.type === "text") {
|
|
3301
|
-
return {
|
|
3302
|
-
type: "input_text",
|
|
3303
|
-
text: block.text
|
|
3304
|
-
};
|
|
3305
|
-
} else if (block.type === "image_url") {
|
|
3306
|
-
return {
|
|
3307
|
-
type: "input_image",
|
|
3308
|
-
image_url: block.image_url.url
|
|
3309
|
-
// Extract the URL string directly
|
|
3310
|
-
};
|
|
3311
|
-
}
|
|
3312
|
-
return block;
|
|
3313
|
-
});
|
|
3314
|
-
} else {
|
|
3315
|
-
cleanMsg.content = msg.content;
|
|
3316
|
-
}
|
|
3317
|
-
return cleanMsg;
|
|
3318
|
-
});
|
|
3549
|
+
getDefaultVisionModel() {
|
|
3550
|
+
return MODEL_KIMI_K2_6;
|
|
3319
3551
|
}
|
|
3320
3552
|
/**
|
|
3321
|
-
*
|
|
3553
|
+
* Check if this provider supports vision
|
|
3322
3554
|
*/
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
...buildOpenAICompatibleTools(
|
|
3329
|
-
this.tools,
|
|
3330
|
-
isResponsesAPI ? "responses" : "chat-completions"
|
|
3331
|
-
)
|
|
3332
|
-
);
|
|
3333
|
-
}
|
|
3334
|
-
if (this.mcpServers.length > 0 && isResponsesAPI) {
|
|
3335
|
-
toolDefs.push(...this.buildMCPToolsDefinition());
|
|
3336
|
-
}
|
|
3337
|
-
return toolDefs;
|
|
3555
|
+
supportsVision() {
|
|
3556
|
+
return this.getVisionSupportLevel() !== "unsupported";
|
|
3557
|
+
}
|
|
3558
|
+
getVisionSupportLevel() {
|
|
3559
|
+
return "supported";
|
|
3338
3560
|
}
|
|
3339
3561
|
/**
|
|
3340
|
-
*
|
|
3562
|
+
* Check if a specific model supports vision capabilities
|
|
3341
3563
|
*/
|
|
3342
|
-
|
|
3343
|
-
return
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
mcpDef.allowed_tools = server.tool_configuration.allowed_tools;
|
|
3357
|
-
}
|
|
3358
|
-
if (server.authorization_token) {
|
|
3359
|
-
mcpDef.headers = {
|
|
3360
|
-
Authorization: `Bearer ${server.authorization_token}`
|
|
3361
|
-
};
|
|
3564
|
+
supportsVisionForModel(model) {
|
|
3565
|
+
return isKimiVisionModel(model);
|
|
3566
|
+
}
|
|
3567
|
+
getVisionSupportLevelForModel(model) {
|
|
3568
|
+
return this.supportsVisionForModel(model) ? "supported" : "unsupported";
|
|
3569
|
+
}
|
|
3570
|
+
resolveEndpoint(options) {
|
|
3571
|
+
if (options.endpoint) {
|
|
3572
|
+
return this.normalizeEndpoint(options.endpoint);
|
|
3573
|
+
}
|
|
3574
|
+
if (options.baseUrl) {
|
|
3575
|
+
const baseUrl = this.normalizeEndpoint(options.baseUrl);
|
|
3576
|
+
if (baseUrl.endsWith("/chat/completions")) {
|
|
3577
|
+
return baseUrl;
|
|
3362
3578
|
}
|
|
3363
|
-
return
|
|
3364
|
-
}
|
|
3579
|
+
return `${baseUrl}/chat/completions`;
|
|
3580
|
+
}
|
|
3581
|
+
return ENDPOINT_KIMI_CHAT_COMPLETIONS_API;
|
|
3365
3582
|
}
|
|
3366
|
-
|
|
3367
|
-
return
|
|
3583
|
+
normalizeEndpoint(value) {
|
|
3584
|
+
return value.replace(/\/+$/, "");
|
|
3368
3585
|
}
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3586
|
+
};
|
|
3587
|
+
|
|
3588
|
+
// src/services/providers/mistral/MistralChatService.ts
|
|
3589
|
+
var MistralChatService = class extends OpenAIChatService {
|
|
3590
|
+
constructor(apiKey, model = MODEL_MISTRAL_SMALL_LATEST, visionModel = model, tools, endpoint = ENDPOINT_MISTRAL_CHAT_COMPLETIONS_API, responseLength, reasoningEffort) {
|
|
3591
|
+
super(
|
|
3592
|
+
apiKey,
|
|
3593
|
+
model,
|
|
3594
|
+
visionModel,
|
|
3595
|
+
tools,
|
|
3596
|
+
endpoint,
|
|
3597
|
+
[],
|
|
3598
|
+
responseLength,
|
|
3599
|
+
void 0,
|
|
3600
|
+
reasoningEffort,
|
|
3601
|
+
false,
|
|
3602
|
+
"mistral",
|
|
3603
|
+
false
|
|
3604
|
+
);
|
|
3605
|
+
}
|
|
3606
|
+
};
|
|
3607
|
+
|
|
3608
|
+
// src/services/providers/mistral/MistralChatServiceProvider.ts
|
|
3609
|
+
var MistralChatServiceProvider = class {
|
|
3610
|
+
createChatService(options) {
|
|
3611
|
+
this.validateRequiredOptions(options);
|
|
3612
|
+
const model = options.model || this.getDefaultModel();
|
|
3613
|
+
const visionModel = resolveVisionModel({
|
|
3614
|
+
model,
|
|
3615
|
+
visionModel: options.visionModel,
|
|
3616
|
+
defaultModel: this.getDefaultModel(),
|
|
3617
|
+
defaultVisionModel: this.getDefaultVisionModel(),
|
|
3618
|
+
supportsVisionForModel: (targetModel) => this.supportsVisionForModel(targetModel),
|
|
3619
|
+
validate: "explicit"
|
|
3372
3620
|
});
|
|
3621
|
+
const tools = options.tools;
|
|
3622
|
+
const reasoningEffort = isMistralReasoningEffortModel(model) ? options.reasoning_effort : void 0;
|
|
3623
|
+
return new MistralChatService(
|
|
3624
|
+
options.apiKey,
|
|
3625
|
+
model,
|
|
3626
|
+
visionModel,
|
|
3627
|
+
tools,
|
|
3628
|
+
this.resolveEndpoint(options),
|
|
3629
|
+
options.responseLength,
|
|
3630
|
+
reasoningEffort
|
|
3631
|
+
);
|
|
3373
3632
|
}
|
|
3374
|
-
|
|
3375
|
-
return
|
|
3633
|
+
getProviderName() {
|
|
3634
|
+
return "mistral";
|
|
3635
|
+
}
|
|
3636
|
+
getSupportedModels() {
|
|
3637
|
+
return [...MISTRAL_SUPPORTED_MODELS];
|
|
3638
|
+
}
|
|
3639
|
+
getDefaultModel() {
|
|
3640
|
+
return MODEL_MISTRAL_SMALL_LATEST;
|
|
3641
|
+
}
|
|
3642
|
+
getDefaultVisionModel() {
|
|
3643
|
+
return MODEL_MISTRAL_SMALL_LATEST;
|
|
3644
|
+
}
|
|
3645
|
+
supportsVision() {
|
|
3646
|
+
return this.getVisionSupportLevel() !== "unsupported";
|
|
3647
|
+
}
|
|
3648
|
+
getVisionSupportLevel() {
|
|
3649
|
+
return "supported";
|
|
3650
|
+
}
|
|
3651
|
+
supportsVisionForModel(model) {
|
|
3652
|
+
return isMistralVisionModel(model);
|
|
3653
|
+
}
|
|
3654
|
+
getVisionSupportLevelForModel(model) {
|
|
3655
|
+
return this.supportsVisionForModel(model) ? "supported" : "unsupported";
|
|
3656
|
+
}
|
|
3657
|
+
validateRequiredOptions(options) {
|
|
3658
|
+
if (!options.apiKey?.trim()) {
|
|
3659
|
+
throw new Error("mistral provider requires apiKey.");
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
resolveEndpoint(options) {
|
|
3663
|
+
if (options.endpoint) {
|
|
3664
|
+
return this.normalizeUrl(options.endpoint);
|
|
3665
|
+
}
|
|
3666
|
+
if (options.baseUrl) {
|
|
3667
|
+
const baseUrl = this.normalizeUrl(options.baseUrl);
|
|
3668
|
+
if (baseUrl.endsWith("/chat/completions")) {
|
|
3669
|
+
return baseUrl;
|
|
3670
|
+
}
|
|
3671
|
+
return `${baseUrl}/chat/completions`;
|
|
3672
|
+
}
|
|
3673
|
+
return ENDPOINT_MISTRAL_CHAT_COMPLETIONS_API;
|
|
3674
|
+
}
|
|
3675
|
+
normalizeUrl(value) {
|
|
3676
|
+
return value.trim().replace(/\/+$/, "");
|
|
3376
3677
|
}
|
|
3377
3678
|
};
|
|
3378
3679
|
|
|
@@ -4449,7 +4750,9 @@ If it's in another language, summarize in that language.
|
|
|
4449
4750
|
new OpenRouterChatServiceProvider(),
|
|
4450
4751
|
new ZAIChatServiceProvider(),
|
|
4451
4752
|
new XAIChatServiceProvider(),
|
|
4452
|
-
new KimiChatServiceProvider()
|
|
4753
|
+
new KimiChatServiceProvider(),
|
|
4754
|
+
new DeepSeekChatServiceProvider(),
|
|
4755
|
+
new MistralChatServiceProvider()
|
|
4453
4756
|
];
|
|
4454
4757
|
|
|
4455
4758
|
// src/services/ChatServiceFactory.ts
|