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.
Files changed (42) hide show
  1. package/README.md +771 -738
  2. package/docs/API.md +10 -1
  3. package/docs/PROVIDERS.md +8 -4
  4. package/package.json +12 -12
  5. package/src/async/asyncJobStore.js +82 -52
  6. package/src/async/eventBus.js +25 -20
  7. package/src/async/fileCache.js +121 -40
  8. package/src/async/jobRunner.js +65 -39
  9. package/src/async/providerStreamNormalizer.js +203 -117
  10. package/src/config.js +374 -102
  11. package/src/continuationStore.js +32 -24
  12. package/src/index.js +45 -25
  13. package/src/prompts/helpPrompt.js +328 -305
  14. package/src/providers/anthropic.js +303 -119
  15. package/src/providers/codex.js +103 -45
  16. package/src/providers/deepseek.js +24 -8
  17. package/src/providers/google.js +337 -93
  18. package/src/providers/index.js +1 -1
  19. package/src/providers/interface.js +16 -11
  20. package/src/providers/mistral.js +179 -69
  21. package/src/providers/openai-compatible.js +231 -94
  22. package/src/providers/openai.js +1094 -914
  23. package/src/providers/openrouter-endpoints-client.js +220 -216
  24. package/src/providers/openrouter.js +426 -381
  25. package/src/providers/xai.js +153 -56
  26. package/src/resources/helpResource.js +70 -67
  27. package/src/router.js +95 -67
  28. package/src/services/summarizationService.js +51 -24
  29. package/src/systemPrompts.js +89 -89
  30. package/src/tools/cancelJob.js +31 -19
  31. package/src/tools/chat.js +997 -883
  32. package/src/tools/checkStatus.js +86 -65
  33. package/src/tools/consensus.js +400 -234
  34. package/src/tools/index.js +39 -16
  35. package/src/transport/httpTransport.js +82 -55
  36. package/src/utils/contextProcessor.js +54 -37
  37. package/src/utils/errorHandler.js +95 -45
  38. package/src/utils/fileValidator.js +107 -98
  39. package/src/utils/formatStatus.js +122 -64
  40. package/src/utils/logger.js +459 -449
  41. package/src/utils/pathUtils.js +2 -2
  42. package/src/utils/tokenLimiter.js +216 -216
