2026-01-12 11:22:56 +00:00
import { Type } from "@sinclair/typebox" ;
2026-01-27 21:57:15 -08:00
import type { MoltbotConfig } from "../../config/config.js" ;
import type { MemoryCitationsMode } from "../../config/types.memory.js" ;
2026-01-12 11:22:56 +00:00
import { getMemorySearchManager } from "../../memory/index.js" ;
2026-01-27 21:57:15 -08:00
import type { MemorySearchResult } from "../../memory/types.js" ;
2026-01-12 11:22:56 +00:00
import { resolveSessionAgentId } from "../agent-scope.js" ;
import { resolveMemorySearchConfig } from "../memory-search.js" ;
2026-01-27 21:57:15 -08:00
import type { AnyAgentTool } from "./common.js" ;
2026-01-12 11:22:56 +00:00
import { jsonResult , readNumberParam , readStringParam } from "./common.js" ;
const MemorySearchSchema = Type . Object ( {
query : Type.String ( ) ,
maxResults : Type.Optional ( Type . Number ( ) ) ,
minScore : Type.Optional ( Type . Number ( ) ) ,
} ) ;
const MemoryGetSchema = Type . Object ( {
path : Type.String ( ) ,
from : Type . Optional ( Type . Number ( ) ) ,
lines : Type.Optional ( Type . Number ( ) ) ,
} ) ;
export function createMemorySearchTool ( options : {
2026-01-27 21:57:15 -08:00
config? : MoltbotConfig ;
2026-01-12 11:22:56 +00:00
agentSessionKey? : string ;
} ) : AnyAgentTool | null {
const cfg = options . config ;
2026-01-27 21:57:15 -08:00
if ( ! cfg ) return null ;
2026-01-12 11:22:56 +00:00
const agentId = resolveSessionAgentId ( {
sessionKey : options.agentSessionKey ,
config : cfg ,
} ) ;
2026-01-27 21:57:15 -08:00
if ( ! resolveMemorySearchConfig ( cfg , agentId ) ) return null ;
2026-01-12 11:22:56 +00:00
return {
label : "Memory Search" ,
name : "memory_search" ,
description :
2026-01-17 18:53:48 +00:00
"Mandatory recall step: semantically search MEMORY.md + memory/*.md (and optional session transcripts) before answering questions about prior work, decisions, dates, people, preferences, or todos; returns top snippets with path + lines." ,
2026-01-12 11:22:56 +00:00
parameters : MemorySearchSchema ,
execute : async ( _toolCallId , params ) = > {
const query = readStringParam ( params , "query" , { required : true } ) ;
const maxResults = readNumberParam ( params , "maxResults" ) ;
const minScore = readNumberParam ( params , "minScore" ) ;
const { manager , error } = await getMemorySearchManager ( {
cfg ,
agentId ,
} ) ;
if ( ! manager ) {
return jsonResult ( { results : [ ] , disabled : true , error } ) ;
}
2026-01-17 09:45:45 +00:00
try {
2026-01-27 21:57:15 -08:00
const citationsMode = resolveMemoryCitationsMode ( cfg ) ;
const includeCitations = citationsMode !== "off" ;
const rawResults = await manager . search ( query , {
2026-01-17 09:45:45 +00:00
maxResults ,
minScore ,
sessionKey : options.agentSessionKey ,
} ) ;
const status = manager . status ( ) ;
2026-01-27 21:57:15 -08:00
const results = decorateCitations ( rawResults , includeCitations ) ;
2026-01-17 09:45:45 +00:00
return jsonResult ( {
results ,
provider : status.provider ,
model : status.model ,
fallback : status.fallback ,
2026-01-27 21:57:15 -08:00
citations : citationsMode ,
2026-01-17 09:45:45 +00:00
} ) ;
} catch ( err ) {
const message = err instanceof Error ? err.message : String ( err ) ;
return jsonResult ( { results : [ ] , disabled : true , error : message } ) ;
}
2026-01-12 11:22:56 +00:00
} ,
} ;
}
export function createMemoryGetTool ( options : {
2026-01-27 21:57:15 -08:00
config? : MoltbotConfig ;
2026-01-12 11:22:56 +00:00
agentSessionKey? : string ;
} ) : AnyAgentTool | null {
const cfg = options . config ;
2026-01-27 21:57:15 -08:00
if ( ! cfg ) return null ;
2026-01-12 11:22:56 +00:00
const agentId = resolveSessionAgentId ( {
sessionKey : options.agentSessionKey ,
config : cfg ,
} ) ;
2026-01-27 21:57:15 -08:00
if ( ! resolveMemorySearchConfig ( cfg , agentId ) ) return null ;
2026-01-12 11:22:56 +00:00
return {
label : "Memory Get" ,
name : "memory_get" ,
2026-01-12 23:29:44 +00:00
description :
2026-01-27 21:57:15 -08:00
"Safe snippet read from MEMORY.md or memory/*.md with optional from/lines; use after memory_search to pull only the needed lines and keep context small." ,
2026-01-12 11:22:56 +00:00
parameters : MemoryGetSchema ,
execute : async ( _toolCallId , params ) = > {
const relPath = readStringParam ( params , "path" , { required : true } ) ;
const from = readNumberParam ( params , "from" , { integer : true } ) ;
const lines = readNumberParam ( params , "lines" , { integer : true } ) ;
const { manager , error } = await getMemorySearchManager ( {
cfg ,
agentId ,
} ) ;
if ( ! manager ) {
return jsonResult ( { path : relPath , text : "" , disabled : true , error } ) ;
}
2026-01-17 09:45:45 +00:00
try {
const result = await manager . readFile ( {
relPath ,
from : from ? ? undefined ,
lines : lines ? ? undefined ,
} ) ;
return jsonResult ( result ) ;
} catch ( err ) {
const message = err instanceof Error ? err.message : String ( err ) ;
return jsonResult ( { path : relPath , text : "" , disabled : true , error : message } ) ;
}
2026-01-12 11:22:56 +00:00
} ,
} ;
}
2026-01-27 21:57:15 -08:00
function resolveMemoryCitationsMode ( cfg : MoltbotConfig ) : MemoryCitationsMode {
const mode = cfg . memory ? . citations ;
if ( mode === "on" || mode === "off" || mode === "auto" ) return mode ;
return "auto" ;
}
function decorateCitations ( results : MemorySearchResult [ ] , include : boolean ) : MemorySearchResult [ ] {
if ( ! include ) {
return results . map ( ( entry ) = > ( { . . . entry , citation : undefined } ) ) ;
}
return results . map ( ( entry ) = > {
const citation = formatCitation ( entry ) ;
const snippet = ` ${ entry . snippet . trim ( ) } \ n \ nSource: ${ citation } ` ;
return { . . . entry , citation , snippet } ;
} ) ;
}
function formatCitation ( entry : MemorySearchResult ) : string {
const lineRange =
entry . startLine === entry . endLine
? ` #L ${ entry . startLine } `
: ` #L ${ entry . startLine } -L ${ entry . endLine } ` ;
return ` ${ entry . path } ${ lineRange } ` ;
}