2025-07-11 09:29:08 -07:00
/ * *
* @license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
import { ToolResult } from '../tools/tools.js' ;
import {
Content ,
GenerateContentConfig ,
GenerateContentResponse ,
} from '@google/genai' ;
import { GeminiClient } from '../core/client.js' ;
import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js' ;
import { PartListUnion } from '@google/genai' ;
/ * *
* A function that summarizes the result of a tool execution .
*
* @param result The result of the tool execution .
* @returns The summary of the result .
* /
export type Summarizer = (
result : ToolResult ,
geminiClient : GeminiClient ,
abortSignal : AbortSignal ,
) = > Promise < string > ;
/ * *
* The default summarizer for tool results .
*
* @param result The result of the tool execution .
* @param geminiClient The Gemini client to use for summarization .
* @param abortSignal The abort signal to use for summarization .
* @returns The summary of the result .
* /
export const defaultSummarizer : Summarizer = (
result : ToolResult ,
_geminiClient : GeminiClient ,
_abortSignal : AbortSignal ,
) = > Promise . resolve ( JSON . stringify ( result . llmContent ) ) ;
// TODO: Move both these functions to utils
function partToString ( part : PartListUnion ) : string {
if ( ! part ) {
return '' ;
}
if ( typeof part === 'string' ) {
return part ;
}
if ( Array . isArray ( part ) ) {
return part . map ( partToString ) . join ( '' ) ;
}
if ( 'text' in part ) {
return part . text ? ? '' ;
}
return '' ;
}
function getResponseText ( response : GenerateContentResponse ) : string | null {
if ( response . candidates && response . candidates . length > 0 ) {
const candidate = response . candidates [ 0 ] ;
if (
candidate . content &&
candidate . content . parts &&
candidate . content . parts . length > 0
) {
return candidate . content . parts
. filter ( ( part ) = > part . text )
. map ( ( part ) = > part . text )
. join ( '' ) ;
}
}
return null ;
}
2025-07-15 10:22:31 -07:00
const SUMMARIZE_TOOL_OUTPUT_PROMPT = ` Summarize the following tool output to be a maximum of {maxOutputTokens} tokens. The summary should be concise and capture the main points of the tool output.
2025-07-11 09:29:08 -07:00
The summarization should be done based on the content that is provided . Here are the basic rules to follow :
1 . If the text is a directory listing or any output that is structural , use the history of the conversation to understand the context . Using this context try to understand what information we need from the tool output and return that as a response .
2 . If the text is text content and there is nothing structural that we need , summarize the text .
3 . If the text is the output of a shell command , use the history of the conversation to understand the context . Using this context try to understand what information we need from the tool output and return a summarization along with the stack trace of any error within the < error > < / error > tags . The stack trace should be complete and not truncated . If there are warnings , you should include them in the summary within < warning > < / warning > tags .
Text to summarize :
"{textToSummarize}"
Return the summary string which should first contain an overall summarization of text followed by the full stack trace of errors and warnings in the tool output .
` ;
export const llmSummarizer : Summarizer = ( result , geminiClient , abortSignal ) = >
summarizeToolOutput (
partToString ( result . llmContent ) ,
geminiClient ,
abortSignal ,
) ;
export async function summarizeToolOutput (
textToSummarize : string ,
geminiClient : GeminiClient ,
abortSignal : AbortSignal ,
2025-07-15 10:22:31 -07:00
maxOutputTokens : number = 2000 ,
2025-07-11 09:29:08 -07:00
) : Promise < string > {
2025-07-15 10:22:31 -07:00
// There is going to be a slight difference here since we are comparing length of string with maxOutputTokens.
// This is meant to be a ballpark estimation of if we need to summarize the tool output.
if ( ! textToSummarize || textToSummarize . length < maxOutputTokens ) {
2025-07-11 09:29:08 -07:00
return textToSummarize ;
}
const prompt = SUMMARIZE_TOOL_OUTPUT_PROMPT . replace (
2025-07-15 10:22:31 -07:00
'{maxOutputTokens}' ,
String ( maxOutputTokens ) ,
2025-07-11 09:29:08 -07:00
) . replace ( '{textToSummarize}' , textToSummarize ) ;
const contents : Content [ ] = [ { role : 'user' , parts : [ { text : prompt } ] } ] ;
2025-07-15 10:22:31 -07:00
const toolOutputSummarizerConfig : GenerateContentConfig = {
maxOutputTokens ,
} ;
2025-07-11 09:29:08 -07:00
try {
const parsedResponse = ( await geminiClient . generateContent (
contents ,
toolOutputSummarizerConfig ,
abortSignal ,
2025-07-15 10:22:31 -07:00
DEFAULT_GEMINI_FLASH_MODEL ,
2025-07-11 09:29:08 -07:00
) ) as unknown as GenerateContentResponse ;
return getResponseText ( parsedResponse ) || textToSummarize ;
} catch ( error ) {
console . error ( 'Failed to summarize tool output.' , error ) ;
return textToSummarize ;
}
}