@juspay/neurolink 7.14.7 → 7.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [7.15.0](https://github.com/juspay/neurolink/compare/v7.14.8...v7.15.0) (2025-08-19)
2
+
3
+ ### Features
4
+
5
+ - **(tools):** add websearch tool using Gemini AI for Google search integration BZ-43347 ([bcd5160](https://github.com/juspay/neurolink/commit/bcd516019db8a6b89ba6ecb39037b257fd955df0))
6
+
7
+ ## [7.14.8](https://github.com/juspay/neurolink/compare/v7.14.7...v7.14.8) (2025-08-19)
8
+
9
+ ### Bug Fixes
10
+
11
+ - **(mcp):** implement generic error handling for all MCP server response formats ([5aa707a](https://github.com/juspay/neurolink/commit/5aa707aa9874ed76ab067a1f7fb6e8301519ce7f))
12
+
1
13
  ## [7.14.7](https://github.com/juspay/neurolink/compare/v7.14.6...v7.14.7) (2025-08-18)
2
14
 
3
15
  ### Bug Fixes
package/README.md CHANGED
@@ -284,6 +284,11 @@ echo 'OPENAI_API_KEY="sk-your-openai-key"' > .env
284
284
  echo 'GOOGLE_AI_API_KEY="AIza-your-google-ai-key"' >> .env
285
285
  echo 'AWS_ACCESS_KEY_ID="your-aws-access-key"' >> .env
286
286
 
287
+ # 🆕 NEW: Google Vertex AI for Websearch Tool
288
+ echo 'GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"' >> .env
289
+ echo 'GOOGLE_VERTEX_PROJECT="your-gcp-project-id"' >> .env
290
+ echo 'GOOGLE_VERTEX_LOCATION="us-central1"' >> .env
291
+
287
292
  # Test configuration
288
293
  npx @juspay/neurolink status
289
294
  ```
@@ -337,12 +342,63 @@ console.log(productData.name, productData.price, productData.features);
337
342
 
338
343
  **📖 [Complete Setup Guide](./docs/CONFIGURATION.md)** - All providers with detailed instructions
339
344
 
345
+ ## 🔍 **NEW: Websearch Tool with Google Vertex AI Grounding**
346
+
347
+ **NeuroLink now includes a powerful websearch tool** that uses Google's native search grounding technology for real-time web information:
348
+
349
+ - **🔍 Native Google Search** - Uses Google's search grounding via Vertex AI
350
+ - **🎯 Real-time Results** - Access current web information during AI conversations
351
+ - **🔒 Credential Protection** - Only activates when Google Vertex AI credentials are properly configured
352
+
353
+ ### Quick Setup & Test
354
+
355
+ ```bash
356
+ # 1. Build the project first
357
+ pnpm run build
358
+
359
+ # 2. Set up environment variables (see detailed setup below)
360
+ cp .env.example .env
361
+ # Edit .env with your Google Vertex AI credentials
362
+
363
+ # 3. Test the websearch tool directly
364
+ node test-websearch-grounding.j
365
+ ```
366
+
367
+ ### Complete Google Vertex AI Setup
368
+
369
+ #### Configure Environment Variables
370
+
371
+ ```bash
372
+ # Add to your .env file
373
+ GOOGLE_APPLICATION_CREDENTIALS="/absolute/path/to/neurolink-service-account.json"
374
+ GOOGLE_VERTEX_PROJECT="YOUR-PROJECT-ID"
375
+ GOOGLE_VERTEX_LOCATION="us-central1"
376
+ ```
377
+
378
+ #### Step 3: Test the Setup
379
+
380
+ ````bash
381
+ # Build the project first
382
+ pnpm run build
383
+
384
+ # Run the dedicated test script
385
+ node test-websearch-grounding.js
386
+
387
+ ### Using the Websearch Tool
388
+
389
+ #### CLI Usage (Works with All Providers)
390
+
391
+ # With specific providers - websearch works across all providers
392
+ npx @juspay/neurolink generate "Weather in Tokyo now" --provider vertex
393
+
394
+ **Note:** The websearch tool gracefully handles missing credentials - it only activates when valid Google Vertex AI credentials are configured. Without proper credentials, other tools continue to work normally and AI responses fall back to training data.
395
+
340
396
  ## ✨ Key Features
341
397
 
342
398
  - 🔗 **LiteLLM Integration** - **Access 100+ AI models** from all major providers through unified interface
343
399
  - 🔍 **Smart Model Auto-Discovery** - OpenAI Compatible provider automatically detects available models via `/v1/models` endpoint
344
400
  - 🏭 **Factory Pattern Architecture** - Unified provider management with BaseProvider inheritance
345
- - 🔧 **Tools-First Design** - All providers automatically include 6 direct tools (getCurrentTime, readFile, listDirectory, calculateMath, writeFile, searchFiles)
401
+ - 🔧 **Tools-First Design** - All providers automatically include 7 direct tools (getCurrentTime, readFile, listDirectory, calculateMath, writeFile, searchFiles, websearchGrounding)
346
402
  - 🔄 **12 AI Providers** - OpenAI, Bedrock, Vertex AI, Google AI Studio, Anthropic, Azure, **LiteLLM**, **OpenAI Compatible**, Hugging Face, Ollama, Mistral AI, **SageMaker**
347
403
  - 💰 **Cost Optimization** - Automatic selection of cheapest models and LiteLLM routing
348
404
  - ⚡ **Automatic Fallback** - Never fail when providers are down, intelligent provider switching
@@ -398,7 +454,7 @@ const result = await neurolink.generate({
398
454
 
399
455
  # Discover available MCP servers
400
456
  npx @juspay/neurolink mcp discover --format table
401
- ```
457
+ ````
402
458
 
403
459
  ### 🔧 SDK Custom Tool Registration (NEW!)
404
460
 
@@ -346,6 +346,139 @@ export declare const directAgentTools: {
346
346
  count?: undefined;
347
347
  }>;
348
348
  };
349
+ websearchGrounding: import("ai").Tool<z.ZodObject<{
350
+ query: z.ZodString;
351
+ maxResults: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
352
+ maxWords: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
353
+ }, "strip", z.ZodTypeAny, {
354
+ query: string;
355
+ maxResults: number;
356
+ maxWords: number;
357
+ }, {
358
+ query: string;
359
+ maxResults?: number | undefined;
360
+ maxWords?: number | undefined;
361
+ }>, {
362
+ success: boolean;
363
+ error: string;
364
+ requiredEnvVars: string[];
365
+ query?: undefined;
366
+ searchResults?: undefined;
367
+ rawContent?: undefined;
368
+ totalResults?: undefined;
369
+ provider?: undefined;
370
+ model?: undefined;
371
+ responseTime?: undefined;
372
+ timestamp?: undefined;
373
+ grounded?: undefined;
374
+ } | {
375
+ success: boolean;
376
+ error: string;
377
+ query: string;
378
+ requiredEnvVars?: undefined;
379
+ searchResults?: undefined;
380
+ rawContent?: undefined;
381
+ totalResults?: undefined;
382
+ provider?: undefined;
383
+ model?: undefined;
384
+ responseTime?: undefined;
385
+ timestamp?: undefined;
386
+ grounded?: undefined;
387
+ } | {
388
+ success: boolean;
389
+ query: string;
390
+ searchResults: {
391
+ title: string;
392
+ url: string;
393
+ snippet: string;
394
+ domain: string;
395
+ }[];
396
+ rawContent: string;
397
+ totalResults: number;
398
+ provider: string;
399
+ model: string;
400
+ responseTime: number;
401
+ timestamp: number;
402
+ grounded: boolean;
403
+ error?: undefined;
404
+ requiredEnvVars?: undefined;
405
+ } | {
406
+ success: boolean;
407
+ error: string;
408
+ query: string;
409
+ provider: string;
410
+ requiredEnvVars?: undefined;
411
+ searchResults?: undefined;
412
+ rawContent?: undefined;
413
+ totalResults?: undefined;
414
+ model?: undefined;
415
+ responseTime?: undefined;
416
+ timestamp?: undefined;
417
+ grounded?: undefined;
418
+ }> & {
419
+ execute: (args: {
420
+ query: string;
421
+ maxResults: number;
422
+ maxWords: number;
423
+ }, options: import("ai").ToolExecutionOptions) => PromiseLike<{
424
+ success: boolean;
425
+ error: string;
426
+ requiredEnvVars: string[];
427
+ query?: undefined;
428
+ searchResults?: undefined;
429
+ rawContent?: undefined;
430
+ totalResults?: undefined;
431
+ provider?: undefined;
432
+ model?: undefined;
433
+ responseTime?: undefined;
434
+ timestamp?: undefined;
435
+ grounded?: undefined;
436
+ } | {
437
+ success: boolean;
438
+ error: string;
439
+ query: string;
440
+ requiredEnvVars?: undefined;
441
+ searchResults?: undefined;
442
+ rawContent?: undefined;
443
+ totalResults?: undefined;
444
+ provider?: undefined;
445
+ model?: undefined;
446
+ responseTime?: undefined;
447
+ timestamp?: undefined;
448
+ grounded?: undefined;
449
+ } | {
450
+ success: boolean;
451
+ query: string;
452
+ searchResults: {
453
+ title: string;
454
+ url: string;
455
+ snippet: string;
456
+ domain: string;
457
+ }[];
458
+ rawContent: string;
459
+ totalResults: number;
460
+ provider: string;
461
+ model: string;
462
+ responseTime: number;
463
+ timestamp: number;
464
+ grounded: boolean;
465
+ error?: undefined;
466
+ requiredEnvVars?: undefined;
467
+ } | {
468
+ success: boolean;
469
+ error: string;
470
+ query: string;
471
+ provider: string;
472
+ requiredEnvVars?: undefined;
473
+ searchResults?: undefined;
474
+ rawContent?: undefined;
475
+ totalResults?: undefined;
476
+ model?: undefined;
477
+ responseTime?: undefined;
478
+ timestamp?: undefined;
479
+ grounded?: undefined;
480
+ }>;
481
+ };
349
482
  };
350
483
  /**
351
484
  * Type aliases for specific tool categories
@@ -7,6 +7,18 @@ import { z } from "zod";
7
7
  import * as fs from "fs";
8
8
  import * as path from "path";
9
9
  import { logger } from "../utils/logger.js";
10
+ import { VertexAI } from "@google-cloud/vertexai";
11
+ // Runtime Google Search tool creation - bypasses TypeScript strict typing
12
+ function createGoogleSearchTools() {
13
+ const searchTool = {};
14
+ // Dynamically assign google_search property at runtime
15
+ Object.defineProperty(searchTool, "google_search", {
16
+ value: {},
17
+ enumerable: true,
18
+ configurable: true,
19
+ });
20
+ return [searchTool];
21
+ }
10
22
  /**
11
23
  * Direct tool definitions that work immediately with Gemini/AI SDK
12
24
  * These bypass MCP complexity and provide reliable agent functionality
@@ -324,6 +336,128 @@ export const directAgentTools = {
324
336
  }
325
337
  },
326
338
  }),
339
+ websearchGrounding: tool({
340
+ description: "Search the web for current information using Google Search grounding. Returns raw search data for AI processing.",
341
+ parameters: z.object({
342
+ query: z.string().describe("Search query to find information about"),
343
+ maxResults: z
344
+ .number()
345
+ .optional()
346
+ .default(3)
347
+ .describe("Maximum number of search results to return (1-5)"),
348
+ maxWords: z
349
+ .number()
350
+ .optional()
351
+ .default(50)
352
+ .describe("Maximum number of words in the response 50"),
353
+ }),
354
+ execute: async ({ query, maxResults = 3, maxWords = 50 }) => {
355
+ try {
356
+ const hasCredentials = process.env.GOOGLE_APPLICATION_CREDENTIALS;
357
+ const hasProjectId = process.env.GOOGLE_VERTEX_PROJECT;
358
+ const projectLocation = process.env.GOOGLE_VERTEX_LOCATION || "us-central1";
359
+ if (!hasCredentials || !hasProjectId) {
360
+ return {
361
+ success: false,
362
+ error: "Google Vertex AI credentials not configured. Please set GOOGLE_APPLICATION_CREDENTIALS and GOOGLE_VERTEX_PROJECT environment variables.",
363
+ requiredEnvVars: [
364
+ "GOOGLE_APPLICATION_CREDENTIALS",
365
+ "GOOGLE_VERTEX_PROJECT",
366
+ ],
367
+ };
368
+ }
369
+ const limitedResults = Math.min(Math.max(maxResults, 1), 5);
370
+ const vertex_ai = new VertexAI({
371
+ project: hasProjectId,
372
+ location: projectLocation,
373
+ });
374
+ const websearchModel = "gemini-2.5-flash-lite";
375
+ const model = vertex_ai.getGenerativeModel({
376
+ model: websearchModel,
377
+ tools: createGoogleSearchTools(),
378
+ });
379
+ // Search query with word limit constraint
380
+ const searchPrompt = `Search for: "${query}". Provide a concise summary in no more than ${maxWords} words.`;
381
+ const startTime = Date.now();
382
+ const response = await model.generateContent({
383
+ contents: [
384
+ {
385
+ role: "user",
386
+ parts: [{ text: searchPrompt }],
387
+ },
388
+ ],
389
+ });
390
+ const responseTime = Date.now() - startTime;
391
+ // Extract grounding metadata and search results
392
+ const result = response.response;
393
+ const candidates = result.candidates;
394
+ if (!candidates || candidates.length === 0) {
395
+ return {
396
+ success: false,
397
+ error: "No search results returned",
398
+ query,
399
+ };
400
+ }
401
+ const content = candidates[0].content;
402
+ if (!content || !content.parts || content.parts.length === 0) {
403
+ return {
404
+ success: false,
405
+ error: "No search content found",
406
+ query,
407
+ };
408
+ }
409
+ // Extract raw search content
410
+ const searchContent = content.parts[0].text || "";
411
+ // Extract grounding sources if available
412
+ const groundingMetadata = candidates[0]?.groundingMetadata;
413
+ const searchResults = [];
414
+ if (groundingMetadata?.groundingChunks) {
415
+ for (const chunk of groundingMetadata.groundingChunks.slice(0, limitedResults)) {
416
+ if (chunk.web) {
417
+ searchResults.push({
418
+ title: chunk.web.title || "No title",
419
+ url: chunk.web.uri || "",
420
+ snippet: searchContent, // Use full content since maxWords already limits length
421
+ domain: chunk.web.uri
422
+ ? new URL(chunk.web.uri).hostname
423
+ : "unknown",
424
+ });
425
+ }
426
+ }
427
+ }
428
+ // If no grounding metadata, create basic result structure
429
+ if (searchResults.length === 0) {
430
+ searchResults.push({
431
+ title: `Search results for: ${query}`,
432
+ url: "",
433
+ snippet: searchContent,
434
+ domain: "google-search",
435
+ });
436
+ }
437
+ return {
438
+ success: true,
439
+ query,
440
+ searchResults,
441
+ rawContent: searchContent,
442
+ totalResults: searchResults.length,
443
+ provider: "google-search-grounding",
444
+ model: websearchModel,
445
+ responseTime,
446
+ timestamp: startTime,
447
+ grounded: true,
448
+ };
449
+ }
450
+ catch (error) {
451
+ logger.error("Web search grounding error:", error);
452
+ return {
453
+ success: false,
454
+ error: error instanceof Error ? error.message : String(error),
455
+ query,
456
+ provider: "google-search-grounding",
457
+ };
458
+ }
459
+ },
460
+ }),
327
461
  };
328
462
  // eslint-disable-next-line no-redeclare
329
463
  export function getToolsForCategory(category = "all") {
@@ -395,12 +395,41 @@ export class BaseProvider {
395
395
  parameters: z.object({}), // Use empty schema for custom tools
396
396
  execute: async (params) => {
397
397
  logger.debug(`[BaseProvider] Executing custom tool: ${toolName}`, { params });
398
- // Use the tool executor if available (from setupToolExecutor)
399
- if (this.toolExecutor) {
400
- return await this.toolExecutor(toolName, params);
398
+ try {
399
+ // Use the tool executor if available (from setupToolExecutor)
400
+ let result;
401
+ if (this.toolExecutor) {
402
+ result = await this.toolExecutor(toolName, params);
403
+ }
404
+ else {
405
+ result = await typedToolDef.execute(params);
406
+ }
407
+ // Log successful execution
408
+ logger.debug(`[BaseProvider] Tool execution successful: ${toolName}`, {
409
+ resultType: typeof result,
410
+ hasResult: result !== null && result !== undefined,
411
+ toolName,
412
+ });
413
+ return result;
401
414
  }
402
- else {
403
- return await typedToolDef.execute(params);
415
+ catch (error) {
416
+ logger.warn(`[BaseProvider] Tool execution failed: ${toolName}`, {
417
+ error: error instanceof Error ? error.message : String(error),
418
+ params,
419
+ toolName,
420
+ });
421
+ // GENERIC ERROR HANDLING FOR ALL MCP TOOLS:
422
+ // Return a generic error object that works with any MCP server
423
+ // The AI can interpret this and try different approaches
424
+ return {
425
+ _neurolinkToolError: true,
426
+ toolName: toolName,
427
+ error: error instanceof Error ? error.message : String(error),
428
+ timestamp: new Date().toISOString(),
429
+ params: params,
430
+ // Keep it simple - just indicate an error occurred
431
+ message: `Error calling ${toolName}: ${error instanceof Error ? error.message : String(error)}`
432
+ };
404
433
  }
405
434
  },
406
435
  });
@@ -346,6 +346,139 @@ export declare const directAgentTools: {
346
346
  count?: undefined;
347
347
  }>;
348
348
  };
349
+ websearchGrounding: import("ai").Tool<z.ZodObject<{
350
+ query: z.ZodString;
351
+ maxResults: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
352
+ maxWords: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
353
+ }, "strip", z.ZodTypeAny, {
354
+ query: string;
355
+ maxResults: number;
356
+ maxWords: number;
357
+ }, {
358
+ query: string;
359
+ maxResults?: number | undefined;
360
+ maxWords?: number | undefined;
361
+ }>, {
362
+ success: boolean;
363
+ error: string;
364
+ requiredEnvVars: string[];
365
+ query?: undefined;
366
+ searchResults?: undefined;
367
+ rawContent?: undefined;
368
+ totalResults?: undefined;
369
+ provider?: undefined;
370
+ model?: undefined;
371
+ responseTime?: undefined;
372
+ timestamp?: undefined;
373
+ grounded?: undefined;
374
+ } | {
375
+ success: boolean;
376
+ error: string;
377
+ query: string;
378
+ requiredEnvVars?: undefined;
379
+ searchResults?: undefined;
380
+ rawContent?: undefined;
381
+ totalResults?: undefined;
382
+ provider?: undefined;
383
+ model?: undefined;
384
+ responseTime?: undefined;
385
+ timestamp?: undefined;
386
+ grounded?: undefined;
387
+ } | {
388
+ success: boolean;
389
+ query: string;
390
+ searchResults: {
391
+ title: string;
392
+ url: string;
393
+ snippet: string;
394
+ domain: string;
395
+ }[];
396
+ rawContent: string;
397
+ totalResults: number;
398
+ provider: string;
399
+ model: string;
400
+ responseTime: number;
401
+ timestamp: number;
402
+ grounded: boolean;
403
+ error?: undefined;
404
+ requiredEnvVars?: undefined;
405
+ } | {
406
+ success: boolean;
407
+ error: string;
408
+ query: string;
409
+ provider: string;
410
+ requiredEnvVars?: undefined;
411
+ searchResults?: undefined;
412
+ rawContent?: undefined;
413
+ totalResults?: undefined;
414
+ model?: undefined;
415
+ responseTime?: undefined;
416
+ timestamp?: undefined;
417
+ grounded?: undefined;
418
+ }> & {
419
+ execute: (args: {
420
+ query: string;
421
+ maxResults: number;
422
+ maxWords: number;
423
+ }, options: import("ai").ToolExecutionOptions) => PromiseLike<{
424
+ success: boolean;
425
+ error: string;
426
+ requiredEnvVars: string[];
427
+ query?: undefined;
428
+ searchResults?: undefined;
429
+ rawContent?: undefined;
430
+ totalResults?: undefined;
431
+ provider?: undefined;
432
+ model?: undefined;
433
+ responseTime?: undefined;
434
+ timestamp?: undefined;
435
+ grounded?: undefined;
436
+ } | {
437
+ success: boolean;
438
+ error: string;
439
+ query: string;
440
+ requiredEnvVars?: undefined;
441
+ searchResults?: undefined;
442
+ rawContent?: undefined;
443
+ totalResults?: undefined;
444
+ provider?: undefined;
445
+ model?: undefined;
446
+ responseTime?: undefined;
447
+ timestamp?: undefined;
448
+ grounded?: undefined;
449
+ } | {
450
+ success: boolean;
451
+ query: string;
452
+ searchResults: {
453
+ title: string;
454
+ url: string;
455
+ snippet: string;
456
+ domain: string;
457
+ }[];
458
+ rawContent: string;
459
+ totalResults: number;
460
+ provider: string;
461
+ model: string;
462
+ responseTime: number;
463
+ timestamp: number;
464
+ grounded: boolean;
465
+ error?: undefined;
466
+ requiredEnvVars?: undefined;
467
+ } | {
468
+ success: boolean;
469
+ error: string;
470
+ query: string;
471
+ provider: string;
472
+ requiredEnvVars?: undefined;
473
+ searchResults?: undefined;
474
+ rawContent?: undefined;
475
+ totalResults?: undefined;
476
+ model?: undefined;
477
+ responseTime?: undefined;
478
+ timestamp?: undefined;
479
+ grounded?: undefined;
480
+ }>;
481
+ };
349
482
  };
350
483
  /**
351
484
  * Type aliases for specific tool categories