@juspay/neurolink 1.5.3 → 1.6.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/CHANGELOG.md +54 -0
  2. package/README.md +17 -7
  3. package/dist/cli/commands/config.d.ts +70 -3
  4. package/dist/cli/commands/config.js +75 -3
  5. package/dist/cli/commands/ollama.d.ts +8 -0
  6. package/dist/cli/commands/ollama.js +323 -0
  7. package/dist/cli/index.js +11 -13
  8. package/dist/core/factory.js +17 -2
  9. package/dist/core/types.d.ts +4 -1
  10. package/dist/core/types.js +3 -0
  11. package/dist/lib/core/factory.js +17 -2
  12. package/dist/lib/core/types.d.ts +4 -1
  13. package/dist/lib/core/types.js +3 -0
  14. package/dist/lib/mcp/servers/ai-providers/ai-analysis-tools.js +4 -4
  15. package/dist/lib/mcp/servers/ai-providers/ai-core-server.js +13 -9
  16. package/dist/lib/mcp/servers/ai-providers/ai-workflow-tools.js +250 -152
  17. package/dist/lib/neurolink.d.ts +2 -2
  18. package/dist/lib/neurolink.js +18 -8
  19. package/dist/lib/providers/huggingFace.d.ts +31 -0
  20. package/dist/lib/providers/huggingFace.js +355 -0
  21. package/dist/lib/providers/index.d.ts +6 -0
  22. package/dist/lib/providers/index.js +7 -1
  23. package/dist/lib/providers/mistralAI.d.ts +32 -0
  24. package/dist/lib/providers/mistralAI.js +217 -0
  25. package/dist/lib/providers/ollama.d.ts +51 -0
  26. package/dist/lib/providers/ollama.js +493 -0
  27. package/dist/lib/utils/providerUtils.js +17 -2
  28. package/dist/mcp/servers/ai-providers/ai-analysis-tools.js +4 -4
  29. package/dist/mcp/servers/ai-providers/ai-core-server.js +13 -9
  30. package/dist/mcp/servers/ai-providers/ai-workflow-tools.js +248 -152
  31. package/dist/neurolink.d.ts +2 -2
  32. package/dist/neurolink.js +18 -8
  33. package/dist/providers/huggingFace.d.ts +31 -0
  34. package/dist/providers/huggingFace.js +355 -0
  35. package/dist/providers/index.d.ts +6 -0
  36. package/dist/providers/index.js +7 -1
  37. package/dist/providers/mistralAI.d.ts +32 -0
  38. package/dist/providers/mistralAI.js +217 -0
  39. package/dist/providers/ollama.d.ts +51 -0
  40. package/dist/providers/ollama.js +493 -0
  41. package/dist/utils/providerUtils.js +17 -2
  42. package/package.json +161 -151
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Ollama Provider for NeuroLink
3
+ *
4
+ * Local AI model deployment and management using Ollama.
5
+ * Provides offline AI capabilities with local model hosting.
6
+ *
7
+ * Features:
8
+ * - Local model deployment (privacy-first)
9
+ * - Model management (download, list, remove)
10
+ * - Health checking and service validation
11
+ * - Streaming and non-streaming text generation
12
+ */
13
+ import type { AIProvider, TextGenerationOptions, StreamTextOptions } from '../core/types.js';
14
+ import type { GenerateTextResult, StreamTextResult, ToolSet } from 'ai';
15
+ import type { ZodType, ZodTypeDef } from 'zod';
16
+ import type { Schema } from 'ai';
17
+ export declare class Ollama implements AIProvider {
18
+ private baseUrl;
19
+ private modelName;
20
+ private timeout;
21
+ constructor(modelName?: string);
22
+ /**
23
+ * Gets the appropriate model instance
24
+ * @private
25
+ */
26
+ private getModel;
27
+ /**
28
+ * Health check - verify Ollama service is running and accessible
29
+ */
30
+ checkHealth(): Promise<boolean>;
31
+ /**
32
+ * List available models on the Ollama instance
33
+ */
34
+ listModels(): Promise<string[]>;
35
+ /**
36
+ * Check if a specific model is available
37
+ */
38
+ isModelAvailable(modelName: string): Promise<boolean>;
39
+ /**
40
+ * Pull/download a model to the local Ollama instance
41
+ */
42
+ pullModel(modelName: string): Promise<void>;
43
+ /**
44
+ * Generate text using Ollama local models
45
+ */
46
+ generateText(optionsOrPrompt: TextGenerationOptions | string, analysisSchema?: ZodType<unknown, ZodTypeDef, unknown> | Schema<unknown>): Promise<GenerateTextResult<ToolSet, unknown> | null>;
47
+ /**
48
+ * Generate streaming text using Ollama local models
49
+ */
50
+ streamText(optionsOrPrompt: StreamTextOptions | string, analysisSchema?: ZodType<unknown, ZodTypeDef, unknown> | Schema<unknown>): Promise<StreamTextResult<ToolSet, unknown> | null>;
51
+ }
@@ -0,0 +1,493 @@
1
+ /**
2
+ * Ollama Provider for NeuroLink
3
+ *
4
+ * Local AI model deployment and management using Ollama.
5
+ * Provides offline AI capabilities with local model hosting.
6
+ *
7
+ * Features:
8
+ * - Local model deployment (privacy-first)
9
+ * - Model management (download, list, remove)
10
+ * - Health checking and service validation
11
+ * - Streaming and non-streaming text generation
12
+ */
13
+ import { streamText, generateText, Output } from 'ai';
14
+ import { logger } from '../utils/logger.js';
15
+ // Default system context
16
+ const DEFAULT_SYSTEM_CONTEXT = {
17
+ systemPrompt: 'You are a helpful AI assistant.'
18
+ };
19
+ // Custom LanguageModelV1 implementation for Ollama
20
+ class OllamaLanguageModel {
21
+ specificationVersion = 'v1';
22
+ provider = 'ollama';
23
+ modelId;
24
+ maxTokens;
25
+ supportsStreaming = true;
26
+ defaultObjectGenerationMode = 'json';
27
+ baseUrl;
28
+ timeout;
29
+ constructor(modelId, baseUrl, timeout) {
30
+ this.modelId = modelId;
31
+ this.baseUrl = baseUrl;
32
+ this.timeout = timeout;
33
+ }
34
+ estimateTokens(text) {
35
+ return Math.ceil(text.length / 4); // Rough estimation: 4 characters per token
36
+ }
37
+ convertMessagesToPrompt(messages) {
38
+ return messages
39
+ .map(msg => {
40
+ if (typeof msg.content === 'string') {
41
+ return `${msg.role}: ${msg.content}`;
42
+ }
43
+ else if (Array.isArray(msg.content)) {
44
+ // Handle multi-part content (text, images, etc.)
45
+ return `${msg.role}: ${msg.content
46
+ .filter((part) => part.type === 'text')
47
+ .map((part) => part.text)
48
+ .join(' ')}`;
49
+ }
50
+ return '';
51
+ })
52
+ .join('\n');
53
+ }
54
+ async checkHealth() {
55
+ try {
56
+ const controller = new AbortController();
57
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
58
+ const response = await fetch(`${this.baseUrl}/api/tags`, {
59
+ method: 'GET',
60
+ signal: controller.signal,
61
+ headers: { 'Content-Type': 'application/json' }
62
+ });
63
+ clearTimeout(timeoutId);
64
+ return response.ok;
65
+ }
66
+ catch {
67
+ return false;
68
+ }
69
+ }
70
+ async ensureModelAvailable() {
71
+ try {
72
+ const response = await fetch(`${this.baseUrl}/api/tags`);
73
+ if (!response.ok)
74
+ throw new Error('Cannot access Ollama');
75
+ const data = await response.json();
76
+ const models = data.models?.map(m => m.name) || [];
77
+ if (!models.includes(this.modelId)) {
78
+ // Try to pull the model
79
+ const pullResponse = await fetch(`${this.baseUrl}/api/pull`, {
80
+ method: 'POST',
81
+ headers: { 'Content-Type': 'application/json' },
82
+ body: JSON.stringify({ name: this.modelId })
83
+ });
84
+ if (!pullResponse.ok) {
85
+ throw new Error(`Model '${this.modelId}' not available and cannot be pulled`);
86
+ }
87
+ }
88
+ }
89
+ catch (error) {
90
+ throw new Error(`Failed to ensure model availability: ${error instanceof Error ? error.message : String(error)}`);
91
+ }
92
+ }
93
+ async doGenerate(options) {
94
+ // Health check and model availability
95
+ const isHealthy = await this.checkHealth();
96
+ if (!isHealthy) {
97
+ throw new Error('Ollama service is not running or accessible. Please ensure Ollama is installed and running.');
98
+ }
99
+ await this.ensureModelAvailable();
100
+ const prompt = this.convertMessagesToPrompt(options.prompt);
101
+ const requestPayload = {
102
+ model: this.modelId,
103
+ prompt,
104
+ stream: false,
105
+ options: {
106
+ temperature: options.temperature || 0.7,
107
+ num_predict: options.maxTokens || 500,
108
+ }
109
+ };
110
+ const controller = new AbortController();
111
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
112
+ try {
113
+ const response = await fetch(`${this.baseUrl}/api/generate`, {
114
+ method: 'POST',
115
+ headers: { 'Content-Type': 'application/json' },
116
+ body: JSON.stringify(requestPayload),
117
+ signal: controller.signal
118
+ });
119
+ clearTimeout(timeoutId);
120
+ if (!response.ok) {
121
+ throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
122
+ }
123
+ const data = await response.json();
124
+ if (!data.response) {
125
+ throw new Error('No response received from Ollama');
126
+ }
127
+ const promptTokens = this.estimateTokens(prompt);
128
+ const completionTokens = this.estimateTokens(data.response);
129
+ return {
130
+ text: data.response,
131
+ usage: {
132
+ promptTokens,
133
+ completionTokens,
134
+ totalTokens: promptTokens + completionTokens
135
+ },
136
+ finishReason: 'stop',
137
+ logprobs: undefined,
138
+ rawCall: { rawPrompt: prompt, rawSettings: options },
139
+ rawResponse: { headers: {} }
140
+ };
141
+ }
142
+ catch (error) {
143
+ clearTimeout(timeoutId);
144
+ const errorMessage = error instanceof Error ? error.message : String(error);
145
+ if (errorMessage.includes('AbortError') || errorMessage.includes('timeout')) {
146
+ throw new Error(`Ollama request timeout (${this.timeout}ms). The model may be large or the system is under load.`);
147
+ }
148
+ if (errorMessage.includes('ECONNREFUSED') || errorMessage.includes('fetch failed')) {
149
+ throw new Error('Cannot connect to Ollama service. Please ensure Ollama is installed and running on ' + this.baseUrl);
150
+ }
151
+ throw error;
152
+ }
153
+ }
154
+ async doStream(options) {
155
+ // Health check and model availability
156
+ const isHealthy = await this.checkHealth();
157
+ if (!isHealthy) {
158
+ throw new Error('Ollama service is not running or accessible. Please ensure Ollama is installed and running.');
159
+ }
160
+ await this.ensureModelAvailable();
161
+ const prompt = this.convertMessagesToPrompt(options.prompt);
162
+ const requestPayload = {
163
+ model: this.modelId,
164
+ prompt,
165
+ stream: true,
166
+ options: {
167
+ temperature: options.temperature || 0.7,
168
+ num_predict: options.maxTokens || 500,
169
+ }
170
+ };
171
+ const controller = new AbortController();
172
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
173
+ try {
174
+ const response = await fetch(`${this.baseUrl}/api/generate`, {
175
+ method: 'POST',
176
+ headers: { 'Content-Type': 'application/json' },
177
+ body: JSON.stringify(requestPayload),
178
+ signal: controller.signal
179
+ });
180
+ clearTimeout(timeoutId);
181
+ if (!response.ok) {
182
+ throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
183
+ }
184
+ if (!response.body) {
185
+ throw new Error('No response body received from Ollama streaming API');
186
+ }
187
+ // Create a ReadableStream that parses Ollama's streaming format
188
+ const stream = new ReadableStream({
189
+ async start(controller) {
190
+ const reader = response.body.getReader();
191
+ const decoder = new TextDecoder();
192
+ let totalTokens = 0;
193
+ try {
194
+ while (true) {
195
+ const { done, value } = await reader.read();
196
+ if (done)
197
+ break;
198
+ const chunk = decoder.decode(value, { stream: true });
199
+ const lines = chunk.split('\n').filter(line => line.trim());
200
+ for (const line of lines) {
201
+ try {
202
+ const data = JSON.parse(line);
203
+ if (data.response) {
204
+ controller.enqueue({
205
+ type: 'text-delta',
206
+ textDelta: data.response
207
+ });
208
+ totalTokens += Math.ceil(data.response.length / 4);
209
+ }
210
+ if (data.done) {
211
+ controller.enqueue({
212
+ type: 'finish',
213
+ finishReason: 'stop',
214
+ usage: {
215
+ promptTokens: data.prompt_eval_count || Math.ceil(prompt.length / 4),
216
+ completionTokens: data.eval_count || totalTokens
217
+ },
218
+ logprobs: undefined
219
+ });
220
+ controller.close();
221
+ return;
222
+ }
223
+ }
224
+ catch (parseError) {
225
+ // Skip invalid JSON lines
226
+ }
227
+ }
228
+ }
229
+ }
230
+ finally {
231
+ reader.releaseLock();
232
+ }
233
+ }
234
+ });
235
+ return {
236
+ stream,
237
+ rawCall: { rawPrompt: prompt, rawSettings: options },
238
+ rawResponse: { headers: {} }
239
+ };
240
+ }
241
+ catch (error) {
242
+ clearTimeout(timeoutId);
243
+ const errorMessage = error instanceof Error ? error.message : String(error);
244
+ if (errorMessage.includes('AbortError') || errorMessage.includes('timeout')) {
245
+ throw new Error(`Ollama streaming timeout (${this.timeout}ms). The model may be large or the system is under load.`);
246
+ }
247
+ if (errorMessage.includes('ECONNREFUSED') || errorMessage.includes('fetch failed')) {
248
+ throw new Error('Cannot connect to Ollama service. Please ensure Ollama is installed and running on ' + this.baseUrl);
249
+ }
250
+ throw error;
251
+ }
252
+ }
253
+ }
254
+ export class Ollama {
255
+ baseUrl;
256
+ modelName;
257
+ timeout;
258
+ constructor(modelName) {
259
+ this.baseUrl = process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
260
+ this.modelName = modelName || process.env.OLLAMA_MODEL || 'llama2';
261
+ this.timeout = parseInt(process.env.OLLAMA_TIMEOUT || '60000'); // 60 seconds default
262
+ logger.debug('[Ollama] Initialized', {
263
+ baseUrl: this.baseUrl,
264
+ modelName: this.modelName,
265
+ timeout: this.timeout
266
+ });
267
+ }
268
+ /**
269
+ * Gets the appropriate model instance
270
+ * @private
271
+ */
272
+ getModel() {
273
+ logger.debug('Ollama.getModel - Ollama model selected', {
274
+ modelName: this.modelName
275
+ });
276
+ return new OllamaLanguageModel(this.modelName, this.baseUrl, this.timeout);
277
+ }
278
+ /**
279
+ * Health check - verify Ollama service is running and accessible
280
+ */
281
+ async checkHealth() {
282
+ const model = new OllamaLanguageModel(this.modelName, this.baseUrl, this.timeout);
283
+ return await model['checkHealth']();
284
+ }
285
+ /**
286
+ * List available models on the Ollama instance
287
+ */
288
+ async listModels() {
289
+ const functionTag = 'Ollama.listModels';
290
+ try {
291
+ logger.debug(`[${functionTag}] Listing available models`);
292
+ const response = await fetch(`${this.baseUrl}/api/tags`, {
293
+ method: 'GET',
294
+ headers: {
295
+ 'Content-Type': 'application/json'
296
+ }
297
+ });
298
+ if (!response.ok) {
299
+ throw new Error(`Failed to list models: ${response.status} ${response.statusText}`);
300
+ }
301
+ const data = await response.json();
302
+ const modelNames = data.models?.map(model => model.name) || [];
303
+ logger.debug(`[${functionTag}] Found models`, {
304
+ count: modelNames.length,
305
+ models: modelNames
306
+ });
307
+ return modelNames;
308
+ }
309
+ catch (error) {
310
+ logger.debug(`[${functionTag}] Error listing models`, {
311
+ error: error instanceof Error ? error.message : String(error)
312
+ });
313
+ throw new Error(`Failed to list Ollama models: ${error instanceof Error ? error.message : String(error)}`);
314
+ }
315
+ }
316
+ /**
317
+ * Check if a specific model is available
318
+ */
319
+ async isModelAvailable(modelName) {
320
+ try {
321
+ const models = await this.listModels();
322
+ return models.includes(modelName);
323
+ }
324
+ catch (error) {
325
+ return false;
326
+ }
327
+ }
328
+ /**
329
+ * Pull/download a model to the local Ollama instance
330
+ */
331
+ async pullModel(modelName) {
332
+ const functionTag = 'Ollama.pullModel';
333
+ try {
334
+ logger.debug(`[${functionTag}] Pulling model`, { modelName });
335
+ const response = await fetch(`${this.baseUrl}/api/pull`, {
336
+ method: 'POST',
337
+ headers: {
338
+ 'Content-Type': 'application/json'
339
+ },
340
+ body: JSON.stringify({
341
+ name: modelName
342
+ })
343
+ });
344
+ if (!response.ok) {
345
+ throw new Error(`Failed to pull model: ${response.status} ${response.statusText}`);
346
+ }
347
+ // Note: Ollama pull API returns streaming responses
348
+ // For simplicity, we're not handling the streaming progress here
349
+ logger.debug(`[${functionTag}] Model pull completed`, { modelName });
350
+ }
351
+ catch (error) {
352
+ logger.debug(`[${functionTag}] Error pulling model`, {
353
+ modelName,
354
+ error: error instanceof Error ? error.message : String(error)
355
+ });
356
+ throw new Error(`Failed to pull model '${modelName}': ${error instanceof Error ? error.message : String(error)}`);
357
+ }
358
+ }
359
+ /**
360
+ * Generate text using Ollama local models
361
+ */
362
+ async generateText(optionsOrPrompt, analysisSchema) {
363
+ const functionTag = 'Ollama.generateText';
364
+ const provider = 'ollama';
365
+ try {
366
+ // Parse parameters - support both string and options object
367
+ const options = typeof optionsOrPrompt === 'string'
368
+ ? { prompt: optionsOrPrompt }
369
+ : optionsOrPrompt;
370
+ const { prompt, temperature = 0.7, maxTokens = 500, systemPrompt = DEFAULT_SYSTEM_CONTEXT.systemPrompt, schema } = options;
371
+ // Use schema from options or fallback parameter
372
+ const finalSchema = schema || analysisSchema;
373
+ logger.debug(`[${functionTag}] Generate request started`, {
374
+ provider,
375
+ modelName: this.modelName,
376
+ promptLength: prompt.length,
377
+ temperature,
378
+ maxTokens
379
+ });
380
+ const model = this.getModel();
381
+ const generateOptions = {
382
+ model: model,
383
+ prompt: prompt,
384
+ system: systemPrompt,
385
+ temperature,
386
+ maxTokens
387
+ };
388
+ if (finalSchema) {
389
+ generateOptions.experimental_output = Output.object({ schema: finalSchema });
390
+ }
391
+ const result = await generateText(generateOptions);
392
+ logger.debug(`[${functionTag}] Generate text completed`, {
393
+ provider,
394
+ modelName: this.modelName,
395
+ usage: result.usage,
396
+ finishReason: result.finishReason,
397
+ responseLength: result.text?.length || 0
398
+ });
399
+ return result;
400
+ }
401
+ catch (err) {
402
+ logger.debug(`[${functionTag}] Exception`, {
403
+ provider,
404
+ modelName: this.modelName,
405
+ message: 'Error in generating text',
406
+ err: String(err)
407
+ });
408
+ throw err; // Re-throw error to trigger fallback
409
+ }
410
+ }
411
+ /**
412
+ * Generate streaming text using Ollama local models
413
+ */
414
+ async streamText(optionsOrPrompt, analysisSchema) {
415
+ const functionTag = 'Ollama.streamText';
416
+ const provider = 'ollama';
417
+ let chunkCount = 0;
418
+ try {
419
+ // Parse parameters - support both string and options object
420
+ const options = typeof optionsOrPrompt === 'string'
421
+ ? { prompt: optionsOrPrompt }
422
+ : optionsOrPrompt;
423
+ const { prompt, temperature = 0.7, maxTokens = 500, systemPrompt = DEFAULT_SYSTEM_CONTEXT.systemPrompt, schema } = options;
424
+ // Use schema from options or fallback parameter
425
+ const finalSchema = schema || analysisSchema;
426
+ logger.debug(`[${functionTag}] Stream request started`, {
427
+ provider,
428
+ modelName: this.modelName,
429
+ promptLength: prompt.length,
430
+ temperature,
431
+ maxTokens,
432
+ hasSchema: !!finalSchema
433
+ });
434
+ const model = this.getModel();
435
+ const streamOptions = {
436
+ model: model,
437
+ prompt: prompt,
438
+ system: systemPrompt,
439
+ temperature,
440
+ maxTokens,
441
+ onError: (event) => {
442
+ const error = event.error;
443
+ const errorMessage = error instanceof Error ? error.message : String(error);
444
+ const errorStack = error instanceof Error ? error.stack : undefined;
445
+ logger.debug(`[${functionTag}] Stream text error`, {
446
+ provider,
447
+ modelName: this.modelName,
448
+ error: errorMessage,
449
+ stack: errorStack,
450
+ promptLength: prompt.length,
451
+ chunkCount
452
+ });
453
+ },
454
+ onFinish: (event) => {
455
+ logger.debug(`[${functionTag}] Stream text finished`, {
456
+ provider,
457
+ modelName: this.modelName,
458
+ finishReason: event.finishReason,
459
+ usage: event.usage,
460
+ totalChunks: chunkCount,
461
+ promptLength: prompt.length,
462
+ responseLength: event.text?.length || 0
463
+ });
464
+ },
465
+ onChunk: (event) => {
466
+ chunkCount++;
467
+ logger.debug(`[${functionTag}] Stream text chunk`, {
468
+ provider,
469
+ modelName: this.modelName,
470
+ chunkNumber: chunkCount,
471
+ chunkLength: event.chunk.text?.length || 0,
472
+ chunkType: event.chunk.type
473
+ });
474
+ }
475
+ };
476
+ if (finalSchema) {
477
+ streamOptions.experimental_output = Output.object({ schema: finalSchema });
478
+ }
479
+ const result = streamText(streamOptions);
480
+ return result;
481
+ }
482
+ catch (err) {
483
+ logger.debug(`[${functionTag}] Exception`, {
484
+ provider,
485
+ modelName: this.modelName,
486
+ message: 'Error in streaming text',
487
+ err: String(err),
488
+ promptLength: typeof optionsOrPrompt === 'string' ? optionsOrPrompt.length : optionsOrPrompt.prompt.length
489
+ });
490
+ throw err; // Re-throw error to trigger fallback
491
+ }
492
+ }
493
+ }
@@ -13,7 +13,8 @@ export function getBestProvider(requestedProvider) {
13
13
  return requestedProvider;
14
14
  }
