insertText.tsโข2.81 kB
import { z } from 'zod'
import { defineTool } from '../tools.js'
import util from '../util.js'
const CONTEXT_LINES = 2
const schema = z.object({
  file_path: z.string().min(1).describe('Path to the file'),
  from_line: z.number().int().min(1).describe('Starting line number (1-based)'),
  text: z.string().describe('Text to insert'),
  to_line: z.number().int().min(1).optional().describe('Replace up to this line number (1-based, inclusive). If omitted only inserts'),
})
const insertText = defineTool({
  id: 'insert_text',
  schema,
  description: util.trimLines(`
    Insert or replace text at precise line ranges in files
    - Ideal for direct line-number operations (from code citations like 12:15:file.ts) and large files where context-heavy editing is inefficient.
    - TIP: Combine with read_symbol (must use optimize: false!) to edit any symbol anywhere without knowing its file or line range!
  `),
  isReadOnly: false,
  fromArgs: ([filePath, fromLine, text, toLine]) => ({
    file_path: filePath, from_line: util.int(fromLine)!, text, to_line: util.int(toLine),
  }),
  handler: (args) => {
    const fullPath = util.resolve(args.file_path)
    const content = util.readFile(fullPath)
    const newContent = updateText(content, args.from_line, args.text, args.to_line)
    util.writeFile(fullPath, newContent)
    return getContext(newContent, args.from_line, args.text, fullPath)
  },
})
export default insertText
export function updateText(content: string, fromLine: number, text: string, toLine?: number): string {
  const endLine = toLine ?? fromLine
  if (endLine < fromLine) {
    throw new Error(`Invalid line range: to_line (${endLine}) cannot be less than from_line (${fromLine})`)
  }
  const lines = content === '' ? [] : content.split('\n')
  if (fromLine > lines.length + 1) {
    throw new Error(`from_line ${fromLine} is beyond file length (${lines.length} lines). Maximum allowed: ${lines.length + 1}`)
  }
  if (toLine && endLine > lines.length) {
    throw new Error(`to_line ${endLine} is beyond file length (${lines.length} lines). Maximum allowed: ${lines.length}`)
  }
  const linesToRemove = toLine ? (endLine - fromLine + 1) : 0
  const newLines = text.split('\n')
  lines.splice(fromLine - 1, linesToRemove, ...newLines)
  return lines.join('\n')
}
function getContext(content: string, fromLine: number, text: string, path: string): string {
  const lines = content.split('\n')
  const newLines = text.split('\n')
  // Show from_line - 2 to from_line + newLines.length + 2
  const startLine = Math.max(0, fromLine - 1 - CONTEXT_LINES)
  const endLine = Math.min(lines.length, fromLine - 1 + newLines.length + CONTEXT_LINES)
  const header = `=== ${startLine + 1}:${endLine + 1}:${path} ===`
  return `${header}\n${lines.slice(startLine, endLine).join('\n')}`
}