converse-mcp-server 2.3.1 → 2.4.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.md +771 -738
- package/docs/API.md +10 -1
- package/docs/PROVIDERS.md +8 -4
- package/package.json +12 -12
- package/src/async/asyncJobStore.js +82 -52
- package/src/async/eventBus.js +25 -20
- package/src/async/fileCache.js +121 -40
- package/src/async/jobRunner.js +65 -39
- package/src/async/providerStreamNormalizer.js +203 -117
- package/src/config.js +374 -102
- package/src/continuationStore.js +32 -24
- package/src/index.js +45 -25
- package/src/prompts/helpPrompt.js +328 -305
- package/src/providers/anthropic.js +303 -119
- package/src/providers/codex.js +103 -45
- package/src/providers/deepseek.js +24 -8
- package/src/providers/google.js +323 -93
- package/src/providers/index.js +1 -1
- package/src/providers/interface.js +16 -11
- package/src/providers/mistral.js +179 -69
- package/src/providers/openai-compatible.js +231 -94
- package/src/providers/openai.js +1094 -914
- package/src/providers/openrouter-endpoints-client.js +220 -216
- package/src/providers/openrouter.js +426 -381
- package/src/providers/xai.js +153 -56
- package/src/resources/helpResource.js +70 -67
- package/src/router.js +95 -67
- package/src/services/summarizationService.js +51 -24
- package/src/systemPrompts.js +89 -89
- package/src/tools/cancelJob.js +31 -19
- package/src/tools/chat.js +997 -883
- package/src/tools/checkStatus.js +86 -65
- package/src/tools/consensus.js +400 -234
- package/src/tools/index.js +39 -16
- package/src/transport/httpTransport.js +82 -55
- package/src/utils/contextProcessor.js +54 -37
- package/src/utils/errorHandler.js +95 -45
- package/src/utils/fileValidator.js +107 -98
- package/src/utils/formatStatus.js +122 -64
- package/src/utils/logger.js +459 -449
- package/src/utils/pathUtils.js +2 -2
- package/src/utils/tokenLimiter.js +216 -216
package/src/providers/google.js
CHANGED
|
@@ -22,8 +22,15 @@ const SUPPORTED_MODELS = {
|
|
|
22
22
|
supportsWebSearch: true,
|
|
23
23
|
maxThinkingTokens: 0,
|
|
24
24
|
timeout: 300000,
|
|
25
|
-
description:
|
|
26
|
-
|
|
25
|
+
description:
|
|
26
|
+
'Gemini 2.0 Flash (1M context) - Latest fast model, supports audio/video input and grounding',
|
|
27
|
+
aliases: [
|
|
28
|
+
'flash-2.0',
|
|
29
|
+
'flash2',
|
|
30
|
+
'flash 2.0',
|
|
31
|
+
'gemini flash 2.0',
|
|
32
|
+
'gemini-2.0-flash-latest',
|
|
33
|
+
],
|
|
27
34
|
},
|
|
28
35
|
'gemini-2.0-flash-lite': {
|
|
29
36
|
modelName: 'gemini-2.0-flash-lite',
|
|
@@ -37,8 +44,16 @@ const SUPPORTED_MODELS = {
|
|
|
37
44
|
supportsWebSearch: true,
|
|
38
45
|
maxThinkingTokens: 0,
|
|
39
46
|
timeout: 300000,
|
|
40
|
-
description:
|
|
41
|
-
|
|
47
|
+
description:
|
|
48
|
+
'Gemini 2.0 Flash Lite (1M context) - Lightweight fast model, text-only with grounding',
|
|
49
|
+
aliases: [
|
|
50
|
+
'flashlite',
|
|
51
|
+
'flash-lite',
|
|
52
|
+
'flash lite',
|
|
53
|
+
'flash-lite-2.0',
|
|
54
|
+
'gemini flash lite',
|
|
55
|
+
'gemini-2.0-flash-lite-latest',
|
|
56
|
+
],
|
|
42
57
|
},
|
|
43
58
|
'gemini-2.5-flash': {
|
|
44
59
|
modelName: 'gemini-flash-latest',
|
|
@@ -52,8 +67,19 @@ const SUPPORTED_MODELS = {
|
|
|
52
67
|
supportsWebSearch: true,
|
|
53
68
|
maxThinkingTokens: 24576,
|
|
54
69
|
timeout: 300000,
|
|
55
|
-
description:
|
|
56
|
-
|
|
70
|
+
description:
|
|
71
|
+
'Ultra-fast (1M context) - Quick analysis, simple queries, rapid iterations with grounding',
|
|
72
|
+
aliases: [
|
|
73
|
+
'flash',
|
|
74
|
+
'flash2.5',
|
|
75
|
+
'gemini-flash',
|
|
76
|
+
'gemini-flash-2.5',
|
|
77
|
+
'flash 2.5',
|
|
78
|
+
'gemini flash 2.5',
|
|
79
|
+
'gemini-2.5-flash',
|
|
80
|
+
'gemini-2.5-flash-preview-09-2025',
|
|
81
|
+
'gemini-2.5-flash-latest',
|
|
82
|
+
],
|
|
57
83
|
},
|
|
58
84
|
'gemini-2.5-flash-lite': {
|
|
59
85
|
modelName: 'gemini-flash-lite-latest',
|
|
@@ -67,8 +93,17 @@ const SUPPORTED_MODELS = {
|
|
|
67
93
|
supportsWebSearch: true,
|
|
68
94
|
maxThinkingTokens: 24576,
|
|
69
95
|
timeout: 300000,
|
|
70
|
-
description:
|
|
71
|
-
|
|
96
|
+
description:
|
|
97
|
+
'Lightweight fast model (1M context) - Efficient quick responses with grounding',
|
|
98
|
+
aliases: [
|
|
99
|
+
'flashlite2.5',
|
|
100
|
+
'flash-lite',
|
|
101
|
+
'flash lite',
|
|
102
|
+
'gemini-flash-lite',
|
|
103
|
+
'gemini flash lite',
|
|
104
|
+
'gemini-2.5-flash-lite-preview-09-2025',
|
|
105
|
+
'gemini-2.5-flash-lite-latest',
|
|
106
|
+
],
|
|
72
107
|
},
|
|
73
108
|
'gemini-2.5-pro': {
|
|
74
109
|
modelName: 'gemini-2.5-pro',
|
|
@@ -82,9 +117,39 @@ const SUPPORTED_MODELS = {
|
|
|
82
117
|
supportsThinking: true,
|
|
83
118
|
maxThinkingTokens: 32768,
|
|
84
119
|
timeout: 300000,
|
|
85
|
-
description:
|
|
86
|
-
|
|
87
|
-
|
|
120
|
+
description:
|
|
121
|
+
'Deep reasoning + thinking mode (1M context) - Complex problems, architecture, deep analysis',
|
|
122
|
+
aliases: [
|
|
123
|
+
'pro 2.5',
|
|
124
|
+
'gemini pro 2.5',
|
|
125
|
+
'gemini-2.5-pro-latest',
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
'gemini-3-pro-preview': {
|
|
129
|
+
modelName: 'gemini-3-pro-preview',
|
|
130
|
+
friendlyName: 'Gemini (Pro 3.0)',
|
|
131
|
+
contextWindow: 1048576, // 1M tokens
|
|
132
|
+
maxOutputTokens: 64000,
|
|
133
|
+
supportsStreaming: true,
|
|
134
|
+
supportsImages: true,
|
|
135
|
+
supportsTemperature: true,
|
|
136
|
+
supportsThinking: true, // Always enabled for Gemini 3.0
|
|
137
|
+
supportsWebSearch: true,
|
|
138
|
+
thinkingMode: 'level', // Distinguishes from 2.5's budget mode
|
|
139
|
+
timeout: 300000,
|
|
140
|
+
description:
|
|
141
|
+
'Gemini 3.0 Pro - Enhanced reasoning with dynamic thinking levels (1M context)',
|
|
142
|
+
aliases: [
|
|
143
|
+
'gemini-3',
|
|
144
|
+
'gemini3',
|
|
145
|
+
'gemini-3-pro',
|
|
146
|
+
'3-pro',
|
|
147
|
+
'gemini', // Moving from 2.5 Pro
|
|
148
|
+
'gemini pro', // Moving from 2.5 Pro
|
|
149
|
+
'gemini-pro', // Moving from 2.5 Pro
|
|
150
|
+
'pro', // Moving from 2.5 Pro
|
|
151
|
+
],
|
|
152
|
+
},
|
|
88
153
|
};
|
|
89
154
|
|
|
90
155
|
// Thinking mode budget percentages
|
|
@@ -93,7 +158,7 @@ const THINKING_BUDGETS = {
|
|
|
93
158
|
low: 0.08, // 8% of max - light reasoning tasks
|
|
94
159
|
medium: 0.33, // 33% of max - balanced reasoning (default)
|
|
95
160
|
high: 0.67, // 67% of max - complex analysis
|
|
96
|
-
max: 1.0 // 100% of max - full thinking budget
|
|
161
|
+
max: 1.0, // 100% of max - full thinking budget
|
|
97
162
|
};
|
|
98
163
|
|
|
99
164
|
/**
|
|
@@ -159,7 +224,10 @@ function validateApiKey(apiKey) {
|
|
|
159
224
|
*/
|
|
160
225
|
function convertMessagesToGemini(messages) {
|
|
161
226
|
if (!Array.isArray(messages)) {
|
|
162
|
-
throw new GoogleProviderError(
|
|
227
|
+
throw new GoogleProviderError(
|
|
228
|
+
'Messages must be an array',
|
|
229
|
+
'INVALID_MESSAGES',
|
|
230
|
+
);
|
|
163
231
|
}
|
|
164
232
|
|
|
165
233
|
const contents = [];
|
|
@@ -167,17 +235,26 @@ function convertMessagesToGemini(messages) {
|
|
|
167
235
|
|
|
168
236
|
for (const [index, msg] of messages.entries()) {
|
|
169
237
|
if (!msg || typeof msg !== 'object') {
|
|
170
|
-
throw new GoogleProviderError(
|
|
238
|
+
throw new GoogleProviderError(
|
|
239
|
+
`Message at index ${index} must be an object`,
|
|
240
|
+
'INVALID_MESSAGE',
|
|
241
|
+
);
|
|
171
242
|
}
|
|
172
243
|
|
|
173
244
|
const { role, content } = msg;
|
|
174
245
|
|
|
175
246
|
if (!role || !['system', 'user', 'assistant'].includes(role)) {
|
|
176
|
-
throw new GoogleProviderError(
|
|
247
|
+
throw new GoogleProviderError(
|
|
248
|
+
`Invalid role "${role}" at message index ${index}`,
|
|
249
|
+
'INVALID_ROLE',
|
|
250
|
+
);
|
|
177
251
|
}
|
|
178
252
|
|
|
179
253
|
if (!content) {
|
|
180
|
-
throw new GoogleProviderError(
|
|
254
|
+
throw new GoogleProviderError(
|
|
255
|
+
`Message content is required at index ${index}`,
|
|
256
|
+
'MISSING_CONTENT',
|
|
257
|
+
);
|
|
181
258
|
}
|
|
182
259
|
|
|
183
260
|
if (role === 'system') {
|
|
@@ -198,27 +275,33 @@ function convertMessagesToGemini(messages) {
|
|
|
198
275
|
parts.push({
|
|
199
276
|
inlineData: {
|
|
200
277
|
mimeType: item.source.media_type,
|
|
201
|
-
data: item.source.data
|
|
202
|
-
}
|
|
278
|
+
data: item.source.data,
|
|
279
|
+
},
|
|
203
280
|
});
|
|
204
|
-
debugLog(
|
|
281
|
+
debugLog(
|
|
282
|
+
`[Google] Converting image: ${item.source.media_type}, data length: ${item.source.data.length}`,
|
|
283
|
+
);
|
|
205
284
|
}
|
|
206
285
|
}
|
|
207
286
|
|
|
208
287
|
// Combine system prompt with text content if present
|
|
209
|
-
const finalTextContent = systemPrompt
|
|
288
|
+
const finalTextContent = systemPrompt
|
|
289
|
+
? `${systemPrompt}\n\n${textContent}`
|
|
290
|
+
: textContent;
|
|
210
291
|
if (finalTextContent) {
|
|
211
292
|
parts.unshift({ text: finalTextContent });
|
|
212
293
|
}
|
|
213
294
|
} else {
|
|
214
295
|
// Simple string content
|
|
215
|
-
const userContent = systemPrompt
|
|
296
|
+
const userContent = systemPrompt
|
|
297
|
+
? `${systemPrompt}\n\n${content}`
|
|
298
|
+
: content;
|
|
216
299
|
parts.push({ text: userContent });
|
|
217
300
|
}
|
|
218
301
|
|
|
219
302
|
contents.push({
|
|
220
303
|
role: 'user',
|
|
221
|
-
parts
|
|
304
|
+
parts,
|
|
222
305
|
});
|
|
223
306
|
systemPrompt = null; // Only use system prompt once
|
|
224
307
|
} else if (role === 'assistant') {
|
|
@@ -233,12 +316,12 @@ function convertMessagesToGemini(messages) {
|
|
|
233
316
|
}
|
|
234
317
|
contents.push({
|
|
235
318
|
role: 'model', // Google uses 'model' instead of 'assistant'
|
|
236
|
-
parts
|
|
319
|
+
parts,
|
|
237
320
|
});
|
|
238
321
|
} else {
|
|
239
322
|
contents.push({
|
|
240
323
|
role: 'model',
|
|
241
|
-
parts: [{ text: content }]
|
|
324
|
+
parts: [{ text: content }],
|
|
242
325
|
});
|
|
243
326
|
}
|
|
244
327
|
}
|
|
@@ -279,10 +362,12 @@ function isErrorRetryable(error) {
|
|
|
279
362
|
'read timeout',
|
|
280
363
|
'timeout error',
|
|
281
364
|
'408',
|
|
282
|
-
'deadline exceeded'
|
|
365
|
+
'deadline exceeded',
|
|
283
366
|
];
|
|
284
367
|
|
|
285
|
-
if (
|
|
368
|
+
if (
|
|
369
|
+
nonRetryableIndicators.some((indicator) => errorStr.includes(indicator))
|
|
370
|
+
) {
|
|
286
371
|
return false;
|
|
287
372
|
}
|
|
288
373
|
|
|
@@ -300,10 +385,10 @@ function isErrorRetryable(error) {
|
|
|
300
385
|
'503',
|
|
301
386
|
'504',
|
|
302
387
|
'ssl',
|
|
303
|
-
'handshake'
|
|
388
|
+
'handshake',
|
|
304
389
|
];
|
|
305
390
|
|
|
306
|
-
return retryableIndicators.some(indicator => errorStr.includes(indicator));
|
|
391
|
+
return retryableIndicators.some((indicator) => errorStr.includes(indicator));
|
|
307
392
|
}
|
|
308
393
|
|
|
309
394
|
/**
|
|
@@ -326,8 +411,11 @@ async function retryWithBackoff(fn, maxRetries = 4) {
|
|
|
326
411
|
|
|
327
412
|
// Wait before retrying
|
|
328
413
|
const delay = retryDelays[attempt];
|
|
329
|
-
debugLog(
|
|
330
|
-
|
|
414
|
+
debugLog(
|
|
415
|
+
`[Google] Retrying after ${delay}ms (attempt ${attempt + 1}/${maxRetries}):`,
|
|
416
|
+
error.message,
|
|
417
|
+
);
|
|
418
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
331
419
|
}
|
|
332
420
|
}
|
|
333
421
|
|
|
@@ -352,6 +440,7 @@ export const googleProvider = {
|
|
|
352
440
|
stream = false,
|
|
353
441
|
reasoning_effort = 'medium',
|
|
354
442
|
use_websearch = false,
|
|
443
|
+
media_resolution = null,
|
|
355
444
|
signal,
|
|
356
445
|
config,
|
|
357
446
|
..._otherOptions
|
|
@@ -370,18 +459,20 @@ export const googleProvider = {
|
|
|
370
459
|
if (!vertexProject || !vertexLocation) {
|
|
371
460
|
throw new GoogleProviderError(
|
|
372
461
|
'Vertex AI requires GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION',
|
|
373
|
-
'MISSING_VERTEX_CONFIG'
|
|
462
|
+
'MISSING_VERTEX_CONFIG',
|
|
374
463
|
);
|
|
375
464
|
}
|
|
376
465
|
|
|
377
|
-
debugLog(
|
|
466
|
+
debugLog(
|
|
467
|
+
`[Google] Using Vertex AI: project=${vertexProject}, location=${vertexLocation}, apiVersion=${apiVersion}`,
|
|
468
|
+
);
|
|
378
469
|
|
|
379
470
|
// Initialize with Vertex AI configuration
|
|
380
471
|
genAI = new GoogleGenAI({
|
|
381
472
|
vertexai: true,
|
|
382
473
|
project: vertexProject,
|
|
383
474
|
location: vertexLocation,
|
|
384
|
-
apiVersion
|
|
475
|
+
apiVersion,
|
|
385
476
|
});
|
|
386
477
|
} else {
|
|
387
478
|
// Use Gemini Developer API with API key
|
|
@@ -390,20 +481,25 @@ export const googleProvider = {
|
|
|
390
481
|
if (!apiKey || apiKey === 'VERTEX_AI') {
|
|
391
482
|
throw new GoogleProviderError(
|
|
392
483
|
'Google API key not configured. Set GOOGLE_API_KEY or GEMINI_API_KEY, or configure Vertex AI',
|
|
393
|
-
'MISSING_API_KEY'
|
|
484
|
+
'MISSING_API_KEY',
|
|
394
485
|
);
|
|
395
486
|
}
|
|
396
487
|
|
|
397
488
|
if (!validateApiKey(apiKey)) {
|
|
398
|
-
throw new GoogleProviderError(
|
|
489
|
+
throw new GoogleProviderError(
|
|
490
|
+
'Invalid Google API key format',
|
|
491
|
+
'INVALID_API_KEY',
|
|
492
|
+
);
|
|
399
493
|
}
|
|
400
494
|
|
|
401
|
-
debugLog(
|
|
495
|
+
debugLog(
|
|
496
|
+
`[Google] Using Gemini Developer API with configured API key, apiVersion=${apiVersion}`,
|
|
497
|
+
);
|
|
402
498
|
|
|
403
499
|
// Initialize with API key - SDK will use GOOGLE_API_KEY as the actual key name
|
|
404
500
|
genAI = new GoogleGenAI({
|
|
405
501
|
apiKey,
|
|
406
|
-
apiVersion
|
|
502
|
+
apiVersion,
|
|
407
503
|
});
|
|
408
504
|
}
|
|
409
505
|
|
|
@@ -420,23 +516,53 @@ export const googleProvider = {
|
|
|
420
516
|
const generationConfig = {};
|
|
421
517
|
|
|
422
518
|
// Add temperature if model supports it
|
|
423
|
-
if (
|
|
519
|
+
if (
|
|
520
|
+
modelConfig.supportsTemperature !== false &&
|
|
521
|
+
temperature !== undefined
|
|
522
|
+
) {
|
|
424
523
|
generationConfig.temperature = Math.max(0, Math.min(2, temperature));
|
|
425
524
|
}
|
|
426
525
|
|
|
427
526
|
// Add max tokens if specified
|
|
428
527
|
if (maxTokens) {
|
|
429
|
-
generationConfig.maxOutputTokens = Math.min(
|
|
528
|
+
generationConfig.maxOutputTokens = Math.min(
|
|
529
|
+
maxTokens,
|
|
530
|
+
modelConfig.maxOutputTokens || 65536,
|
|
531
|
+
);
|
|
430
532
|
}
|
|
431
533
|
|
|
432
534
|
// Add thinking configuration for models that support it
|
|
433
535
|
if (modelConfig.supportsThinking && reasoning_effort) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
536
|
+
if (modelConfig.thinkingMode === 'level') {
|
|
537
|
+
// Gemini 3.0: Use thinking level (low/high)
|
|
538
|
+
const thinkingLevel = ['minimal', 'low'].includes(reasoning_effort)
|
|
539
|
+
? 'low'
|
|
540
|
+
: 'high';
|
|
541
|
+
generationConfig.thinkingConfig = { thinkingLevel };
|
|
542
|
+
} else {
|
|
543
|
+
// Gemini 2.5: Use thinking budget (token count)
|
|
544
|
+
const thinkingBudget = calculateThinkingBudget(
|
|
545
|
+
modelConfig,
|
|
546
|
+
reasoning_effort,
|
|
547
|
+
);
|
|
548
|
+
if (thinkingBudget > 0) {
|
|
549
|
+
generationConfig.thinkingConfig = { thinkingBudget };
|
|
550
|
+
}
|
|
437
551
|
}
|
|
438
552
|
}
|
|
439
553
|
|
|
554
|
+
// Add media resolution for Gemini 3.0 models
|
|
555
|
+
if (modelConfig.thinkingMode === 'level') {
|
|
556
|
+
// Default to "high" for Gemini 3.0 if not specified
|
|
557
|
+
const resolution = media_resolution || 'high';
|
|
558
|
+
if (['low', 'medium', 'high'].includes(resolution)) {
|
|
559
|
+
generationConfig.mediaResolution = resolution;
|
|
560
|
+
}
|
|
561
|
+
} else if (media_resolution && ['low', 'medium', 'high'].includes(media_resolution)) {
|
|
562
|
+
// For other models, only add if explicitly specified
|
|
563
|
+
generationConfig.mediaResolution = media_resolution;
|
|
564
|
+
}
|
|
565
|
+
|
|
440
566
|
// Add web search grounding if requested and model supports it
|
|
441
567
|
if (use_websearch && modelConfig.supportsWebSearch) {
|
|
442
568
|
generationConfig.tools = [{ googleSearch: {} }];
|
|
@@ -446,14 +572,27 @@ export const googleProvider = {
|
|
|
446
572
|
if (stream) {
|
|
447
573
|
// Check if model supports streaming
|
|
448
574
|
if (modelConfig.supportsStreaming === false) {
|
|
449
|
-
debugLog(
|
|
575
|
+
debugLog(
|
|
576
|
+
`[Google] Model ${resolvedModel} doesn't support streaming, falling back to non-streaming mode`,
|
|
577
|
+
);
|
|
450
578
|
} else {
|
|
451
|
-
return this._createStreamingGenerator(
|
|
579
|
+
return this._createStreamingGenerator(
|
|
580
|
+
genAI,
|
|
581
|
+
resolvedModel,
|
|
582
|
+
geminiContents,
|
|
583
|
+
generationConfig,
|
|
584
|
+
modelConfig,
|
|
585
|
+
reasoning_effort,
|
|
586
|
+
use_websearch,
|
|
587
|
+
signal,
|
|
588
|
+
);
|
|
452
589
|
}
|
|
453
590
|
}
|
|
454
591
|
|
|
455
592
|
try {
|
|
456
|
-
debugLog(
|
|
593
|
+
debugLog(
|
|
594
|
+
`[Google] Calling ${resolvedModel} with ${messages.length} messages${use_websearch && modelConfig.supportsWebSearch ? ' (with grounding)' : ''}`,
|
|
595
|
+
);
|
|
457
596
|
|
|
458
597
|
// Check if already aborted before making request
|
|
459
598
|
if (signal?.aborted) {
|
|
@@ -471,7 +610,7 @@ export const googleProvider = {
|
|
|
471
610
|
return await genAI.models.generateContent({
|
|
472
611
|
model: resolvedModel,
|
|
473
612
|
contents: geminiContents,
|
|
474
|
-
config: generationConfig
|
|
613
|
+
config: generationConfig,
|
|
475
614
|
});
|
|
476
615
|
});
|
|
477
616
|
|
|
@@ -481,14 +620,17 @@ export const googleProvider = {
|
|
|
481
620
|
// Extract response data using the new SDK format
|
|
482
621
|
const content = response.text;
|
|
483
622
|
if (!content) {
|
|
484
|
-
throw new GoogleProviderError(
|
|
623
|
+
throw new GoogleProviderError(
|
|
624
|
+
'No text content received from Google',
|
|
625
|
+
'NO_RESPONSE_CONTENT',
|
|
626
|
+
);
|
|
485
627
|
}
|
|
486
628
|
|
|
487
629
|
// Extract usage information from the new SDK format
|
|
488
630
|
const usage = {
|
|
489
631
|
input_tokens: response.usageMetadata?.promptTokenCount || 0,
|
|
490
632
|
output_tokens: response.usageMetadata?.candidatesTokenCount || 0,
|
|
491
|
-
total_tokens: response.usageMetadata?.totalTokenCount || 0
|
|
633
|
+
total_tokens: response.usageMetadata?.totalTokenCount || 0,
|
|
492
634
|
};
|
|
493
635
|
|
|
494
636
|
// Extract finish reason from candidates
|
|
@@ -504,36 +646,67 @@ export const googleProvider = {
|
|
|
504
646
|
usage,
|
|
505
647
|
response_time_ms: responseTime,
|
|
506
648
|
finish_reason: finishReason,
|
|
507
|
-
reasoning_effort: modelConfig.supportsThinking
|
|
649
|
+
reasoning_effort: modelConfig.supportsThinking
|
|
650
|
+
? reasoning_effort
|
|
651
|
+
: null,
|
|
508
652
|
provider: 'google',
|
|
509
653
|
web_search_used: use_websearch && modelConfig.supportsWebSearch,
|
|
510
|
-
grounding_metadata: response.groundingMetadata || null
|
|
511
|
-
}
|
|
654
|
+
grounding_metadata: response.groundingMetadata || null,
|
|
655
|
+
},
|
|
512
656
|
};
|
|
513
|
-
|
|
514
657
|
} catch (error) {
|
|
515
658
|
debugError('[Google] Error during API call:', error);
|
|
516
659
|
|
|
517
660
|
// Handle specific Google errors
|
|
518
|
-
if (
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
661
|
+
if (
|
|
662
|
+
error.message?.includes('quota') ||
|
|
663
|
+
error.message?.includes('QUOTA_EXCEEDED')
|
|
664
|
+
) {
|
|
665
|
+
throw new GoogleProviderError(
|
|
666
|
+
'Google API quota exceeded',
|
|
667
|
+
'QUOTA_EXCEEDED',
|
|
668
|
+
error,
|
|
669
|
+
);
|
|
670
|
+
} else if (
|
|
671
|
+
error.message?.includes('API_KEY_INVALID') ||
|
|
672
|
+
error.message?.includes('invalid api key')
|
|
673
|
+
) {
|
|
674
|
+
throw new GoogleProviderError(
|
|
675
|
+
'Invalid Google API key',
|
|
676
|
+
'INVALID_API_KEY',
|
|
677
|
+
error,
|
|
678
|
+
);
|
|
522
679
|
} else if (error.message?.includes('MODEL_NOT_FOUND')) {
|
|
523
|
-
throw new GoogleProviderError(
|
|
680
|
+
throw new GoogleProviderError(
|
|
681
|
+
`Model ${resolvedModel} not found`,
|
|
682
|
+
'MODEL_NOT_FOUND',
|
|
683
|
+
error,
|
|
684
|
+
);
|
|
524
685
|
} else if (error.message?.includes('CONTEXT_LENGTH_EXCEEDED')) {
|
|
525
|
-
throw new GoogleProviderError(
|
|
686
|
+
throw new GoogleProviderError(
|
|
687
|
+
'Context length exceeded for model',
|
|
688
|
+
'CONTEXT_LENGTH_EXCEEDED',
|
|
689
|
+
error,
|
|
690
|
+
);
|
|
526
691
|
} else if (error.message?.includes('SAFETY')) {
|
|
527
|
-
throw new GoogleProviderError(
|
|
692
|
+
throw new GoogleProviderError(
|
|
693
|
+
'Content blocked by safety filters',
|
|
694
|
+
'SAFETY_ERROR',
|
|
695
|
+
error,
|
|
696
|
+
);
|
|
528
697
|
} else if (error.message?.includes('RATE_LIMIT_EXCEEDED')) {
|
|
529
|
-
throw new GoogleProviderError(
|
|
698
|
+
throw new GoogleProviderError(
|
|
699
|
+
'Google rate limit exceeded',
|
|
700
|
+
'RATE_LIMIT_EXCEEDED',
|
|
701
|
+
error,
|
|
702
|
+
);
|
|
530
703
|
}
|
|
531
704
|
|
|
532
705
|
// Generic error handling
|
|
533
706
|
throw new GoogleProviderError(
|
|
534
707
|
`Google API error: ${error.message || 'Unknown error'}`,
|
|
535
708
|
'API_ERROR',
|
|
536
|
-
error
|
|
709
|
+
error,
|
|
537
710
|
);
|
|
538
711
|
}
|
|
539
712
|
},
|
|
@@ -549,8 +722,19 @@ export const googleProvider = {
|
|
|
549
722
|
* @param {boolean} use_websearch - Whether web search is enabled
|
|
550
723
|
* @returns {AsyncGenerator} - Streaming generator yielding chunks
|
|
551
724
|
*/
|
|
552
|
-
async *_createStreamingGenerator(
|
|
553
|
-
|
|
725
|
+
async *_createStreamingGenerator(
|
|
726
|
+
genAI,
|
|
727
|
+
resolvedModel,
|
|
728
|
+
geminiContents,
|
|
729
|
+
generationConfig,
|
|
730
|
+
modelConfig,
|
|
731
|
+
reasoning_effort,
|
|
732
|
+
use_websearch,
|
|
733
|
+
signal,
|
|
734
|
+
) {
|
|
735
|
+
debugLog(
|
|
736
|
+
`[Google] Starting streaming for ${resolvedModel} with ${geminiContents.length} messages${use_websearch && modelConfig.supportsWebSearch ? ' (with grounding)' : ''}`,
|
|
737
|
+
);
|
|
554
738
|
|
|
555
739
|
const startTime = Date.now();
|
|
556
740
|
let totalContent = '';
|
|
@@ -571,7 +755,7 @@ export const googleProvider = {
|
|
|
571
755
|
model: resolvedModel,
|
|
572
756
|
provider: 'google',
|
|
573
757
|
thinking_mode: modelConfig.supportsThinking && reasoning_effort,
|
|
574
|
-
web_search: use_websearch && modelConfig.supportsWebSearch
|
|
758
|
+
web_search: use_websearch && modelConfig.supportsWebSearch,
|
|
575
759
|
};
|
|
576
760
|
|
|
577
761
|
// Create streaming request with retry logic and abort signal support
|
|
@@ -585,7 +769,7 @@ export const googleProvider = {
|
|
|
585
769
|
return await genAI.models.generateContentStream({
|
|
586
770
|
model: resolvedModel,
|
|
587
771
|
contents: geminiContents,
|
|
588
|
-
config: generationConfig
|
|
772
|
+
config: generationConfig,
|
|
589
773
|
});
|
|
590
774
|
});
|
|
591
775
|
|
|
@@ -594,7 +778,9 @@ export const googleProvider = {
|
|
|
594
778
|
try {
|
|
595
779
|
// Check for cancellation during stream processing
|
|
596
780
|
if (signal?.aborted) {
|
|
597
|
-
debugLog(
|
|
781
|
+
debugLog(
|
|
782
|
+
`[Google] Stream aborted during processing: ${signal.reason || 'Cancelled'}`,
|
|
783
|
+
);
|
|
598
784
|
break;
|
|
599
785
|
}
|
|
600
786
|
const content = chunk.text || '';
|
|
@@ -607,7 +793,7 @@ export const googleProvider = {
|
|
|
607
793
|
content,
|
|
608
794
|
timestamp: new Date().toISOString(),
|
|
609
795
|
model: resolvedModel,
|
|
610
|
-
provider: 'google'
|
|
796
|
+
provider: 'google',
|
|
611
797
|
};
|
|
612
798
|
}
|
|
613
799
|
|
|
@@ -615,13 +801,12 @@ export const googleProvider = {
|
|
|
615
801
|
if (chunk.candidates?.[0]?.finishReason) {
|
|
616
802
|
finishReason = chunk.candidates[0].finishReason;
|
|
617
803
|
}
|
|
618
|
-
|
|
619
804
|
} catch (chunkError) {
|
|
620
805
|
debugError('[Google] Error processing streaming chunk:', chunkError);
|
|
621
806
|
yield {
|
|
622
807
|
type: 'error',
|
|
623
808
|
error: chunkError.message,
|
|
624
|
-
timestamp: new Date().toISOString()
|
|
809
|
+
timestamp: new Date().toISOString(),
|
|
625
810
|
};
|
|
626
811
|
}
|
|
627
812
|
}
|
|
@@ -634,7 +819,7 @@ export const googleProvider = {
|
|
|
634
819
|
finalUsage = {
|
|
635
820
|
input_tokens: finalResponse.usageMetadata?.promptTokenCount || 0,
|
|
636
821
|
output_tokens: finalResponse.usageMetadata?.candidatesTokenCount || 0,
|
|
637
|
-
total_tokens: finalResponse.usageMetadata?.totalTokenCount || 0
|
|
822
|
+
total_tokens: finalResponse.usageMetadata?.totalTokenCount || 0,
|
|
638
823
|
};
|
|
639
824
|
|
|
640
825
|
// Extract grounding metadata if web search was used
|
|
@@ -646,9 +831,11 @@ export const googleProvider = {
|
|
|
646
831
|
if (!finishReason && finalResponse.candidates?.[0]?.finishReason) {
|
|
647
832
|
finishReason = finalResponse.candidates[0].finishReason;
|
|
648
833
|
}
|
|
649
|
-
|
|
650
834
|
} catch (finalResponseError) {
|
|
651
|
-
debugError(
|
|
835
|
+
debugError(
|
|
836
|
+
'[Google] Error getting final response metadata:',
|
|
837
|
+
finalResponseError,
|
|
838
|
+
);
|
|
652
839
|
}
|
|
653
840
|
|
|
654
841
|
const responseTime = Date.now() - startTime;
|
|
@@ -661,19 +848,28 @@ export const googleProvider = {
|
|
|
661
848
|
timestamp: new Date().toISOString(),
|
|
662
849
|
metadata: {
|
|
663
850
|
model: resolvedModel,
|
|
664
|
-
usage: finalUsage || {
|
|
851
|
+
usage: finalUsage || {
|
|
852
|
+
input_tokens: 0,
|
|
853
|
+
output_tokens: 0,
|
|
854
|
+
total_tokens: 0,
|
|
855
|
+
},
|
|
665
856
|
response_time_ms: responseTime,
|
|
666
857
|
finish_reason: finishReason || 'STOP',
|
|
667
|
-
reasoning_effort: modelConfig.supportsThinking
|
|
858
|
+
reasoning_effort: modelConfig.supportsThinking
|
|
859
|
+
? reasoning_effort
|
|
860
|
+
: null,
|
|
668
861
|
provider: 'google',
|
|
669
862
|
web_search_used: use_websearch && modelConfig.supportsWebSearch,
|
|
670
863
|
grounding_metadata: groundingMetadata,
|
|
671
|
-
thinking_mode_enabled: !!(
|
|
672
|
-
|
|
864
|
+
thinking_mode_enabled: !!(
|
|
865
|
+
modelConfig.supportsThinking && reasoning_effort
|
|
866
|
+
),
|
|
867
|
+
},
|
|
673
868
|
};
|
|
674
869
|
|
|
675
|
-
debugLog(
|
|
676
|
-
|
|
870
|
+
debugLog(
|
|
871
|
+
`[Google] Streaming completed in ${responseTime}ms, ${finalUsage?.total_tokens || 0} total tokens`,
|
|
872
|
+
);
|
|
677
873
|
} catch (error) {
|
|
678
874
|
debugError('[Google] Streaming error:', error);
|
|
679
875
|
|
|
@@ -682,29 +878,59 @@ export const googleProvider = {
|
|
|
682
878
|
type: 'error',
|
|
683
879
|
error: error.message || 'Unknown streaming error',
|
|
684
880
|
timestamp: new Date().toISOString(),
|
|
685
|
-
provider: 'google'
|
|
881
|
+
provider: 'google',
|
|
686
882
|
};
|
|
687
883
|
|
|
688
884
|
// Re-throw with proper error handling
|
|
689
|
-
if (
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
885
|
+
if (
|
|
886
|
+
error.message?.includes('quota') ||
|
|
887
|
+
error.message?.includes('QUOTA_EXCEEDED')
|
|
888
|
+
) {
|
|
889
|
+
throw new GoogleProviderError(
|
|
890
|
+
'Google API quota exceeded',
|
|
891
|
+
'QUOTA_EXCEEDED',
|
|
892
|
+
error,
|
|
893
|
+
);
|
|
894
|
+
} else if (
|
|
895
|
+
error.message?.includes('API_KEY_INVALID') ||
|
|
896
|
+
error.message?.includes('invalid api key')
|
|
897
|
+
) {
|
|
898
|
+
throw new GoogleProviderError(
|
|
899
|
+
'Invalid Google API key',
|
|
900
|
+
'INVALID_API_KEY',
|
|
901
|
+
error,
|
|
902
|
+
);
|
|
693
903
|
} else if (error.message?.includes('MODEL_NOT_FOUND')) {
|
|
694
|
-
throw new GoogleProviderError(
|
|
904
|
+
throw new GoogleProviderError(
|
|
905
|
+
`Model ${resolvedModel} not found`,
|
|
906
|
+
'MODEL_NOT_FOUND',
|
|
907
|
+
error,
|
|
908
|
+
);
|
|
695
909
|
} else if (error.message?.includes('CONTEXT_LENGTH_EXCEEDED')) {
|
|
696
|
-
throw new GoogleProviderError(
|
|
910
|
+
throw new GoogleProviderError(
|
|
911
|
+
'Context length exceeded for model',
|
|
912
|
+
'CONTEXT_LENGTH_EXCEEDED',
|
|
913
|
+
error,
|
|
914
|
+
);
|
|
697
915
|
} else if (error.message?.includes('SAFETY')) {
|
|
698
|
-
throw new GoogleProviderError(
|
|
916
|
+
throw new GoogleProviderError(
|
|
917
|
+
'Content blocked by safety filters',
|
|
918
|
+
'SAFETY_ERROR',
|
|
919
|
+
error,
|
|
920
|
+
);
|
|
699
921
|
} else if (error.message?.includes('RATE_LIMIT_EXCEEDED')) {
|
|
700
|
-
throw new GoogleProviderError(
|
|
922
|
+
throw new GoogleProviderError(
|
|
923
|
+
'Google rate limit exceeded',
|
|
924
|
+
'RATE_LIMIT_EXCEEDED',
|
|
925
|
+
error,
|
|
926
|
+
);
|
|
701
927
|
}
|
|
702
928
|
|
|
703
929
|
// Generic error handling
|
|
704
930
|
throw new GoogleProviderError(
|
|
705
931
|
`Google streaming error: ${error.message || 'Unknown error'}`,
|
|
706
932
|
'STREAMING_ERROR',
|
|
707
|
-
error
|
|
933
|
+
error,
|
|
708
934
|
);
|
|
709
935
|
}
|
|
710
936
|
},
|
|
@@ -716,12 +942,16 @@ export const googleProvider = {
|
|
|
716
942
|
*/
|
|
717
943
|
validateConfig(config) {
|
|
718
944
|
// Check for Vertex AI configuration
|
|
719
|
-
const hasVertexAI = !!(
|
|
720
|
-
|
|
721
|
-
|
|
945
|
+
const hasVertexAI = !!(
|
|
946
|
+
config?.providers?.googlegenaiusevertexai &&
|
|
947
|
+
config?.providers?.googlecloudproject &&
|
|
948
|
+
config?.providers?.googlecloudlocation
|
|
949
|
+
);
|
|
722
950
|
|
|
723
951
|
// Check for API key configuration
|
|
724
|
-
const hasApiKey = !!(
|
|
952
|
+
const hasApiKey = !!(
|
|
953
|
+
config?.apiKeys?.google && validateApiKey(config.apiKeys.google)
|
|
954
|
+
);
|
|
725
955
|
|
|
726
956
|
return hasVertexAI || hasApiKey;
|
|
727
957
|
},
|
|
@@ -751,5 +981,5 @@ export const googleProvider = {
|
|
|
751
981
|
getModelConfig(modelName) {
|
|
752
982
|
const resolved = resolveModelName(modelName);
|
|
753
983
|
return SUPPORTED_MODELS[resolved] || null;
|
|
754
|
-
}
|
|
984
|
+
},
|
|
755
985
|
};
|