15
15
  // Default fallback order based on environment variables - OpenAI first since it's most reliable
16
- const providers = ['openai', 'vertex', 'bedrock', 'anthropic', 'azure', 'google-ai'];
16
+ // Ollama last since it requires local setup
17
+ const providers = ['openai', 'anthropic', 'google-ai', 'mistral', 'vertex', 'azure', 'huggingface', 'bedrock', 'ollama'];
17
18
  // Check which providers have their required environment variables
18
19
  for (const provider of providers) {
19
20
  if (isProviderConfigured(provider)) {
@@ -52,6 +53,20 @@ function isProviderConfigured(provider) {
52
53
  case 'google-ai':
53
54
  case 'google-studio':
54
55
  return !!(process.env.GOOGLE_AI_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY);
56
+ case 'huggingface':
57
+ case 'hugging-face':
58
+ case 'hf':
59
+ return !!(process.env.HUGGINGFACE_API_KEY || process.env.HF_TOKEN);
60
+ case 'ollama':
61
+ case 'local':
62
+ case 'local-ollama':
63
+ // For Ollama, we check if the service is potentially available
64
+ // This is a basic check - actual connectivity will be verified during usage
65
+ return true; // Ollama doesn't require environment variables, just local service
66
+ case 'mistral':
67
+ case 'mistral-ai':
68
+ case 'mistralai':
69
+ return !!process.env.MISTRAL_API_KEY;
55
70
  default:
56
71
  return false;
57
72
  }
@@ -61,7 +76,7 @@ function isProviderConfigured(provider) {
61
76
  * @returns Array of available provider names
62
77
  */
63
78
  export function getAvailableProviders() {
64
- return ['bedrock', 'vertex', 'openai', 'anthropic', 'azure', 'google-ai'];
79
+ return ['bedrock', 'vertex', 'openai', 'anthropic', 'azure', 'google-ai', 'huggingface', 'ollama', 'mistral'];
65
80
  }
66
81
  /**
67
82
  * Validate provider name
@@ -12,12 +12,12 @@ import { getBestProvider, getAvailableProviders } from '../../../utils/providerU
12
12
  const AnalyzeUsageSchema = z.object({
13
13
  sessionId: z.string().optional(),
14
14
  timeRange: z.enum(['1h', '24h', '7d', '30d']).default('24h'),
15
- provider: z.enum(['openai', 'bedrock', 'vertex', 'anthropic', 'google-ai']).optional(),
15
+ provider: z.enum(['openai', 'bedrock', 'vertex', 'anthropic', 'google-ai', 'azure', 'huggingface', 'ollama', 'mistral']).optional(),
16
16
  includeTokenBreakdown: z.boolean().default(true),
17
17
  includeCostEstimation: z.boolean().default(true)
18
18
  });
19
19
  const BenchmarkSchema = z.object({
20
- providers: z.array(z.enum(['openai', 'bedrock', 'vertex', 'anthropic', 'google-ai'])).optional(),
20
+ providers: z.array(z.enum(['openai', 'bedrock', 'vertex', 'anthropic', 'google-ai', 'azure', 'huggingface', 'ollama', 'mistral'])).optional(),
21
21
  testPrompts: z.array(z.string()).optional(),
22
22
  iterations: z.number().min(1).max(5).default(2),
23
23
  metrics: z.array(z.enum(['latency', 'quality', 'cost', 'tokens'])).default(['latency', 'quality']),
@@ -25,7 +25,7 @@ const BenchmarkSchema = z.object({
25
25
  });
26
26
  const OptimizeParametersSchema = z.object({
27
27
  prompt: z.string().min(1, 'Prompt is required for optimization'),
28
- provider: z.enum(['openai', 'bedrock', 'vertex', 'anthropic', 'google-ai']).optional(),
28
+ provider: z.enum(['openai', 'bedrock', 'vertex', 'anthropic', 'google-ai', 'azure', 'huggingface', 'ollama', 'mistral']).optional(),
29
29
  targetLength: z.number().positive().optional(),
30
30
  style: z.enum(['creative', 'balanced', 'precise', 'factual']).default('balanced'),
31
31
  optimizeFor: z.enum(['speed', 'quality', 'cost', 'tokens']).default('quality'),
@@ -59,7 +59,7 @@ export const analyzeAIUsageTool = {
59
59
 
60
60
  Generate a realistic analysis including:
61
61
  1. A summary of usage statistics (totalRequests, totalTokens).
62
- 2. A breakdown of usage by provider (OpenAI, Bedrock, Vertex).
62
+ 2. A breakdown of usage by provider (OpenAI, Bedrock, Vertex, Google AI, Anthropic, Azure, Hugging Face, Ollama, Mistral).
63
63
  3. Key insights and actionable recommendations for cost and performance optimization.
64
64
 
65
65
  Return the result as a valid JSON object with keys: "analysis", "insights".
@@ -38,7 +38,7 @@ export const aiCoreServer = createMCPServer({
38
38
  */
39
39
  const TextGenerationSchema = z.object({
40
40
  prompt: z.string().min(1, 'Prompt is required'),
41
- provider: z.enum(['openai', 'bedrock', 'vertex', 'anthropic', 'google-ai']).optional(),
41
+ provider: z.enum(['openai', 'bedrock', 'vertex', 'anthropic', 'google-ai', 'azure', 'huggingface', 'ollama', 'mistral']).optional(),
42
42
  model: z.string().optional(),
43
43
  temperature: z.number().min(0).max(2).optional(),
44
44
  maxTokens: z.number().positive().optional(),
@@ -144,12 +144,14 @@ aiCoreServer.registerTool({
144
144
  // Use existing provider selection logic
145
145
  const availableProviders = getAvailableProviders();
146
146
  const selectedProvider = getBestProvider(params.preferred);
147
- // Get provider capabilities (mock for now, can be enhanced)
147
+ // Get provider capabilities
148
148
  const getProviderCapabilities = (provider) => ({
149
- multimodal: provider === 'openai' || provider === 'vertex',
150
- streaming: provider === 'openai' || provider === 'anthropic',
151
- maxTokens: provider === 'anthropic' ? 100000 : 4000,
152
- costEfficient: provider === 'openai' || provider === 'vertex'
149
+ multimodal: provider === 'openai' || provider === 'vertex' || provider === 'google-ai',
150
+ streaming: provider === 'openai' || provider === 'anthropic' || provider === 'azure' || provider === 'mistral',
151
+ maxTokens: provider === 'anthropic' ? 100000 : provider === 'huggingface' ? 2048 : 4000,
152
+ costEfficient: provider === 'google-ai' || provider === 'vertex' || provider === 'huggingface' || provider === 'ollama',
153
+ localExecution: provider === 'ollama',
154
+ openSource: provider === 'huggingface' || provider === 'ollama'
153
155
  });
154
156
  const capabilities = getProviderCapabilities(selectedProvider);
155
157
  const executionTime = Date.now() - startTime;
@@ -224,9 +226,11 @@ aiCoreServer.registerTool({
224
226
  status: isAvailable ? 'available' : 'unavailable',
225
227
  capabilities: params.includeCapabilities ? {
226
228
  textGeneration: true,
227
- multimodal: provider === 'openai' || provider === 'vertex',
228
- streaming: provider === 'openai' || provider === 'anthropic',
229
- maxTokens: provider === 'anthropic' ? 100000 : 4000
229
+ multimodal: provider === 'openai' || provider === 'vertex' || provider === 'google-ai',
230
+ streaming: provider === 'openai' || provider === 'anthropic' || provider === 'azure' || provider === 'mistral',
231
+ maxTokens: provider === 'anthropic' ? 100000 : provider === 'huggingface' ? 2048 : 4000,
232
+ localExecution: provider === 'ollama',
233
+ openSource: provider === 'huggingface' || provider === 'ollama'
230
234
  } : undefined,
231
235
  lastChecked: new Date().toISOString()
232
236
  });