2025-05-20 11:36:21 -07:00
/ * *
* @license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
2025-06-02 14:55:51 -07:00
import { GroundingMetadata } from '@google/genai' ;
2025-07-17 16:25:23 -06:00
import { BaseTool , Icon , ToolResult } from './tools.js' ;
2025-07-07 23:48:44 -07:00
import { Type } from '@google/genai' ;
2025-05-20 11:36:21 -07:00
import { SchemaValidator } from '../utils/schemaValidator.js' ;
import { getErrorMessage } from '../utils/errors.js' ;
import { Config } from '../config/config.js' ;
import { getResponseText } from '../utils/generateContentResponseUtilities.js' ;
interface GroundingChunkWeb {
uri? : string ;
title? : string ;
}
interface GroundingChunkItem {
web? : GroundingChunkWeb ;
// Other properties might exist if needed in the future
}
interface GroundingSupportSegment {
startIndex : number ;
endIndex : number ;
text? : string ; // text is optional as per the example
}
interface GroundingSupportItem {
segment? : GroundingSupportSegment ;
groundingChunkIndices? : number [ ] ;
confidenceScores? : number [ ] ; // Optional as per example
}
/ * *
* Parameters for the WebSearchTool .
* /
export interface WebSearchToolParams {
/ * *
* The search query .
* /
query : string ;
}
/ * *
* Extends ToolResult to include sources for web search .
* /
export interface WebSearchToolResult extends ToolResult {
sources? : GroundingMetadata extends { groundingChunks : GroundingChunkItem [ ] }
? GroundingMetadata [ 'groundingChunks' ]
: GroundingChunkItem [ ] ;
}
/ * *
* A tool to perform web searches using Google Search via the Gemini API .
* /
export class WebSearchTool extends BaseTool <
WebSearchToolParams ,
WebSearchToolResult
> {
static readonly Name : string = 'google_web_search' ;
constructor ( private readonly config : Config ) {
super (
WebSearchTool . Name ,
'GoogleSearch' ,
'Performs a web search using Google Search (via the Gemini API) and returns the results. This tool is useful for finding information on the internet based on a query.' ,
2025-07-17 16:25:23 -06:00
Icon . Globe ,
2025-05-20 11:36:21 -07:00
{
2025-07-07 23:48:44 -07:00
type : Type . OBJECT ,
2025-05-20 11:36:21 -07:00
properties : {
query : {
2025-07-07 23:48:44 -07:00
type : Type . STRING ,
2025-05-20 11:36:21 -07:00
description : 'The search query to find information on the web.' ,
} ,
} ,
required : [ 'query' ] ,
} ,
) ;
}
2025-07-04 09:13:02 +09:00
/ * *
* Validates the parameters for the WebSearchTool .
* @param params The parameters to validate
* @returns An error message string if validation fails , null if valid
* /
2025-07-07 23:48:44 -07:00
validateParams ( params : WebSearchToolParams ) : string | null {
2025-08-11 16:12:41 -07:00
const errors = SchemaValidator . validate (
this . schema . parametersJsonSchema ,
params ,
) ;
2025-07-07 23:48:44 -07:00
if ( errors ) {
return errors ;
2025-05-20 11:36:21 -07:00
}
2025-07-07 23:48:44 -07:00
2025-05-20 11:36:21 -07:00
if ( ! params . query || params . query . trim ( ) === '' ) {
return "The 'query' parameter cannot be empty." ;
}
return null ;
}
getDescription ( params : WebSearchToolParams ) : string {
return ` Searching the web for: " ${ params . query } " ` ;
}
2025-06-02 14:55:51 -07:00
async execute (
params : WebSearchToolParams ,
signal : AbortSignal ,
) : Promise < WebSearchToolResult > {
2025-07-04 09:13:02 +09:00
const validationError = this . validateToolParams ( params ) ;
2025-05-20 11:36:21 -07:00
if ( validationError ) {
return {
llmContent : ` Error: Invalid parameters provided. Reason: ${ validationError } ` ,
returnDisplay : validationError ,
} ;
}
2025-06-02 14:55:51 -07:00
const geminiClient = this . config . getGeminiClient ( ) ;
2025-05-20 11:36:21 -07:00
try {
2025-06-02 14:55:51 -07:00
const response = await geminiClient . generateContent (
[ { role : 'user' , parts : [ { text : params.query } ] } ] ,
{ tools : [ { googleSearch : { } } ] } ,
signal ,
) ;
2025-05-20 11:36:21 -07:00
const responseText = getResponseText ( response ) ;
const groundingMetadata = response . candidates ? . [ 0 ] ? . groundingMetadata ;
const sources = groundingMetadata ? . groundingChunks as
| GroundingChunkItem [ ]
| undefined ;
const groundingSupports = groundingMetadata ? . groundingSupports as
| GroundingSupportItem [ ]
| undefined ;
if ( ! responseText || ! responseText . trim ( ) ) {
return {
llmContent : ` No search results or information found for query: " ${ params . query } " ` ,
returnDisplay : 'No information found.' ,
} ;
}
let modifiedResponseText = responseText ;
const sourceListFormatted : string [ ] = [ ] ;
if ( sources && sources . length > 0 ) {
sources . forEach ( ( source : GroundingChunkItem , index : number ) = > {
const title = source . web ? . title || 'Untitled' ;
const uri = source . web ? . uri || 'No URI' ;
sourceListFormatted . push ( ` [ ${ index + 1 } ] ${ title } ( ${ uri } ) ` ) ;
} ) ;
if ( groundingSupports && groundingSupports . length > 0 ) {
const insertions : Array < { index : number ; marker : string } > = [ ] ;
groundingSupports . forEach ( ( support : GroundingSupportItem ) = > {
if ( support . segment && support . groundingChunkIndices ) {
const citationMarker = support . groundingChunkIndices
. map ( ( chunkIndex : number ) = > ` [ ${ chunkIndex + 1 } ] ` )
. join ( '' ) ;
insertions . push ( {
index : support.segment.endIndex ,
marker : citationMarker ,
} ) ;
}
} ) ;
// Sort insertions by index in descending order to avoid shifting subsequent indices
insertions . sort ( ( a , b ) = > b . index - a . index ) ;
const responseChars = modifiedResponseText . split ( '' ) ; // Use new variable
insertions . forEach ( ( insertion ) = > {
// Fixed arrow function syntax
responseChars . splice ( insertion . index , 0 , insertion . marker ) ;
} ) ;
modifiedResponseText = responseChars . join ( '' ) ; // Assign back to modifiedResponseText
}
if ( sourceListFormatted . length > 0 ) {
modifiedResponseText +=
'\n\nSources:\n' + sourceListFormatted . join ( '\n' ) ; // Fixed string concatenation
}
}
return {
llmContent : ` Web search results for " ${ params . query } ": \ n \ n ${ modifiedResponseText } ` ,
returnDisplay : ` Search results for " ${ params . query } " returned. ` ,
sources ,
} ;
} catch ( error : unknown ) {
const errorMessage = ` Error during web search for query " ${ params . query } ": ${ getErrorMessage ( error ) } ` ;
console . error ( errorMessage , error ) ;
return {
llmContent : ` Error: ${ errorMessage } ` ,
returnDisplay : ` Error performing web search. ` ,
} ;
}
}
}