converse-mcp-server 2.3.1 → 2.4.1
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 +337 -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,20 +516,64 @@ 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
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Add media resolution for Gemini 3.0 models
|
|
555
|
+
if (modelConfig.thinkingMode === 'level') {
|
|
556
|
+
// Default to MEDIA_RESOLUTION_HIGH for Gemini 3.0 if not specified
|
|
557
|
+
const resolution = media_resolution || 'MEDIA_RESOLUTION_HIGH';
|
|
558
|
+
const validResolutions = [
|
|
559
|
+
'MEDIA_RESOLUTION_LOW',
|
|
560
|
+
'MEDIA_RESOLUTION_MEDIUM',
|
|
561
|
+
'MEDIA_RESOLUTION_HIGH',
|
|
562
|
+
'MEDIA_RESOLUTION_UNSPECIFIED',
|
|
563
|
+
];
|
|
564
|
+
if (validResolutions.includes(resolution)) {
|
|
565
|
+
generationConfig.mediaResolution = resolution;
|
|
566
|
+
}
|
|
567
|
+
} else if (media_resolution) {
|
|
568
|
+
// For other models, only add if explicitly specified
|
|
569
|
+
const validResolutions = [
|
|
570
|
+
'MEDIA_RESOLUTION_LOW',
|
|
571
|
+
'MEDIA_RESOLUTION_MEDIUM',
|
|
572
|
+
'MEDIA_RESOLUTION_HIGH',
|
|
573
|
+
'MEDIA_RESOLUTION_UNSPECIFIED',
|
|
574
|
+
];
|
|
575
|
+
if (validResolutions.includes(media_resolution)) {
|
|
576
|
+
generationConfig.mediaResolution = media_resolution;
|
|
437
577
|
}
|
|
438
578
|
}
|
|
439
579
|
|
|
@@ -446,14 +586,27 @@ export const googleProvider = {
|
|
|
446
586
|
if (stream) {
|
|
447
587
|
// Check if model supports streaming
|
|
448
588
|
if (modelConfig.supportsStreaming === false) {
|
|
449
|
-
debugLog(
|
|
589
|
+
debugLog(
|
|
590
|
+
`[Google] Model ${resolvedModel} doesn't support streaming, falling back to non-streaming mode`,
|
|
591
|
+
);
|
|
450
592
|
} else {
|
|
451
|
-
return this._createStreamingGenerator(
|
|
593
|
+
return this._createStreamingGenerator(
|
|
594
|
+
genAI,
|
|
595
|
+
resolvedModel,
|
|
596
|
+
geminiContents,
|
|
597
|
+
generationConfig,
|
|
598
|
+
modelConfig,
|
|
599
|
+
reasoning_effort,
|
|
600
|
+
use_websearch,
|
|
601
|
+
signal,
|
|
602
|
+
);
|
|
452
603
|
}
|
|
453
604
|
}
|
|
454
605
|
|
|
455
606
|
try {
|
|
456
|
-
debugLog(
|
|
607
|
+
debugLog(
|
|
608
|
+
`[Google] Calling ${resolvedModel} with ${messages.length} messages${use_websearch && modelConfig.supportsWebSearch ? ' (with grounding)' : ''}`,
|
|
609
|
+
);
|
|
457
610
|
|
|
458
611
|
// Check if already aborted before making request
|
|
459
612
|
if (signal?.aborted) {
|
|
@@ -471,7 +624,7 @@ export const googleProvider = {
|
|
|
471
624
|
return await genAI.models.generateContent({
|
|
472
625
|
model: resolvedModel,
|
|
473
626
|
contents: geminiContents,
|
|
474
|
-
config: generationConfig
|
|
627
|
+
config: generationConfig,
|
|
475
628
|
});
|
|
476
629
|
});
|
|
477
630
|
|
|
@@ -481,14 +634,17 @@ export const googleProvider = {
|
|
|
481
634
|
// Extract response data using the new SDK format
|
|
482
635
|
const content = response.text;
|
|
483
636
|
if (!content) {
|
|
484
|
-
throw new GoogleProviderError(
|
|
637
|
+
throw new GoogleProviderError(
|
|
638
|
+
'No text content received from Google',
|
|
639
|
+
'NO_RESPONSE_CONTENT',
|
|
640
|
+
);
|
|
485
641
|
}
|
|
486
642
|
|
|
487
643
|
// Extract usage information from the new SDK format
|
|
488
644
|
const usage = {
|
|
489
645
|
input_tokens: response.usageMetadata?.promptTokenCount || 0,
|
|
490
646
|
output_tokens: response.usageMetadata?.candidatesTokenCount || 0,
|
|
491
|
-
total_tokens: response.usageMetadata?.totalTokenCount || 0
|
|
647
|
+
total_tokens: response.usageMetadata?.totalTokenCount || 0,
|
|
492
648
|
};
|
|
493
649
|
|
|
494
650
|
// Extract finish reason from candidates
|
|
@@ -504,36 +660,67 @@ export const googleProvider = {
|
|
|
504
660
|
usage,
|
|
505
661
|
response_time_ms: responseTime,
|
|
506
662
|
finish_reason: finishReason,
|
|
507
|
-
reasoning_effort: modelConfig.supportsThinking
|
|
663
|
+
reasoning_effort: modelConfig.supportsThinking
|
|
664
|
+
? reasoning_effort
|
|
665
|
+
: null,
|
|
508
666
|
provider: 'google',
|
|
509
667
|
web_search_used: use_websearch && modelConfig.supportsWebSearch,
|
|
510
|
-
grounding_metadata: response.groundingMetadata || null
|
|
511
|
-
}
|
|
668
|
+
grounding_metadata: response.groundingMetadata || null,
|
|
669
|
+
},
|
|
512
670
|
};
|
|
513
|
-
|
|
514
671
|
} catch (error) {
|
|
515
672
|
debugError('[Google] Error during API call:', error);
|
|
516
673
|
|
|
517
674
|
// Handle specific Google errors
|
|
518
|
-
if (
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
675
|
+
if (
|
|
676
|
+
error.message?.includes('quota') ||
|
|
677
|
+
error.message?.includes('QUOTA_EXCEEDED')
|
|
678
|
+
) {
|
|
679
|
+
throw new GoogleProviderError(
|
|
680
|
+
'Google API quota exceeded',
|
|
681
|
+
'QUOTA_EXCEEDED',
|
|
682
|
+
error,
|
|
683
|
+
);
|
|
684
|
+
} else if (
|
|
685
|
+
error.message?.includes('API_KEY_INVALID') ||
|
|
686
|
+
error.message?.includes('invalid api key')
|
|
687
|
+
) {
|
|
688
|
+
throw new GoogleProviderError(
|
|
689
|
+
'Invalid Google API key',
|
|
690
|
+
'INVALID_API_KEY',
|
|
691
|
+
error,
|
|
692
|
+
);
|
|
522
693
|
} else if (error.message?.includes('MODEL_NOT_FOUND')) {
|
|
523
|
-
throw new GoogleProviderError(
|
|
694
|
+
throw new GoogleProviderError(
|
|
695
|
+
`Model ${resolvedModel} not found`,
|
|
696
|
+
'MODEL_NOT_FOUND',
|
|
697
|
+
error,
|
|
698
|
+
);
|
|
524
699
|
} else if (error.message?.includes('CONTEXT_LENGTH_EXCEEDED')) {
|
|
525
|
-
throw new GoogleProviderError(
|
|
700
|
+
throw new GoogleProviderError(
|
|
701
|
+
'Context length exceeded for model',
|
|
702
|
+
'CONTEXT_LENGTH_EXCEEDED',
|
|
703
|
+
error,
|
|
704
|
+
);
|
|
526
705
|
} else if (error.message?.includes('SAFETY')) {
|
|
527
|
-
throw new GoogleProviderError(
|
|
706
|
+
throw new GoogleProviderError(
|
|
707
|
+
'Content blocked by safety filters',
|
|
708
|
+
'SAFETY_ERROR',
|
|
709
|
+
error,
|
|
710
|
+
);
|
|
528
711
|
} else if (error.message?.includes('RATE_LIMIT_EXCEEDED')) {
|
|
529
|
-
throw new GoogleProviderError(
|
|
712
|
+
throw new GoogleProviderError(
|
|
713
|
+
'Google rate limit exceeded',
|
|
714
|
+
'RATE_LIMIT_EXCEEDED',
|
|
715
|
+
error,
|
|
716
|
+
);
|
|
530
717
|
}
|
|
531
718
|
|
|
532
719
|
// Generic error handling
|
|
533
720
|
throw new GoogleProviderError(
|
|
534
721
|
`Google API error: ${error.message || 'Unknown error'}`,
|
|
535
722
|
'API_ERROR',
|
|
536
|
-
error
|
|
723
|
+
error,
|
|
537
724
|
);
|
|
538
725
|
}
|
|
539
726
|
},
|
|
@@ -549,8 +736,19 @@ export const googleProvider = {
|
|
|
549
736
|
* @param {boolean} use_websearch - Whether web search is enabled
|
|
550
737
|
* @returns {AsyncGenerator} - Streaming generator yielding chunks
|
|
551
738
|
*/
|
|
552
|
-
async *_createStreamingGenerator(
|
|
553
|
-
|
|
739
|
+
async *_createStreamingGenerator(
|
|
740
|
+
genAI,
|
|
741
|
+
resolvedModel,
|
|
742
|
+
geminiContents,
|
|
743
|
+
generationConfig,
|
|
744
|
+
modelConfig,
|
|
745
|
+
reasoning_effort,
|
|
746
|
+
use_websearch,
|
|
747
|
+
signal,
|
|
748
|
+
) {
|
|
749
|
+
debugLog(
|
|
750
|
+
`[Google] Starting streaming for ${resolvedModel} with ${geminiContents.length} messages${use_websearch && modelConfig.supportsWebSearch ? ' (with grounding)' : ''}`,
|
|
751
|
+
);
|
|
554
752
|
|
|
555
753
|
const startTime = Date.now();
|
|
556
754
|
let totalContent = '';
|
|
@@ -571,7 +769,7 @@ export const googleProvider = {
|
|
|
571
769
|
model: resolvedModel,
|
|
572
770
|
provider: 'google',
|
|
573
771
|
thinking_mode: modelConfig.supportsThinking && reasoning_effort,
|
|
574
|
-
web_search: use_websearch && modelConfig.supportsWebSearch
|
|
772
|
+
web_search: use_websearch && modelConfig.supportsWebSearch,
|
|
575
773
|
};
|
|
576
774
|
|
|
577
775
|
// Create streaming request with retry logic and abort signal support
|
|
@@ -585,7 +783,7 @@ export const googleProvider = {
|
|
|
585
783
|
return await genAI.models.generateContentStream({
|
|
586
784
|
model: resolvedModel,
|
|
587
785
|
contents: geminiContents,
|
|
588
|
-
config: generationConfig
|
|
786
|
+
config: generationConfig,
|
|
589
787
|
});
|
|
590
788
|
});
|
|
591
789
|
|
|
@@ -594,7 +792,9 @@ export const googleProvider = {
|
|
|
594
792
|
try {
|
|
595
793
|
// Check for cancellation during stream processing
|
|
596
794
|
if (signal?.aborted) {
|
|
597
|
-
debugLog(
|
|
795
|
+
debugLog(
|
|
796
|
+
`[Google] Stream aborted during processing: ${signal.reason || 'Cancelled'}`,
|
|
797
|
+
);
|
|
598
798
|
break;
|
|
599
799
|
}
|
|
600
800
|
const content = chunk.text || '';
|
|
@@ -607,7 +807,7 @@ export const googleProvider = {
|
|
|
607
807
|
content,
|
|
608
808
|
timestamp: new Date().toISOString(),
|
|
609
809
|
model: resolvedModel,
|
|
610
|
-
provider: 'google'
|
|
810
|
+
provider: 'google',
|
|
611
811
|
};
|
|
612
812
|
}
|
|
613
813
|
|
|
@@ -615,13 +815,12 @@ export const googleProvider = {
|
|
|
615
815
|
if (chunk.candidates?.[0]?.finishReason) {
|
|
616
816
|
finishReason = chunk.candidates[0].finishReason;
|
|
617
817
|
}
|
|
618
|
-
|
|
619
818
|
} catch (chunkError) {
|
|
620
819
|
debugError('[Google] Error processing streaming chunk:', chunkError);
|
|
621
820
|
yield {
|
|
622
821
|
type: 'error',
|
|
623
822
|
error: chunkError.message,
|
|
624
|
-
timestamp: new Date().toISOString()
|
|
823
|
+
timestamp: new Date().toISOString(),
|
|
625
824
|
};
|
|
626
825
|
}
|
|
627
826
|
}
|
|
@@ -634,7 +833,7 @@ export const googleProvider = {
|
|
|
634
833
|
finalUsage = {
|
|
635
834
|
input_tokens: finalResponse.usageMetadata?.promptTokenCount || 0,
|
|
636
835
|
output_tokens: finalResponse.usageMetadata?.candidatesTokenCount || 0,
|
|
637
|
-
total_tokens: finalResponse.usageMetadata?.totalTokenCount || 0
|
|
836
|
+
total_tokens: finalResponse.usageMetadata?.totalTokenCount || 0,
|
|
638
837
|
};
|
|
639
838
|
|
|
640
839
|
// Extract grounding metadata if web search was used
|
|
@@ -646,9 +845,11 @@ export const googleProvider = {
|
|
|
646
845
|
if (!finishReason && finalResponse.candidates?.[0]?.finishReason) {
|
|
647
846
|
finishReason = finalResponse.candidates[0].finishReason;
|
|
648
847
|
}
|
|
649
|
-
|
|
650
848
|
} catch (finalResponseError) {
|
|
651
|
-
debugError(
|
|
849
|
+
debugError(
|
|
850
|
+
'[Google] Error getting final response metadata:',
|
|
851
|
+
finalResponseError,
|
|
852
|
+
);
|
|
652
853
|
}
|
|
653
854
|
|
|
654
855
|
const responseTime = Date.now() - startTime;
|
|
@@ -661,19 +862,28 @@ export const googleProvider = {
|
|
|
661
862
|
timestamp: new Date().toISOString(),
|
|
662
863
|
metadata: {
|
|
663
864
|
model: resolvedModel,
|
|
664
|
-
usage: finalUsage || {
|
|
865
|
+
usage: finalUsage || {
|
|
866
|
+
input_tokens: 0,
|
|
867
|
+
output_tokens: 0,
|
|
868
|
+
total_tokens: 0,
|
|
869
|
+
},
|
|
665
870
|
response_time_ms: responseTime,
|
|
666
871
|
finish_reason: finishReason || 'STOP',
|
|
667
|
-
reasoning_effort: modelConfig.supportsThinking
|
|
872
|
+
reasoning_effort: modelConfig.supportsThinking
|
|
873
|
+
? reasoning_effort
|
|
874
|
+
: null,
|
|
668
875
|
provider: 'google',
|
|
669
876
|
web_search_used: use_websearch && modelConfig.supportsWebSearch,
|
|
670
877
|
grounding_metadata: groundingMetadata,
|
|
671
|
-
thinking_mode_enabled: !!(
|
|
672
|
-
|
|
878
|
+
thinking_mode_enabled: !!(
|
|
879
|
+
modelConfig.supportsThinking && reasoning_effort
|
|
880
|
+
),
|
|
881
|
+
},
|
|
673
882
|
};
|
|
674
883
|
|
|
675
|
-
debugLog(
|
|
676
|
-
|
|
884
|
+
debugLog(
|
|
885
|
+
`[Google] Streaming completed in ${responseTime}ms, ${finalUsage?.total_tokens || 0} total tokens`,
|
|
886
|
+
);
|
|
677
887
|
} catch (error) {
|
|
678
888
|
debugError('[Google] Streaming error:', error);
|
|
679
889
|
|
|
@@ -682,29 +892,59 @@ export const googleProvider = {
|
|
|
682
892
|
type: 'error',
|
|
683
893
|
error: error.message || 'Unknown streaming error',
|
|
684
894
|
timestamp: new Date().toISOString(),
|
|
685
|
-
provider: 'google'
|
|
895
|
+
provider: 'google',
|
|
686
896
|
};
|
|
687
897
|
|
|
688
898
|
// Re-throw with proper error handling
|
|
689
|
-
if (
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
899
|
+
if (
|
|
900
|
+
error.message?.includes('quota') ||
|
|
901
|
+
error.message?.includes('QUOTA_EXCEEDED')
|
|
902
|
+
) {
|
|
903
|
+
throw new GoogleProviderError(
|
|
904
|
+
'Google API quota exceeded',
|
|
905
|
+
'QUOTA_EXCEEDED',
|
|
906
|
+
error,
|
|
907
|
+
);
|
|
908
|
+
} else if (
|
|
909
|
+
error.message?.includes('API_KEY_INVALID') ||
|
|
910
|
+
error.message?.includes('invalid api key')
|
|
911
|
+
) {
|
|
912
|
+
throw new GoogleProviderError(
|
|
913
|
+
'Invalid Google API key',
|
|
914
|
+
'INVALID_API_KEY',
|
|
915
|
+
error,
|
|
916
|
+
);
|
|
693
917
|
} else if (error.message?.includes('MODEL_NOT_FOUND')) {
|
|
694
|
-
throw new GoogleProviderError(
|
|
918
|
+
throw new GoogleProviderError(
|
|
919
|
+
`Model ${resolvedModel} not found`,
|
|
920
|
+
'MODEL_NOT_FOUND',
|
|
921
|
+
error,
|
|
922
|
+
);
|
|
695
923
|
} else if (error.message?.includes('CONTEXT_LENGTH_EXCEEDED')) {
|
|
696
|
-
throw new GoogleProviderError(
|
|
924
|
+
throw new GoogleProviderError(
|
|
925
|
+
'Context length exceeded for model',
|
|
926
|
+
'CONTEXT_LENGTH_EXCEEDED',
|
|
927
|
+
error,
|
|
928
|
+
);
|
|
697
929
|
} else if (error.message?.includes('SAFETY')) {
|
|
698
|
-
throw new GoogleProviderError(
|
|
930
|
+
throw new GoogleProviderError(
|
|
931
|
+
'Content blocked by safety filters',
|
|
932
|
+
'SAFETY_ERROR',
|
|
933
|
+
error,
|
|
934
|
+
);
|
|
699
935
|
} else if (error.message?.includes('RATE_LIMIT_EXCEEDED')) {
|
|
700
|
-
throw new GoogleProviderError(
|
|
936
|
+
throw new GoogleProviderError(
|
|
937
|
+
'Google rate limit exceeded',
|
|
938
|
+
'RATE_LIMIT_EXCEEDED',
|
|
939
|
+
error,
|
|
940
|
+
);
|
|
701
941
|
}
|
|
702
942
|
|
|
703
943
|
// Generic error handling
|
|
704
944
|
throw new GoogleProviderError(
|
|
705
945
|
`Google streaming error: ${error.message || 'Unknown error'}`,
|
|
706
946
|
'STREAMING_ERROR',
|
|
707
|
-
error
|
|
947
|
+
error,
|
|
708
948
|
);
|
|
709
949
|
}
|
|
710
950
|
},
|
|
@@ -716,12 +956,16 @@ export const googleProvider = {
|
|
|
716
956
|
*/
|
|
717
957
|
validateConfig(config) {
|
|
718
958
|
// Check for Vertex AI configuration
|
|
719
|
-
const hasVertexAI = !!(
|
|
720
|
-
|
|
721
|
-
|
|
959
|
+
const hasVertexAI = !!(
|
|
960
|
+
config?.providers?.googlegenaiusevertexai &&
|
|
961
|
+
config?.providers?.googlecloudproject &&
|
|
962
|
+
config?.providers?.googlecloudlocation
|
|
963
|
+
);
|
|
722
964
|
|
|
723
965
|
// Check for API key configuration
|
|
724
|
-
const hasApiKey = !!(
|
|
966
|
+
const hasApiKey = !!(
|
|
967
|
+
config?.apiKeys?.google && validateApiKey(config.apiKeys.google)
|
|
968
|
+
);
|
|
725
969
|
|
|
726
970
|
return hasVertexAI || hasApiKey;
|
|
727
971
|
},
|
|
@@ -751,5 +995,5 @@ export const googleProvider = {
|
|
|
751
995
|
getModelConfig(modelName) {
|
|
752
996
|
const resolved = resolveModelName(modelName);
|
|
753
997
|
return SUPPORTED_MODELS[resolved] || null;
|
|
754
|
-
}
|
|
998
|
+
},
|
|
755
999
|
};
|