Code Editing
AI-powered code editing with SEARCH/REPLACE diff format
Code Editing
PraisonAI provides a powerful code editing module for AI-powered code manipulation, inspired by Kilo Code’s architecture.
Installation
The code editing module is included with PraisonAI:
pip install praisonaiQuick Start
from praisonai.code import (
set_workspace,
code_read_file,
code_write_file,
code_apply_diff,
code_execute_command,
CODE_TOOLS
)
# Set the workspace directory
set_workspace("/path/to/project")
# Read a file with line numbers
content = code_read_file("src/main.py")
print(content)
# Write to a file
code_write_file("src/new_file.py", "print('Hello, World!')")
# Execute a command
output = code_execute_command("python --version")SEARCH/REPLACE Diff Format
The code editing module uses a SEARCH/REPLACE diff format for precise code modifications:
from praisonai.code import code_apply_diff
diff = """
<<<<<<< SEARCH
def old_function():
return "old"
=======
def new_function():
return "new"
>>>>>>> REPLACE
"""
result = code_apply_diff("src/main.py", diff)
print(result) # "Successfully applied diff to src/main.py"Line Number Hints
For faster matching in large files, use line number hints:
diff = """
<<<<<<< SEARCH :start_line:42
def target_function():
pass
=======
def target_function():
return "updated"
>>>>>>> REPLACE
"""
result = code_apply_diff("src/large_file.py", diff)Available Tools
code_read_file
Read file contents with optional line range:
from praisonai.code import code_read_file
# 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=20)code_write_file
Create or overwrite a file:
from praisonai.code import code_write_file
code_write_file("src/new_file.py", """
def hello():
print("Hello, World!")
""")code_apply_diff
Apply SEARCH/REPLACE diffs:
from praisonai.code import code_apply_diff
diff = """
<<<<<<< SEARCH
old_code
=======
new_code
>>>>>>> REPLACE
"""
result = code_apply_diff("src/file.py", diff)code_search_replace
Apply multiple search/replace operations:
from praisonai.code import code_search_replace
operations = [
{"search": "old_name", "replace": "new_name"},
{"search": "deprecated_func", "replace": "new_func"}
]
result = code_search_replace("src/file.py", operations)code_list_files
List files in a directory:
from praisonai.code import code_list_files
files = code_list_files("src/", recursive=True)
for f in files:
print(f)code_execute_command
Execute shell commands:
from praisonai.code import code_execute_command
output = code_execute_command("python -m pytest tests/")
print(output)Commands run without a shell, so shell operators are rejected rather than passed through as literal arguments.
code_run_python
Execute Python source in a subprocess (not a sandbox — the child inherits the current interpreter and environment). Source is passed as an argv list, so newlines, backslashes and quotes reach the child interpreter exactly as written, identically on POSIX and Windows.
from praisonai.code import code_run_python
result = code_run_python(
code="print('hello')\nfor i in range(3): print(i)",
cwd=".",
timeout=30,
)
# {
# 'success': True, 'exit_code': 0,
# 'stdout': 'hello\n0\n1\n2\n',
# 'stderr': '',
# 'command': '/usr/bin/python -c <code>',
# 'cwd': '.',
# }| Field | Type | Notes |
|---|---|---|
success | bool | True iff exit_code == 0 |
exit_code | int | -1 on timeout / launch error |
stdout | str | Child stdout |
stderr | str | Child stderr |
command | str | Always "<python> -c <code>" — source is not echoed back |
cwd | str | Working directory used |
error | str | Present only on timeout / launch error |
Using with Agents
The code tools can be used directly with PraisonAI agents:
from praisonai import Agent
from praisonai.code import CODE_TOOLS
agent = Agent(
name="Code Editor",
role="Software Developer",
goal="Edit and improve code",
tools=CODE_TOOLS
)
result = agent.start("Refactor the main.py file to use async/await")Features
Fuzzy Matching
The diff application uses fuzzy matching with Levenshtein distance:
from praisonai.code import code_apply_diff
# Even with slight differences, the diff will match
diff = """
<<<<<<< SEARCH
def function(): # slight whitespace difference
pass
=======
def function():
return True
>>>>>>> REPLACE
"""
result = code_apply_diff("src/file.py", diff, similarity_threshold=0.8)Indentation Preservation
The module automatically preserves indentation when applying diffs:
diff = """
<<<<<<< SEARCH
def nested_function():
pass
=======
def nested_function():
return "preserved indentation"
>>>>>>> REPLACE
"""Workspace & Write Safety
Every file-modifying tool (code_apply_diff, code_search_replace, code_write_file, code_read_file, code_list_files, and the low-level append_to_file) is confined to the workspace root. Paths that resolve outside the workspace are rejected with "Path '<path>' is outside the workspace".
- Default workspace is the current working directory. If you never call
set_workspace(...), the tools confine writes and reads tocwd. Absolute paths outsidecwdare rejected. - Explicit
set_workspace(...)is still recommended — it makes the confinement boundary explicit and portable across working directories. - Protected-path check runs first. System paths (e.g.
/etc/hosts,~/.ssh/) are rejected even if they sit inside the workspace. See Protected Paths. - Gitignore Support: Respects
.gitignorepatterns for listing. - Access Control: Configurable file access rules.
Crash-safe writes
code_write_file, code_search_replace, and code_apply_diff write through a temp file + fsync + os.replace, so an interrupted write (cancellation, timeout, SIGTERM, disk-full) never leaves the target zero-byte or half-written — the original file stays intact until the atomic rename.
Permission bits are preserved (a 0o755 script stays 0o755; a 0o600 .env stays 0o600 and its new content is never world-readable while the temp file exists). On POSIX, owner (uid/gid) is preserved best-effort when the process has privilege. Symlinks are written through (the link stays, its target gets the new content); read-only targets are refused with PermissionError instead of being replaced via inode swap.
from praisonai.code.utils.file_utils import atomic_write
# temp file → fsync → os.replace; a mid-write crash leaves config.yaml intact
atomic_write("config.yaml", "model: gpt-4o\nmax_steps: 20\n")The same atomic_write helper backs the multiedit tool — see MultiEdit → Safety Guarantees.
append_to_file
Append content to a file (creates it if missing):
from praisonai.code.tools.write_file import append_to_file
result = append_to_file(
"logs/run.log",
"new line\n",
workspace="/path/to/project", # optional — defaults to cwd
create_if_missing=True, # default True
encoding="utf-8", # default utf-8
)Returns {"success": bool, "path": str, "created": bool, "bytes_appended": int} on success, or {"success": False, "error": str} on failure.
Configuration
Set Workspace
from praisonai.code import set_workspace, get_workspace
# Set workspace
set_workspace("/path/to/project")
# Get current workspace
workspace = get_workspace()
print(workspace) # "/path/to/project"Environment Variables
# Set default workspace
export PRAISONAI_CODE_WORKSPACE=/path/to/projectModule Structure
praisonai/code/
├── __init__.py # Main exports
├── agent_tools.py # Agent-compatible tool wrappers
├── diff/
│ └── diff_strategy.py # SEARCH/REPLACE diff with fuzzy matching
├── tools/
│ ├── read_file.py # Read files with line ranges
│ ├── write_file.py # Create/overwrite files
│ ├── list_files.py # List directory contents
│ ├── apply_diff.py # Apply SEARCH/REPLACE diffs
│ ├── search_replace.py # Multiple search/replace ops
│ └── execute_command.py # Run shell commands
└── utils/
├── file_utils.py # Line numbers, file ops, atomic_write()
├── text_utils.py # Similarity, fuzzy search
└── ignore_utils.py # Gitignore, access control