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.
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 +323 -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,23 +516,53 @@ 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
+ }
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(`[Google] Model ${resolvedModel} doesn't support streaming, falling back to non-streaming mode`);
575
+ debugLog(
576
+ `[Google] Model ${resolvedModel} doesn't support streaming, falling back to non-streaming mode`,
577
+ );
450
578
  } else {
451
- return this._createStreamingGenerator(genAI, resolvedModel, geminiContents, generationConfig, modelConfig, reasoning_effort, use_websearch, signal);
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(`[Google] Calling ${resolvedModel} with ${messages.length} messages${use_websearch && modelConfig.supportsWebSearch ? ' (with grounding)' : ''}`);
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('No text content received from Google', 'NO_RESPONSE_CONTENT');
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 ? reasoning_effort : null,
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 (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);
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(`Model ${resolvedModel} not found`, 'MODEL_NOT_FOUND', error);
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('Context length exceeded for model', 'CONTEXT_LENGTH_EXCEEDED', error);
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('Content blocked by safety filters', 'SAFETY_ERROR', error);
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('Google rate limit exceeded', 'RATE_LIMIT_EXCEEDED', error);
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(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)' : ''}`);
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(`[Google] Stream aborted during processing: ${signal.reason || 'Cancelled'}`);
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('[Google] Error getting final response metadata:', finalResponseError);
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 || { input_tokens: 0, output_tokens: 0, total_tokens: 0 },
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 ? reasoning_effort : null,
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: !!(modelConfig.supportsThinking && reasoning_effort)
672
- }
864
+ thinking_mode_enabled: !!(
865
+ modelConfig.supportsThinking && reasoning_effort
866
+ ),
867
+ },
673
868
  };
674
869
 
675
- debugLog(`[Google] Streaming completed in ${responseTime}ms, ${finalUsage?.total_tokens || 0} total tokens`);
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 (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);
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(`Model ${resolvedModel} not found`, 'MODEL_NOT_FOUND', error);
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('Context length exceeded for model', 'CONTEXT_LENGTH_EXCEEDED', error);
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('Content blocked by safety filters', 'SAFETY_ERROR', error);
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('Google rate limit exceeded', 'RATE_LIMIT_EXCEEDED', error);
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 = !!(config?.providers?.googlegenaiusevertexai &&
720
- config?.providers?.googlecloudproject &&
721
- config?.providers?.googlecloudlocation);
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 = !!(config?.apiKeys?.google && validateApiKey(config.apiKeys.google));
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
  };