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:

bash
pip install praisonai

Quick Start

python
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:

python
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:

python
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:

python
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:

python
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:

python
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:

python
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:

python
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:

python
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.

python
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': '.',
# }
FieldTypeNotes
successboolTrue iff exit_code == 0
exit_codeint-1 on timeout / launch error
stdoutstrChild stdout
stderrstrChild stderr
commandstrAlways "<python> -c <code>" — source is not echoed back
cwdstrWorking directory used
errorstrPresent only on timeout / launch error

Using with Agents

The code tools can be used directly with PraisonAI agents:

python
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:

python
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:

python
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 to cwd. Absolute paths outside cwd are 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 .gitignore patterns 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.

python
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):

python
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

python
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

bash
# Set default workspace
export PRAISONAI_CODE_WORKSPACE=/path/to/project

Module 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