MemoryReadTool.tsx
1 import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync } from 'fs' 2 import { Box, Text } from 'ink' 3 import { join } from 'path' 4 import * as React from 'react' 5 import { z } from 'zod' 6 import { FallbackToolUseRejectedMessage } from '../../components/FallbackToolUseRejectedMessage.js' 7 import { Tool } from '../../Tool.js' 8 import { MEMORY_DIR } from '../../utils/env.js' 9 import { DESCRIPTION, PROMPT } from './prompt.js' 10 11 const inputSchema = z.strictObject({ 12 file_path: z 13 .string() 14 .optional() 15 .describe('Optional path to a specific memory file to read'), 16 }) 17 18 export const MemoryReadTool = { 19 name: 'MemoryRead', 20 async description() { 21 return DESCRIPTION 22 }, 23 async prompt() { 24 return PROMPT 25 }, 26 inputSchema, 27 userFacingName() { 28 return 'Read Memory' 29 }, 30 async isEnabled() { 31 // TODO: Use a statsig gate 32 // TODO: Figure out how to do that without regressing app startup perf 33 return false 34 }, 35 isReadOnly() { 36 return true 37 }, 38 needsPermissions() { 39 return false 40 }, 41 renderResultForAssistant({ content }) { 42 return content 43 }, 44 renderToolUseMessage(input) { 45 return Object.entries(input) 46 .map(([key, value]) => `${key}: ${JSON.stringify(value)}`) 47 .join(', ') 48 }, 49 renderToolUseRejectedMessage() { 50 return <FallbackToolUseRejectedMessage /> 51 }, 52 renderToolResultMessage(output) { 53 return ( 54 <Box justifyContent="space-between" overflowX="hidden" width="100%"> 55 <Box flexDirection="row"> 56 <Text> ⎿ </Text> 57 <Text>{output.content}</Text> 58 </Box> 59 </Box> 60 ) 61 }, 62 async validateInput({ file_path }) { 63 if (file_path) { 64 const fullPath = join(MEMORY_DIR, file_path) 65 if (!fullPath.startsWith(MEMORY_DIR)) { 66 return { result: false, message: 'Invalid memory file path' } 67 } 68 if (!existsSync(fullPath)) { 69 return { result: false, message: 'Memory file does not exist' } 70 } 71 } 72 return { result: true } 73 }, 74 async *call({ file_path }) { 75 mkdirSync(MEMORY_DIR, { recursive: true }) 76 77 // If a specific file is requested, return its contents 78 if (file_path) { 79 const fullPath = join(MEMORY_DIR, file_path) 80 if (!existsSync(fullPath)) { 81 throw new Error('Memory file does not exist') 82 } 83 const content = readFileSync(fullPath, 'utf-8') 84 yield { 85 type: 'result', 86 data: { 87 content, 88 }, 89 resultForAssistant: this.renderResultForAssistant({ content }), 90 } 91 return 92 } 93 94 // Otherwise return the index and file list 95 const files = readdirSync(MEMORY_DIR, { recursive: true }) 96 .map(f => join(MEMORY_DIR, f.toString())) 97 .filter(f => !lstatSync(f).isDirectory()) 98 .map(f => `- ${f}`) 99 .join('\n') 100 101 const indexPath = join(MEMORY_DIR, 'index.md') 102 const index = existsSync(indexPath) ? readFileSync(indexPath, 'utf-8') : '' 103 104 const quotes = "'''" 105 const content = `Here are the contents of the root memory file, \`${indexPath}\`: 106 ${quotes} 107 ${index} 108 ${quotes} 109 110 Files in the memory directory: 111 ${files}` 112 yield { 113 type: 'result', 114 data: { content }, 115 resultForAssistant: this.renderResultForAssistant({ content }), 116 } 117 }, 118 } satisfies Tool<typeof inputSchema, { content: string }>