@juspay/neurolink 10.8.22 → 10.9.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 +6 -0
- package/dist/agent/directTools.d.ts +65 -0
- package/dist/agent/directTools.js +86 -50
- package/dist/browser/neurolink.min.js +390 -390
- package/dist/lib/agent/directTools.d.ts +65 -0
- package/dist/lib/agent/directTools.js +86 -50
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [10.9.0](https://github.com/juspay/neurolink/compare/v10.8.22...v10.9.0) (2026-08-05)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- **(agent):** migrate websearchGrounding to @google/genai ([b5f9e15](https://github.com/juspay/neurolink/commit/b5f9e15ed32392757ac9a9d9ec101b6c143f107d))
|
|
6
|
+
|
|
1
7
|
## [10.8.22](https://github.com/juspay/neurolink/compare/v10.8.21...v10.8.22) (2026-08-05)
|
|
2
8
|
|
|
3
9
|
### 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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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 =
|
|
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 {
|
|
609
|
-
const vertex_ai = new
|
|
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 =
|
|
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
|
-
|
|
623
|
-
|
|
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
|
-
|
|
642
|
-
|
|
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
|
|
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({
|