import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync } from 'fs'
import { Box, Text } from 'ink'
import { join } from 'path'
import * as React from 'react'
import { z } from 'zod'
import { FallbackToolUseRejectedMessage } from '../../components/FallbackToolUseRejectedMessage'
import { Tool } from '../../Tool'
import { MEMORY_DIR } from '../../utils/env'
import { resolveAgentId } from '../../utils/agentStorage'
import { DESCRIPTION, PROMPT } from './prompt'
const inputSchema = z.strictObject({
file_path: z
.string()
.optional()
.describe('Optional path to a specific memory file to read'),
})
export const MemoryReadTool = {
name: 'MemoryRead',
async description() {
return DESCRIPTION
},
async prompt() {
return PROMPT
},
inputSchema,
userFacingName() {
return 'Read Memory'
},
async isEnabled() {
// TODO: Use a statsig gate
// TODO: Figure out how to do that without regressing app startup perf
return false
},
isReadOnly() {
return true
},
isConcurrencySafe() {
return true // MemoryRead is read-only, safe for concurrent execution
},
needsPermissions() {
return false
},
renderResultForAssistant({ content }) {
return content
},
renderToolUseMessage(input) {
return Object.entries(input)
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
.join(', ')
},
renderToolUseRejectedMessage() {
return
},
renderToolResultMessage(output) {
return (
⎿
{output.content}
)
},
async validateInput({ file_path }, context) {
const agentId = resolveAgentId(context?.agentId)
const agentMemoryDir = join(MEMORY_DIR, 'agents', agentId)
if (file_path) {
const fullPath = join(agentMemoryDir, file_path)
if (!fullPath.startsWith(agentMemoryDir)) {
return { result: false, message: 'Invalid memory file path' }
}
if (!existsSync(fullPath)) {
return { result: false, message: 'Memory file does not exist' }
}
}
return { result: true }
},
async *call({ file_path }, context) {
const agentId = resolveAgentId(context?.agentId)
const agentMemoryDir = join(MEMORY_DIR, 'agents', agentId)
mkdirSync(agentMemoryDir, { recursive: true })
// If a specific file is requested, return its contents
if (file_path) {
const fullPath = join(agentMemoryDir, file_path)
if (!existsSync(fullPath)) {
throw new Error('Memory file does not exist')
}
const content = readFileSync(fullPath, 'utf-8')
yield {
type: 'result',
data: {
content,
},
resultForAssistant: this.renderResultForAssistant({ content }),
}
return
}
// Otherwise return the index and file list for this agent
const files = readdirSync(agentMemoryDir, { recursive: true })
.map(f => join(agentMemoryDir, f.toString()))
.filter(f => !lstatSync(f).isDirectory())
.map(f => `- ${f}`)
.join('\n')
const indexPath = join(agentMemoryDir, 'index.md')
const index = existsSync(indexPath) ? readFileSync(indexPath, 'utf-8') : ''
const quotes = "'''"
const content = `Here are the contents of the agent memory file, \`${indexPath}\`:
${quotes}
${index}
${quotes}
Files in the agent memory directory:
${files}`
yield {
type: 'result',
data: { content },
resultForAssistant: this.renderResultForAssistant({ content }),
}
},
} satisfies Tool