2025-05-16 16:36:50 -07:00
/ * *
* @license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
2025-08-26 00:04:53 +02:00
import type { ToolEditConfirmationDetails , ToolResult } from './tools.js' ;
2025-07-30 15:21:31 -07:00
import {
2025-08-13 11:57:37 -07:00
BaseDeclarativeTool ,
BaseToolInvocation ,
2025-08-13 12:58:26 -03:00
Kind ,
2025-07-30 15:21:31 -07:00
ToolConfirmationOutcome ,
} from './tools.js' ;
2025-08-26 00:04:53 +02:00
import type { FunctionDeclaration } from '@google/genai' ;
2025-08-25 22:11:27 +02:00
import * as fs from 'node:fs/promises' ;
import * as path from 'node:path' ;
2025-08-20 10:55:47 +09:00
import { Storage } from '../config/storage.js' ;
2025-07-30 15:21:31 -07:00
import * as Diff from 'diff' ;
import { DEFAULT_DIFF_OPTIONS } from './diffOptions.js' ;
import { tildeifyPath } from '../utils/paths.js' ;
2025-08-26 00:04:53 +02:00
import type {
ModifiableDeclarativeTool ,
ModifyContext ,
} from './modifiable-tool.js' ;
2025-08-21 14:40:18 -07:00
import { ToolErrorType } from './tool-error.js' ;
2025-10-19 20:53:53 -04:00
import { MEMORY_TOOL_NAME } from './tool-names.js' ;
2025-10-24 13:04:40 -07:00
import type { MessageBus } from '../confirmation-bus/message-bus.js' ;
2025-05-16 16:36:50 -07:00
2025-07-07 23:48:44 -07:00
const memoryToolSchemaData : FunctionDeclaration = {
2025-10-19 20:53:53 -04:00
name : MEMORY_TOOL_NAME ,
2025-05-16 16:36:50 -07:00
description :
'Saves a specific piece of information or fact to your long-term memory. Use this when the user explicitly asks you to remember something, or when they state a clear, concise fact that seems important to retain for future interactions.' ,
2025-08-11 16:12:41 -07:00
parametersJsonSchema : {
type : 'object' ,
2025-05-16 16:36:50 -07:00
properties : {
fact : {
2025-08-11 16:12:41 -07:00
type : 'string' ,
2025-05-16 16:36:50 -07:00
description :
'The specific fact or piece of information to remember. Should be a clear, self-contained statement.' ,
} ,
} ,
required : [ 'fact' ] ,
} ,
} ;
const memoryToolDescription = `
Saves a specific piece of information or fact to your long - term memory .
Use this tool :
- When the user explicitly asks you to remember something ( e . g . , "Remember that I like pineapple on pizza" , "Please save this: my cat's name is Whiskers" ) .
- When the user states a clear , concise fact about themselves , their preferences , or their environment that seems important for you to retain for future interactions to provide a more personalized and effective assistance .
Do NOT use this tool :
- To remember conversational context that is only relevant for the current session .
- To save long , complex , or rambling pieces of text . The fact should be relatively short and to the point .
- If you are unsure whether the information is a fact worth remembering long - term . If in doubt , you can ask the user , "Should I remember that for you?"
# # Parameters
2025-10-24 13:04:40 -07:00
- \ ` fact \` (string, required): The specific fact or piece of information to remember. This should be a clear, self-contained statement. For example, if the user says "My favorite color is blue", the fact would be "My favorite color is blue". ` ;
2025-05-16 16:36:50 -07:00
2025-05-31 12:49:28 -07:00
export const DEFAULT_CONTEXT_FILENAME = 'GEMINI.md' ;
2025-05-16 16:36:50 -07:00
export const MEMORY_SECTION_HEADER = '## Gemini Added Memories' ;
2025-05-31 12:49:28 -07:00
// This variable will hold the currently configured filename for GEMINI.md context files.
// It defaults to DEFAULT_CONTEXT_FILENAME but can be overridden by setGeminiMdFilename.
2025-06-13 09:19:08 -07:00
let currentGeminiMdFilename : string | string [ ] = DEFAULT_CONTEXT_FILENAME ;
export function setGeminiMdFilename ( newFilename : string | string [ ] ) : void {
if ( Array . isArray ( newFilename ) ) {
if ( newFilename . length > 0 ) {
currentGeminiMdFilename = newFilename . map ( ( name ) = > name . trim ( ) ) ;
}
} else if ( newFilename && newFilename . trim ( ) !== '' ) {
2025-05-31 12:49:28 -07:00
currentGeminiMdFilename = newFilename . trim ( ) ;
}
}
export function getCurrentGeminiMdFilename ( ) : string {
2025-06-13 09:19:08 -07:00
if ( Array . isArray ( currentGeminiMdFilename ) ) {
return currentGeminiMdFilename [ 0 ] ;
}
2025-05-31 12:49:28 -07:00
return currentGeminiMdFilename ;
}
2025-06-13 09:19:08 -07:00
export function getAllGeminiMdFilenames ( ) : string [ ] {
if ( Array . isArray ( currentGeminiMdFilename ) ) {
return currentGeminiMdFilename ;
}
return [ currentGeminiMdFilename ] ;
}
2025-05-16 16:36:50 -07:00
interface SaveMemoryParams {
fact : string ;
2025-07-30 15:21:31 -07:00
modified_by_user? : boolean ;
modified_content? : string ;
2025-05-16 16:36:50 -07:00
}
2025-10-14 02:31:39 +09:00
export function getGlobalMemoryFilePath ( ) : string {
2025-08-20 10:55:47 +09:00
return path . join ( Storage . getGlobalGeminiDir ( ) , getCurrentGeminiMdFilename ( ) ) ;
2025-05-16 16:36:50 -07:00
}
/ * *
* Ensures proper newline separation before appending content .
* /
function ensureNewlineSeparation ( currentContent : string ) : string {
if ( currentContent . length === 0 ) return '' ;
if ( currentContent . endsWith ( '\n\n' ) || currentContent . endsWith ( '\r\n\r\n' ) )
return '' ;
if ( currentContent . endsWith ( '\n' ) || currentContent . endsWith ( '\r\n' ) )
return '\n' ;
return '\n\n' ;
}
2025-08-13 11:57:37 -07:00
/ * *
* Reads the current content of the memory file
* /
async function readMemoryFileContent ( ) : Promise < string > {
try {
return await fs . readFile ( getGlobalMemoryFilePath ( ) , 'utf-8' ) ;
} catch ( err ) {
const error = err as Error & { code? : string } ;
if ( ! ( error instanceof Error ) || error . code !== 'ENOENT' ) throw err ;
return '' ;
2025-07-30 15:21:31 -07:00
}
2025-08-13 11:57:37 -07:00
}
2025-07-30 15:21:31 -07:00
2025-08-13 11:57:37 -07:00
/ * *
* Computes the new content that would result from adding a memory entry
* /
function computeNewContent ( currentContent : string , fact : string ) : string {
let processedText = fact . trim ( ) ;
processedText = processedText . replace ( /^(-+\s*)+/ , '' ) . trim ( ) ;
const newMemoryItem = ` - ${ processedText } ` ;
const headerIndex = currentContent . indexOf ( MEMORY_SECTION_HEADER ) ;
if ( headerIndex === - 1 ) {
// Header not found, append header and then the entry
const separator = ensureNewlineSeparation ( currentContent ) ;
return (
currentContent +
` ${ separator } ${ MEMORY_SECTION_HEADER } \ n ${ newMemoryItem } \ n `
) ;
} else {
// Header found, find where to insert the new memory entry
const startOfSectionContent = headerIndex + MEMORY_SECTION_HEADER . length ;
let endOfSectionIndex = currentContent . indexOf (
'\n## ' ,
startOfSectionContent ,
) ;
if ( endOfSectionIndex === - 1 ) {
endOfSectionIndex = currentContent . length ; // End of file
2025-07-30 15:21:31 -07:00
}
2025-08-13 11:57:37 -07:00
const beforeSectionMarker = currentContent
. substring ( 0 , startOfSectionContent )
. trimEnd ( ) ;
let sectionContent = currentContent
. substring ( startOfSectionContent , endOfSectionIndex )
. trimEnd ( ) ;
const afterSectionMarker = currentContent . substring ( endOfSectionIndex ) ;
sectionContent += ` \ n ${ newMemoryItem } ` ;
return (
` ${ beforeSectionMarker } \ n ${ sectionContent . trimStart ( ) } \ n ${ afterSectionMarker } ` . trimEnd ( ) +
'\n'
) ;
}
}
2025-07-30 15:21:31 -07:00
2025-08-13 11:57:37 -07:00
class MemoryToolInvocation extends BaseToolInvocation <
SaveMemoryParams ,
ToolResult
> {
private static readonly allowlist : Set < string > = new Set ( ) ;
2025-07-30 15:21:31 -07:00
2025-10-24 13:04:40 -07:00
constructor (
params : SaveMemoryParams ,
2026-01-04 17:11:43 -05:00
messageBus : MessageBus ,
2025-10-24 13:04:40 -07:00
toolName? : string ,
displayName? : string ,
) {
super ( params , messageBus , toolName , displayName ) ;
}
2025-08-13 11:57:37 -07:00
getDescription ( ) : string {
const memoryFilePath = getGlobalMemoryFilePath ( ) ;
return ` in ${ tildeifyPath ( memoryFilePath ) } ` ;
2025-07-30 15:21:31 -07:00
}
2025-10-24 13:04:40 -07:00
protected override async getConfirmationDetails (
2025-07-30 15:21:31 -07:00
_abortSignal : AbortSignal ,
) : Promise < ToolEditConfirmationDetails | false > {
const memoryFilePath = getGlobalMemoryFilePath ( ) ;
const allowlistKey = memoryFilePath ;
2025-08-13 11:57:37 -07:00
if ( MemoryToolInvocation . allowlist . has ( allowlistKey ) ) {
2025-07-30 15:21:31 -07:00
return false ;
}
2025-08-13 11:57:37 -07:00
const currentContent = await readMemoryFileContent ( ) ;
const newContent = computeNewContent ( currentContent , this . params . fact ) ;
2025-07-30 15:21:31 -07:00
const fileName = path . basename ( memoryFilePath ) ;
const fileDiff = Diff . createPatch (
fileName ,
currentContent ,
newContent ,
'Current' ,
'Proposed' ,
DEFAULT_DIFF_OPTIONS ,
) ;
const confirmationDetails : ToolEditConfirmationDetails = {
type : 'edit' ,
title : ` Confirm Memory Save: ${ tildeifyPath ( memoryFilePath ) } ` ,
fileName : memoryFilePath ,
2025-08-06 17:36:05 +00:00
filePath : memoryFilePath ,
2025-07-30 15:21:31 -07:00
fileDiff ,
originalContent : currentContent ,
newContent ,
onConfirm : async ( outcome : ToolConfirmationOutcome ) = > {
if ( outcome === ToolConfirmationOutcome . ProceedAlways ) {
2025-08-13 11:57:37 -07:00
MemoryToolInvocation . allowlist . add ( allowlistKey ) ;
2025-07-30 15:21:31 -07:00
}
2025-12-12 13:45:39 -08:00
await this . publishPolicyUpdate ( outcome ) ;
2025-07-30 15:21:31 -07:00
} ,
} ;
return confirmationDetails ;
}
2025-08-13 11:57:37 -07:00
async execute ( _signal : AbortSignal ) : Promise < ToolResult > {
const { fact , modified_by_user , modified_content } = this . params ;
try {
if ( modified_by_user && modified_content !== undefined ) {
// User modified the content in external editor, write it directly
await fs . mkdir ( path . dirname ( getGlobalMemoryFilePath ( ) ) , {
recursive : true ,
} ) ;
await fs . writeFile (
getGlobalMemoryFilePath ( ) ,
modified_content ,
'utf-8' ,
) ;
const successMessage = ` Okay, I've updated the memory file with your modifications. ` ;
return {
llmContent : JSON.stringify ( {
success : true ,
message : successMessage ,
} ) ,
returnDisplay : successMessage ,
} ;
} else {
// Use the normal memory entry logic
await MemoryTool . performAddMemoryEntry (
fact ,
getGlobalMemoryFilePath ( ) ,
{
readFile : fs.readFile ,
writeFile : fs.writeFile ,
mkdir : fs.mkdir ,
} ,
) ;
const successMessage = ` Okay, I've remembered that: " ${ fact } " ` ;
return {
llmContent : JSON.stringify ( {
success : true ,
message : successMessage ,
} ) ,
returnDisplay : successMessage ,
} ;
}
} catch ( error ) {
const errorMessage =
error instanceof Error ? error.message : String ( error ) ;
return {
llmContent : JSON.stringify ( {
success : false ,
error : ` Failed to save memory. Detail: ${ errorMessage } ` ,
} ) ,
returnDisplay : ` Error saving memory: ${ errorMessage } ` ,
2025-08-21 14:40:18 -07:00
error : {
message : errorMessage ,
type : ToolErrorType . MEMORY_TOOL_EXECUTION_ERROR ,
} ,
2025-08-13 11:57:37 -07:00
} ;
}
}
}
export class MemoryTool
extends BaseDeclarativeTool < SaveMemoryParams , ToolResult >
implements ModifiableDeclarativeTool < SaveMemoryParams >
{
2025-10-20 22:35:35 -04:00
static readonly Name = MEMORY_TOOL_NAME ;
2026-01-04 17:11:43 -05:00
constructor ( messageBus : MessageBus ) {
2025-08-13 11:57:37 -07:00
super (
2025-10-20 22:35:35 -04:00
MemoryTool . Name ,
2025-11-11 20:28:13 -08:00
'SaveMemory' ,
2025-08-13 11:57:37 -07:00
memoryToolDescription ,
Kind . Think ,
memoryToolSchemaData . parametersJsonSchema as Record < string , unknown > ,
2026-01-04 17:11:43 -05:00
messageBus ,
2025-10-24 13:04:40 -07:00
true ,
false ,
2025-08-13 11:57:37 -07:00
) ;
}
2025-08-19 13:55:06 -07:00
protected override validateToolParamValues (
params : SaveMemoryParams ,
) : string | null {
2025-08-13 11:57:37 -07:00
if ( params . fact . trim ( ) === '' ) {
return 'Parameter "fact" must be a non-empty string.' ;
}
return null ;
}
2025-10-24 13:04:40 -07:00
protected createInvocation (
params : SaveMemoryParams ,
2026-01-04 17:11:43 -05:00
messageBus : MessageBus ,
2025-10-24 13:04:40 -07:00
toolName? : string ,
displayName? : string ,
) {
return new MemoryToolInvocation (
params ,
2026-01-04 17:11:43 -05:00
messageBus ,
2025-10-24 13:04:40 -07:00
toolName ? ? this . name ,
displayName ? ? this . displayName ,
) ;
2025-08-13 11:57:37 -07:00
}
2025-05-16 16:36:50 -07:00
static async performAddMemoryEntry (
text : string ,
memoryFilePath : string ,
fsAdapter : {
readFile : ( path : string , encoding : 'utf-8' ) = > Promise < string > ;
writeFile : (
path : string ,
data : string ,
encoding : 'utf-8' ,
) = > Promise < void > ;
mkdir : (
path : string ,
options : { recursive : boolean } ,
) = > Promise < string | undefined > ;
} ,
) : Promise < void > {
try {
await fsAdapter . mkdir ( path . dirname ( memoryFilePath ) , { recursive : true } ) ;
2025-08-30 01:35:00 +08:00
let currentContent = '' ;
2025-05-16 16:36:50 -07:00
try {
2025-08-30 01:35:00 +08:00
currentContent = await fsAdapter . readFile ( memoryFilePath , 'utf-8' ) ;
2025-05-16 16:36:50 -07:00
} catch ( _e ) {
2025-08-30 01:35:00 +08:00
// File doesn't exist, which is fine. currentContent will be empty.
2025-05-16 16:36:50 -07:00
}
2025-08-30 01:35:00 +08:00
const newContent = computeNewContent ( currentContent , text ) ;
2025-05-16 16:36:50 -07:00
2025-08-30 01:35:00 +08:00
await fsAdapter . writeFile ( memoryFilePath , newContent , 'utf-8' ) ;
2025-05-16 16:36:50 -07:00
} catch ( error ) {
throw new Error (
` [MemoryTool] Failed to add memory entry: ${ error instanceof Error ? error.message : String ( error ) } ` ,
) ;
}
}
2025-07-30 15:21:31 -07:00
getModifyContext ( _abortSignal : AbortSignal ) : ModifyContext < SaveMemoryParams > {
return {
getFilePath : ( _params : SaveMemoryParams ) = > getGlobalMemoryFilePath ( ) ,
getCurrentContent : async ( _params : SaveMemoryParams ) : Promise < string > = >
2025-08-13 11:57:37 -07:00
readMemoryFileContent ( ) ,
2025-07-30 15:21:31 -07:00
getProposedContent : async ( params : SaveMemoryParams ) : Promise < string > = > {
2025-08-13 11:57:37 -07:00
const currentContent = await readMemoryFileContent ( ) ;
return computeNewContent ( currentContent , params . fact ) ;
2025-07-30 15:21:31 -07:00
} ,
createUpdatedParams : (
_oldContent : string ,
modifiedProposedContent : string ,
originalParams : SaveMemoryParams ,
) : SaveMemoryParams = > ( {
. . . originalParams ,
modified_by_user : true ,
modified_content : modifiedProposedContent ,
} ) ,
} ;
}
2025-05-16 16:36:50 -07:00
}