AI Code Editing
AI-powered code editing tools for agents to read, write, and modify code files with precision using SEARCH/REPLACE diffs
from praisonaiagents import Agent
agent = Agent(name="code-agent", instructions="Write and review Python code.")
agent.start("Write a function to check if a number is prime.")PraisonAI Code provides AI agents with powerful tools to read, write, and modify code files with surgical precision. Inspired by Kilo Code’s architecture, it uses a SEARCH/REPLACE diff strategy with fuzzy matching for reliable code modifications.
The user asks the agent to edit project files; read, write, and SEARCH/REPLACE diffs apply changes safely.
How It Works
Quick Start
- Set the workspace
python from praisonai.code import set_workspace, code_read_file, code_apply_diff set_workspace("/path/to/your/project")When you launch
praisonai codefrom the terminal, the CLI resolves the workspace from--workspace/PRAISONAI_WORKSPACE/ the resumed session’s directory and callsset_workspacefor you (see Workspace directory). Callset_workspaceyourself only when embedding the code tools in your own Python script. - Read and patch a file
python content = code_read_file("src/main.py") print(content) diff = """<<<<<<< SEARCH :start_line:10 ------- def old_function(): pass ======= def new_function(): return True >>>>>>> REPLACE""" result = code_apply_diff("src/main.py", diff) print(result) # "Successfully applied 1 change(s) to src/main.py"
Available Tools
| Tool | Description |
|---|---|
code_read_file | Read file contents with optional line ranges |
code_write_file | Create or overwrite files |
code_list_files | List directory contents with filtering |
code_apply_diff | Apply SEARCH/REPLACE diffs with fuzzy matching |
code_search_replace | Simple search and replace operations |
code_execute_command | Run shell commands safely |
Using with Agents
The CODE_TOOLS list contains all tools ready for use with PraisonAI agents:
from praisonaiagents import Agent
from praisonai.code import CODE_TOOLS, set_workspace
# Set workspace before creating agent
set_workspace("/path/to/project")
# Create an agent with code editing capabilities
agent = Agent(
name="Code Editor",
instructions="""You are a code editing assistant.
Use the code tools to read, modify, and write files.
Always read a file before modifying it.""",
tools=CODE_TOOLS
)
# The agent can now edit code
agent.start("Add error handling to the main.py file")Tool Reference
code_read_file
Read file contents with optional line ranges and line number annotations.
from praisonai.code import code_read_file, set_workspace
set_workspace("/my/project")
# Read entire file
content = code_read_file("src/main.py")
# Read specific lines (1-indexed)
content = code_read_file("src/main.py", start_line=10, end_line=50)Output format:
File: src/main.py (150 lines)
1 | def hello():
2 | print("Hello, World!")
3 |
4 | def main():
5 | hello()code_write_file
Create new files or completely replace existing files.
from praisonai.code import code_write_file, set_workspace
set_workspace("/my/project")
# Create a new file
result = code_write_file("src/new_module.py", """
def greet(name):
return f"Hello, {name}!"
""")
print(result) # "Created file: src/new_module.py (45 bytes)"
# Overwrite existing file
result = code_write_file("src/config.py", "DEBUG = True")
print(result) # "Updated file: src/config.py (12 bytes)"code_list_files
List files and directories with optional filtering.
from praisonai.code import code_list_files, set_workspace
set_workspace("/my/project")
# List current directory
files = code_list_files(".")
# List recursively with extension filter
files = code_list_files("src", recursive=True, extensions="py,js")Output format:
Contents of src:
📁 utils/
📁 models/
📄 main.py (2.5KB)
📄 config.py (512B)
📄 helpers.js (1.2KB)code_apply_diff
Apply precise, surgical modifications using SEARCH/REPLACE diffs.
from praisonai.code import code_apply_diff, set_workspace
set_workspace("/my/project")
diff = """<<<<<<< SEARCH
:start_line:5
-------
def calculate_total(items):
total = 0
for item in items:
total += item
return total
=======
def calculate_total(items):
\"\"\"Calculate total with 10% markup.\"\"\"
return sum(item * 1.1 for item in items)
>>>>>>> REPLACE"""
result = code_apply_diff("src/utils.py", diff)Diff Format
The diff format uses clear markers:
<<<<<<< SEARCH
:start_line:N
-------
[exact content to find]
=======
[new content to replace with]
>>>>>>> REPLACE:start_line:N– Optional line number hint for faster matching-------– Separator after line hints=======– Divider between search and replace content- Multiple blocks can be included in a single diff
Multiple Changes
Apply multiple changes in one operation:
diff = """<<<<<<< SEARCH
:start_line:1
-------
import os
=======
import os
import sys
>>>>>>> REPLACE
<<<<<<< SEARCH
:start_line:20
-------
def old_function():
pass
=======
def new_function():
return True
>>>>>>> REPLACE"""
result = code_apply_diff("src/main.py", diff)
# "Successfully applied 2 change(s) to src/main.py"code_search_replace
Simple search and replace for straightforward text substitutions.
from praisonai.code import code_search_replace, set_workspace
set_workspace("/my/project")
# Simple text replacement
result = code_search_replace(
"src/main.py",
search="old_variable_name",
replace="new_variable_name"
)
print(result) # "Replaced 5 occurrence(s) in src/main.py"
# Regex replacement
result = code_search_replace(
"src/main.py",
search=r"def (\w+)\(",
replace=r"def renamed_\1(",
is_regex=True
)code_execute_command
Execute shell commands safely within the workspace.
from praisonai.code import code_execute_command, set_workspace
set_workspace("/my/project")
# Run tests
result = code_execute_command("python -m pytest tests/")
print(result)
# Run linter
result = code_execute_command("ruff check src/")
# Run in subdirectory
result = code_execute_command("npm test", cwd="frontend")Fuzzy Matching
The diff strategy includes fuzzy matching using Levenshtein distance, which helps when:
- Code has been slightly modified since you read it
- There are minor whitespace differences
- Line numbers have shifted due to other changes
from praisonai.code import apply_diff
# The tool uses a 95% similarity threshold by default
# This allows for minor variations while preventing incorrect matchesIndentation Preservation
When applying diffs, indentation is automatically preserved:
# Original file:
class MyClass:
def method(self):
pass
# Diff with different base indentation:
diff = """<<<<<<< SEARCH
:start_line:2
-------
def method(self):
pass
=======
def method(self):
return self.value
>>>>>>> REPLACE"""
# Result maintains correct indentation:
class MyClass:
def method(self):
return self.valueSecurity Features
Workspace Isolation
All file operations are restricted to the configured workspace:
set_workspace("/my/project")
# This works - within workspace
code_read_file("src/main.py")
# This fails - path traversal blocked
code_read_file("../../../etc/passwd")
# Error: Path '../../../etc/passwd' is outside the workspaceGitignore Support
The code_list_files tool respects .gitignore patterns by default:
# Files in .gitignore are automatically excluded
files = code_list_files(".", recursive=True)
# node_modules/, __pycache__/, .git/ are excludedSafe Command Execution
Commands are executed with safety checks:
from praisonai.code.tools.execute_command import is_safe_command
# Safe commands
is_safe_command("python test.py") # True
is_safe_command("npm run build") # True
# Potentially dangerous commands
is_safe_command("rm -rf /") # False
is_safe_command("sudo apt install") # FalseCrash-Safe Writes
code_write_file, code_search_replace, and code_apply_diff write files atomically. Each write goes to a temp file in the target’s directory, is flushed and fsync‘d, then swapped over the target with os.replace. If the agent is cancelled, times out, is SIGTERM‘d, or the disk fills up mid-write, the original file is untouched.
graph LR
subgraph "Crash-safe write"
Call["📝 code_write_file /<br/>search_replace /<br/>apply_diff"] --> Tmp["📄 temp file<br/>.tmp_<hex>"]
Tmp --> Fsync["💾 fsync"]
Fsync --> Replace["♻️ os.replace<br/>(atomic)"]
Replace --> Done["✅ Target updated"]
Call -. interrupt .-> Keep["🛡️ Original intact"]
Tmp -. interrupt .-> Keep
Fsync -. interrupt .-> Keep
end
classDef call fill:#8B0000,stroke:#7C90A0,color:#fff
classDef io fill:#189AB4,stroke:#7C90A0,color:#fff
classDef ok fill:#10B981,stroke:#7C90A0,color:#fff
classDef keep fill:#F59E0B,stroke:#7C90A0,color:#fff
class Call call
class Tmp,Fsync io
class Replace,Done ok
class Keep keep| Guarantee | What it prevents |
|---|---|
| Atomic replace | A cancellation / SIGTERM / disk-full mid-write leaving the file zero-byte or half-written |
fsync before rename | Power loss leaving an empty file after apparent success |
| Permission bits preserved | A 0o755 script turning into 0o600; a 0o600 .env becoming world-readable |
| Owner preserved (POSIX, best-effort) | A group-/shared-owned file silently changing owner |
| Symlinks written through, not replaced | A symlink being turned into a plain file |
Read-only targets refused (PermissionError) | An inode swap bypassing a target’s read-only permissions |
Nothing changes for the caller when a write succeeds — same return value, same file. On failure, the temp file (.tmp_<hex> next to the target) is removed before the error propagates.
Low-Level API
For advanced use cases, you can use the low-level functions directly:
from praisonai.code import (
read_file,
write_file,
apply_diff,
DiffResult,
apply_search_replace_diff,
)
# Low-level read (returns dict)
result = read_file("src/main.py", workspace="/my/project")
if result['success']:
print(result['content'])
print(f"Total lines: {result['total_lines']}")
# Low-level diff application
from praisonai.code.diff import apply_search_replace_diff
result = apply_search_replace_diff(
original_content="def old(): pass",
diff_content=diff,
fuzzy_threshold=0.95, # 95% similarity required
buffer_lines=40, # Search range around line hints
)
if result.success:
print(result.content)
else:
print(f"Error: {result.error}")Helper Functions
Creating Diff Blocks Programmatically
from praisonai.code import create_diff_block, create_multi_diff
# Create a single diff block
diff = create_diff_block(
search_content="def old():\n pass",
replace_content="def new():\n return True",
start_line=10
)
# Create multiple diff blocks
diff = create_multi_diff([
{'search': 'old1', 'replace': 'new1', 'start_line': 5},
{'search': 'old2', 'replace': 'new2', 'start_line': 20},
])Best Practices
Always read before modifying
Read the file first to get the exact content for your SEARCH block:
content = code_read_file("src/main.py")
# Use the exact content from the file in your diffUse line number hints
Include :start_line:N for faster and more accurate matching:
<<<<<<< SEARCH
:start_line:42
-------Make atomic changes
Group related changes in a single diff, but keep unrelated changes separate for easier debugging.
Trust the tools on cancellation
code_write_file, code_search_replace, and code_apply_diff write atomically — if the agent is cancelled, times out, or is killed mid-write, the original file is untouched. You don’t need a manual backup for crash safety (you may still want one for content-level rollback).
Verify changes
After applying a diff, read the file again or run tests to verify:
result = code_apply_diff("src/main.py", diff)
if "Successfully" in result:
test_result = code_execute_command("python -m pytest tests/")Complete Example
Here’s a complete workflow showing an agent that adds error handling to a function:
from praisonaiagents import Agent
from praisonai.code import CODE_TOOLS, set_workspace
# Configure workspace
set_workspace("/path/to/my/project")
# Create coding agent
agent = Agent(
name="Error Handler",
instructions="""You are a Python expert. Your task is to:
1. Read the specified file
2. Identify functions without error handling
3. Add appropriate try/except blocks
4. Verify the changes compile correctly
Always use code_read_file before making changes.
Use code_apply_diff for precise modifications.
Run tests after making changes.""",
tools=CODE_TOOLS
)
# Run the agent
result = agent.start(
"Add error handling to all database functions in src/database.py"
)
print(result)