@juspay/neurolink 10.8.22 → 10.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [10.9.1](https://github.com/juspay/neurolink/compare/v10.9.0...v10.9.1) (2026-08-05)
2
+
3
+ ### Bug Fixes
4
+
5
+ - **(proxy):** harden status and lifecycle telemetry ([03399a8](https://github.com/juspay/neurolink/commit/03399a8ac36861c96774fee8ae2465c2eed39026))
6
+
7
+ ## [10.9.0](https://github.com/juspay/neurolink/compare/v10.8.22...v10.9.0) (2026-08-05)
8
+
9
+ ### Features
10
+
11
+ - **(agent):** migrate websearchGrounding to @google/genai ([b5f9e15](https://github.com/juspay/neurolink/commit/b5f9e15ed32392757ac9a9d9ec101b6c143f107d))
12
+
1
13
  ## [10.8.22](https://github.com/juspay/neurolink/compare/v10.8.21...v10.8.22) (2026-08-05)
2
14
 
3
15
  ### Bug Fixes
@@ -1,4 +1,69 @@
1
1
  import type { AllToolsMap, BasicToolsMap, FilesystemToolsMap, Tool, UtilityToolsMap } from "../types/index.js";
2
+ /**
3
+ * Vertex location for the websearchGrounding tool:
4
+ * NEUROLINK_WEBSEARCH_LOCATION, else `global`.
5
+ *
6
+ * Deliberately does NOT inherit GOOGLE_VERTEX_LOCATION. The search models are
7
+ * served only from the `global`, `us` and `eu` endpoints — never from a single
8
+ * region — so inheriting a regional value produces a 404 for the model rather
9
+ * than a working search. That is not hypothetical: a deployment pinned to
10
+ * us-east5 for Claude-on-Vertex sent web search to us-east5 too and every
11
+ * query failed with NOT_FOUND.
12
+ *
13
+ * Set NEUROLINK_WEBSEARCH_LOCATION to `us` or `eu` where the multi-region
14
+ * endpoints are required, e.g. for data residency.
15
+ */
16
+ export declare function resolveWebsearchLocation(): string;
17
+ /** Model for websearchGrounding — NEUROLINK_WEBSEARCH_MODEL, else the default. */
18
+ export declare function resolveWebsearchModel(): string;
19
+ /**
20
+ * The generateContent request for a web search. Google Search grounding is
21
+ * requested natively through `config.tools`.
22
+ */
23
+ export declare function buildWebsearchRequest(model: string, query: string, maxWords: number): {
24
+ model: string;
25
+ contents: {
26
+ role: string;
27
+ parts: {
28
+ text: string;
29
+ }[];
30
+ }[];
31
+ config: {
32
+ tools: {
33
+ googleSearch: {};
34
+ }[];
35
+ };
36
+ };
37
+ /**
38
+ * Display domain for one grounding chunk.
39
+ *
40
+ * Blank `domain` values count as absent, and the uri fallback is guarded by
41
+ * `URL.canParse` — an unparseable provider uri would otherwise throw and take
42
+ * the entire search down with it.
43
+ */
44
+ export declare function resolveGroundingDomain(web: {
45
+ uri?: string;
46
+ domain?: string;
47
+ }): string;
48
+ /**
49
+ * Map grounding chunks onto search results, capped at `limitedResults`.
50
+ * Returns an empty array when there is no usable grounding metadata, which the
51
+ * caller replaces with a single synthesised result.
52
+ */
53
+ export declare function buildWebsearchResults(groundingMetadata: {
54
+ groundingChunks?: {
55
+ web?: {
56
+ uri?: string;
57
+ title?: string;
58
+ domain?: string;
59
+ };
60
+ }[];
61
+ } | undefined, searchContent: string, limitedResults: number): {
62
+ title: string;
63
+ url: string;
64
+ snippet: string;
65
+ domain: string;
66
+ }[];
2
67
  /**
3
68
  * Direct tool definitions that work immediately with Gemini/AI SDK
4
69
  * These bypass MCP complexity and provide reliable agent functionality
@@ -6,6 +6,8 @@ import { logger } from "../utils/logger.js";
6
6
  import { CSVProcessor } from "../utils/csvProcessor.js";
7
7
  import { shouldEnableBashTool } from "../utils/toolUtils.js";
8
8
  import { tool } from "../utils/tool.js";
9
+ import { withTimeout } from "../utils/async/withTimeout.js";
10
+ import { TIMEOUTS } from "../constants/timeouts.js";
9
11
  const MAX_OUTPUT_BYTES = 102400; // 100KB
10
12
  function truncateOutput(output) {
11
13
  if (output.length > MAX_OUTPUT_BYTES) {
@@ -35,16 +37,77 @@ function resolveWithinCwd(filePath) {
35
37
  }
36
38
  return { path: resolvedPath };
37
39
  }
38
- // Runtime Google Search tool creation - bypasses TypeScript strict typing
39
- function createGoogleSearchTools() {
40
- const searchTool = {};
41
- // Dynamically assign google_search property at runtime
42
- Object.defineProperty(searchTool, "google_search", {
43
- value: {},
44
- enumerable: true,
45
- configurable: true,
46
- });
47
- return [searchTool];
40
+ /**
41
+ * Vertex location for the websearchGrounding tool:
42
+ * NEUROLINK_WEBSEARCH_LOCATION, else `global`.
43
+ *
44
+ * Deliberately does NOT inherit GOOGLE_VERTEX_LOCATION. The search models are
45
+ * served only from the `global`, `us` and `eu` endpoints — never from a single
46
+ * region — so inheriting a regional value produces a 404 for the model rather
47
+ * than a working search. That is not hypothetical: a deployment pinned to
48
+ * us-east5 for Claude-on-Vertex sent web search to us-east5 too and every
49
+ * query failed with NOT_FOUND.
50
+ *
51
+ * Set NEUROLINK_WEBSEARCH_LOCATION to `us` or `eu` where the multi-region
52
+ * endpoints are required, e.g. for data residency.
53
+ */
54
+ export function resolveWebsearchLocation() {
55
+ return process.env.NEUROLINK_WEBSEARCH_LOCATION?.trim() || "global";
56
+ }
57
+ /** Model for websearchGrounding — NEUROLINK_WEBSEARCH_MODEL, else the default. */
58
+ export function resolveWebsearchModel() {
59
+ return (process.env.NEUROLINK_WEBSEARCH_MODEL?.trim() || "gemini-3.1-flash-lite");
60
+ }
61
+ /**
62
+ * The generateContent request for a web search. Google Search grounding is
63
+ * requested natively through `config.tools`.
64
+ */
65
+ export function buildWebsearchRequest(model, query, maxWords) {
66
+ return {
67
+ model,
68
+ contents: [
69
+ {
70
+ role: "user",
71
+ parts: [
72
+ {
73
+ text: `Search for: "${query}". Provide a concise summary in no more than ${maxWords} words.`,
74
+ },
75
+ ],
76
+ },
77
+ ],
78
+ config: { tools: [{ googleSearch: {} }] },
79
+ };
80
+ }
81
+ /**
82
+ * Display domain for one grounding chunk.
83
+ *
84
+ * Blank `domain` values count as absent, and the uri fallback is guarded by
85
+ * `URL.canParse` — an unparseable provider uri would otherwise throw and take
86
+ * the entire search down with it.
87
+ */
88
+ export function resolveGroundingDomain(web) {
89
+ return (web.domain?.trim() ||
90
+ (web.uri && URL.canParse(web.uri) ? new URL(web.uri).hostname : "unknown"));
91
+ }
92
+ /**
93
+ * Map grounding chunks onto search results, capped at `limitedResults`.
94
+ * Returns an empty array when there is no usable grounding metadata, which the
95
+ * caller replaces with a single synthesised result.
96
+ */
97
+ export function buildWebsearchResults(groundingMetadata, searchContent, limitedResults) {
98
+ const searchResults = [];
99
+ for (const chunk of groundingMetadata?.groundingChunks?.slice(0, limitedResults) ?? []) {
100
+ if (chunk.web) {
101
+ searchResults.push({
102
+ title: chunk.web.title || "No title",
103
+ url: chunk.web.uri || "",
104
+ // Full content — maxWords already bounds the length.
105
+ snippet: searchContent,
106
+ domain: resolveGroundingDomain(chunk.web),
107
+ });
108
+ }
109
+ }
110
+ return searchResults;
48
111
  }
49
112
  /**
50
113
  * Direct tool definitions that work immediately with Gemini/AI SDK
@@ -593,7 +656,7 @@ export const directAgentTools = {
593
656
  try {
594
657
  const hasCredentials = process.env.GOOGLE_APPLICATION_CREDENTIALS;
595
658
  const hasProjectId = process.env.GOOGLE_VERTEX_PROJECT;
596
- const projectLocation = process.env.GOOGLE_VERTEX_LOCATION || "us-central1";
659
+ const projectLocation = resolveWebsearchLocation();
597
660
  if (!hasCredentials || !hasProjectId) {
598
661
  return {
599
662
  success: false,
@@ -605,31 +668,19 @@ export const directAgentTools = {
605
668
  };
606
669
  }
607
670
  const limitedResults = Math.min(Math.max(maxResults, 1), 5);
608
- const { VertexAI } = await import("@google-cloud/vertexai");
609
- const vertex_ai = new VertexAI({
671
+ const { GoogleGenAI } = await import("@google/genai");
672
+ const vertex_ai = new GoogleGenAI({
673
+ vertexai: true,
610
674
  project: hasProjectId,
611
675
  location: projectLocation,
612
676
  });
613
- const websearchModel = process.env.NEUROLINK_WEBSEARCH_MODEL?.trim() ||
614
- "gemini-2.5-flash-lite";
615
- const model = vertex_ai.getGenerativeModel({
616
- model: websearchModel,
617
- tools: createGoogleSearchTools(),
618
- });
619
- // Search query with word limit constraint
620
- const searchPrompt = `Search for: "${query}". Provide a concise summary in no more than ${maxWords} words.`;
677
+ const websearchModel = resolveWebsearchModel();
621
678
  const startTime = Date.now();
622
- const response = await model.generateContent({
623
- contents: [
624
- {
625
- role: "user",
626
- parts: [{ text: searchPrompt }],
627
- },
628
- ],
629
- });
679
+ // A stalled Vertex call would otherwise leave the tool pending
680
+ // indefinitely; the outer catch turns a timeout into the failure shape.
681
+ const result = await withTimeout(vertex_ai.models.generateContent(buildWebsearchRequest(websearchModel, query, maxWords)), TIMEOUTS.TOOL.EXECUTION_DEFAULT_MS, "[websearchGrounding] Vertex AI web search request timed out");
630
682
  const responseTime = Date.now() - startTime;
631
683
  // Extract grounding metadata and search results
632
- const result = response.response;
633
684
  const candidates = result.candidates;
634
685
  if (!candidates || candidates.length === 0) {
635
686
  return {
@@ -638,33 +689,18 @@ export const directAgentTools = {
638
689
  query,
639
690
  };
640
691
  }
641
- const content = candidates[0].content;
642
- if (!content || !content.parts || content.parts.length === 0) {
692
+ // Extract raw search content. The aggregated `text` getter joins every
693
+ // text part, so it survives responses that lead with a non-text part.
694
+ const searchContent = result.text?.trim() || "";
695
+ if (!searchContent) {
643
696
  return {
644
697
  success: false,
645
698
  error: "No search content found",
646
699
  query,
647
700
  };
648
701
  }
649
- // Extract raw search content
650
- const searchContent = content.parts[0].text || "";
651
702
  // Extract grounding sources if available
652
- const groundingMetadata = candidates[0]?.groundingMetadata;
653
- const searchResults = [];
654
- if (groundingMetadata?.groundingChunks) {
655
- for (const chunk of groundingMetadata.groundingChunks.slice(0, limitedResults)) {
656
- if (chunk.web) {
657
- searchResults.push({
658
- title: chunk.web.title || "No title",
659
- url: chunk.web.uri || "",
660
- snippet: searchContent, // Use full content since maxWords already limits length
661
- domain: chunk.web.uri
662
- ? new URL(chunk.web.uri).hostname
663
- : "unknown",
664
- });
665
- }
666
- }
667
- }
703
+ const searchResults = buildWebsearchResults(candidates[0]?.groundingMetadata, searchContent, limitedResults);
668
704
  // If no grounding metadata, create basic result structure
669
705
  if (searchResults.length === 0) {
670
706
  searchResults.push({