@@ -22,8 +22,15 @@ const SUPPORTED_MODELS = {
22
22
  supportsWebSearch: true,
23
23
  maxThinkingTokens: 0,
24
24
  timeout: 300000,
25
- description: 'Gemini 2.0 Flash (1M context) - Latest fast model, supports audio/video input and grounding',
26
- aliases: ['flash-2.0', 'flash2', 'flash 2.0', 'gemini flash 2.0', 'gemini-2.0-flash-latest']
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: 'Gemini 2.0 Flash Lite (1M context) - Lightweight fast model, text-only with grounding',
41
- aliases: ['flashlite', 'flash-lite', 'flash lite', 'flash-lite-2.0', 'gemini flash lite', 'gemini-2.0-flash-lite-latest']
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: 'Ultra-fast (1M context) - Quick analysis, simple queries, rapid iterations with grounding',
56
- aliases: ['flash', 'flash2.5', 'gemini-flash', 'gemini-flash-2.5', 'flash 2.5', 'gemini flash 2.5', 'gemini-2.5-flash', 'gemini-2.5-flash-preview-09-2025', 'gemini-2.5-flash-latest']
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: 'Lightweight fast model (1M context) - Efficient quick responses with grounding',
71
- aliases: ['flashlite2.5', 'flash-lite', 'flash lite', 'gemini-flash-lite', 'gemini flash lite', 'gemini-2.5-flash-lite-preview-09-2025', 'gemini-2.5-flash-lite-latest']
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: 'Deep reasoning + thinking mode (1M context) - Complex problems, architecture, deep analysis',
86
- aliases: ['pro', 'gemini pro', 'gemini-pro', 'gemini', 'pro 2.5', 'gemini pro 2.5', 'gemini-2.5-pro-latest']
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('Messages must be an array', 'INVALID_MESSAGES');
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(`Message at index ${index} must be an object`, 'INVALID_MESSAGE');
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(`Invalid role "${role}" at message index ${index}`, 'INVALID_ROLE');
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(`Message content is required at index ${index}`, 'MISSING_CONTENT');
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(`[Google] Converting image: ${item.source.media_type}, data length: ${item.source.data.length}`);
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 ? `${systemPrompt}\n\n${textContent}` : textContent;
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 ? `${systemPrompt}\n\n${content}` : content;
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 (nonRetryableIndicators.some(indicator => errorStr.includes(indicator))) {
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(`[Google] Retrying after ${delay}ms (attempt ${attempt + 1}/${maxRetries}):`, error.message);
330
- await new Promise(resolve => setTimeout(resolve, delay));
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(`[Google] Using Vertex AI: project=${vertexProject}, location=${vertexLocation}, apiVersion=${apiVersion}`);
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('Invalid Google API key format', 'INVALID_API_KEY');
489
+ throw new GoogleProviderError(
490
+ 'Invalid Google API key format',
491
+ 'INVALID_API_KEY',
492
+ );
399
493
  }
400
494
 
401
- debugLog(`[Google] Using Gemini Developer API with configured API key, apiVersion=${apiVersion}`);
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 (modelConfig.supportsTemperature !== false && temperature !== undefined) {
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(maxTokens, modelConfig.maxOutputTokens || 65536);
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
- const thinkingBudget = calculateThinkingBudget(modelConfig, reasoning_effort);
435
- if (thinkingBudget > 0) {
436
- generationConfig.thinkingConfig = { thinkingBudget };
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(`[Google] Model ${resolvedModel} doesn't support streaming, falling back to non-streaming mode`);
589
+ debugLog(
590
+ `[Google] Model ${resolvedModel} doesn't support streaming, falling back to non-streaming mode`,
591
+ );
450
592
  } else {
451
- return this._createStreamingGenerator(genAI, resolvedModel, geminiContents, generationConfig, modelConfig, reasoning_effort, use_websearch, signal);
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(`[Google] Calling ${resolvedModel} with ${messages.length} messages${use_websearch && modelConfig.supportsWebSearch ? ' (with grounding)' : ''}`);
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('No text content received from Google', 'NO_RESPONSE_CONTENT');
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 ? reasoning_effort : null,
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 (error.message?.includes('quota') || error.message?.includes('QUOTA_EXCEEDED')) {
519
- throw new GoogleProviderError('Google API quota exceeded', 'QUOTA_EXCEEDED', error);
520
- } else if (error.message?.includes('API_KEY_INVALID') || error.message?.includes('invalid api key')) {
521
- throw new GoogleProviderError('Invalid Google API key', 'INVALID_API_KEY', error);
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(`Model ${resolvedModel} not found`, 'MODEL_NOT_FOUND', error);
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('Context length exceeded for model', 'CONTEXT_LENGTH_EXCEEDED', error);
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('Content blocked by safety filters', 'SAFETY_ERROR', error);
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('Google rate limit exceeded', 'RATE_LIMIT_EXCEEDED', error);
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(genAI, resolvedModel, geminiContents, generationConfig, modelConfig, reasoning_effort, use_websearch, signal) {
553
- debugLog(`[Google] Starting streaming for ${resolvedModel} with ${geminiContents.length} messages${use_websearch && modelConfig.supportsWebSearch ? ' (with grounding)' : ''}`);
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(`[Google] Stream aborted during processing: ${signal.reason || 'Cancelled'}`);
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('[Google] Error getting final response metadata:', finalResponseError);
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 || { input_tokens: 0, output_tokens: 0, total_tokens: 0 },
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 ? reasoning_effort : null,
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: !!(modelConfig.supportsThinking && reasoning_effort)
672
- }
878
+ thinking_mode_enabled: !!(
879
+ modelConfig.supportsThinking && reasoning_effort
880
+ ),
881
+ },
673
882
  };
674
883
 
675
- debugLog(`[Google] Streaming completed in ${responseTime}ms, ${finalUsage?.total_tokens || 0} total tokens`);
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 (error.message?.includes('quota') || error.message?.includes('QUOTA_EXCEEDED')) {
690
- throw new GoogleProviderError('Google API quota exceeded', 'QUOTA_EXCEEDED', error);
691
- } else if (error.message?.includes('API_KEY_INVALID') || error.message?.includes('invalid api key')) {
692
- throw new GoogleProviderError('Invalid Google API key', 'INVALID_API_KEY', error);
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(`Model ${resolvedModel} not found`, 'MODEL_NOT_FOUND', error);
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('Context length exceeded for model', 'CONTEXT_LENGTH_EXCEEDED', error);
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('Content blocked by safety filters', 'SAFETY_ERROR', error);
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('Google rate limit exceeded', 'RATE_LIMIT_EXCEEDED', error);
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 = !!(config?.providers?.googlegenaiusevertexai &&
720
- config?.providers?.googlecloudproject &&
721
- config?.providers?.googlecloudlocation);
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 = !!(config?.apiKeys?.google && validateApiKey(config.apiKeys.google));
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
  };