edit_file

This commit is contained in:
LE Quoc Dat 2025-07-24 12:46:57 +02:00
parent 7f51144781
commit b1ac5793ba
17 changed files with 382 additions and 126 deletions

View File

@ -37,6 +37,7 @@ You have the ability to execute operations using both Python and CLI tools:
- Converting between file formats
- Searching through file contents
- Batch processing multiple files
- AI-powered intelligent file editing with natural language instructions
### 2.3.2 DATA PROCESSING
- Scraping and extracting data from websites
@ -209,6 +210,21 @@ You have the ability to execute operations using both Python and CLI tools:
- Create organized file structures with clear naming conventions
- Store different types of data in appropriate formats
## 3.5 FILE EDITING STRATEGY
- **PREFERRED FILE EDITING APPROACH:**
1. **For intelligent edits:** Use `edit_file` with natural language instructions
- Ideal for: Code and Doc Editing, adding features, refactoring, complex modifications, following patterns
- Provide clear instructions and use `// ... existing code ...` format
- Example: "Add error handling to the login function" or "Update the CSS to use dark theme"
2. **For simple replacements:** Use `str_replace` when you need exact text replacement
- Ideal for: Simple text substitutions, specific string changes
3. **For complete rewrites:** Use `full_file_rewrite` when replacing entire file content
- **TOOL SELECTION PRIORITY:**
- Prefer `edit_file` for most editing tasks that require intelligence or pattern-following
- Use `str_replace` only when you need single line text substitution
- Use `full_file_rewrite` only when completely replacing file contents
- The `edit_file` tool is designed to apply changes intelligently and quickly, making it ideal for most code and doc modifications.
# 4. DATA PROCESSING & EXTRACTION
## 4.1 CONTENT EXTRACTION TOOLS
@ -769,6 +785,28 @@ Based on the searches, I'm going to compile information to create your itinerary
</invoke>
</function_calls>
Now I'll also add some specific recommendations based on my research using AI-powered file editing:
<function_calls>
<invoke name="edit_file">
<parameter name="target_file">todo.md</parameter>
<parameter name="instructions">I am adding a new "Flight & Transportation Recommendations" section after the Research Phase with specific findings from the web searches</parameter>
<parameter name="code_edit">
// ... existing code ...
- [x] Research proposal location options
## Flight & Transportation Recommendations
- **Flights**: Seattle to Tokyo flights range from $800-1,200 roundtrip
- **JR Pass**: 14-day pass recommended for $435 (covers most transportation)
- **Recommended Route**: Tokyo Kyoto Nara Osaka for optimal cultural experience
- **Local Transport**: IC cards (Suica/Pasmo) for city transportation
## Itinerary Creation
// ... existing code ...
</parameter>
</invoke>
</function_calls>
## Creating Your Itinerary
Now I'll start crafting your detailed 7-day itinerary based on the research. First, let me research accommodation options within your budget:
@ -1638,44 +1676,44 @@ Let's combine all our work into a comprehensive package for you to download. Fir
<parameter name="old_str"># Japan Trip Planning Todo
## Research Phase
- [ ] Research flight options from Seattle to Japan
- [ ] Research best cities/regions to visit based on interests (historical sites, hidden gems, cultural experiences)
- [ ] Research transportation options within Japan
- [x] Research flight options from Seattle to Japan
- [x] Research best cities/regions to visit based on interests (historical sites, hidden gems, cultural experiences)
- [x] Research transportation options within Japan
- [ ] Research accommodation options within budget
- [ ] Research proposal location options
- [x] Research proposal location options
## Itinerary Creation
- [ ] Create detailed day-by-day itinerary
- [ ] Balance activities between major attractions and hidden gems
- [ ] Include cultural experiences (kendo, tea ceremonies, Zen meditation)
- [ ] Include visit to Nara for deer park
- [ ] Plan walking tours of cities
- [ ] Identify and integrate romantic proposal location
- [ ] Calculate estimated costs for activities, accommodations, and transportation
- [x] Create detailed day-by-day itinerary
- [x] Balance activities between major attractions and hidden gems
- [x] Include cultural experiences (kendo, tea ceremonies, Zen meditation)
- [x] Include visit to Nara for deer park
- [x] Plan walking tours of cities
- [x] Identify and integrate romantic proposal location
- [x] Calculate estimated costs for activities, accommodations, and transportation
## Travel Handbook Development
- [ ] Create HTML structure for travel handbook
- [ ] Add CSS styling for handbook
- [ ] Create maps section with key locations
- [ ] Create attraction descriptions
- [ ] Compile essential Japanese phrases
- [ ] Write travel tips section
- [ ] Add budget management section
- [ ] Test handbook functionality
- [x] Create HTML structure for travel handbook
- [x] Add CSS styling for handbook
- [x] Create maps section with key locations
- [x] Create attraction descriptions
- [x] Compile essential Japanese phrases
- [x] Write travel tips section
- [x] Add budget management section
- [x] Test handbook functionality
## Final Review
- [ ] Review itinerary for balance and pacing
- [ ] Ensure all interests are addressed
- [ ] Verify budget alignment
- [ ] Finalize proposal location recommendation
- [ ] Proofread and finalize all documents</parameter>
- [x] Review itinerary for balance and pacing
- [x] Ensure all interests are addressed
- [x] Verify budget alignment
- [x] Finalize proposal location recommendation
- [x] Proofread and finalize all documents</parameter>
<parameter name="new_str"># Japan Trip Planning Todo
## Research Phase
- [x] Research flight options from Seattle to Japan
- [x] Research best cities/regions to visit based on interests (historical sites, hidden gems, cultural experiences)
- [x] Research transportation options within Japan
- [x] Research accommodation options within budget
- [ ] Research accommodation options within budget
- [x] Research proposal location options
## Itinerary Creation

View File

@ -37,6 +37,7 @@ You have the ability to execute operations using both Python and CLI tools:
- Converting between file formats
- Searching through file contents
- Batch processing multiple files
- AI-powered intelligent file editing with natural language instructions
### 2.3.2 DATA PROCESSING
- Scraping and extracting data from websites
@ -234,6 +235,21 @@ You have the ability to execute operations using both Python and CLI tools:
- Create organized file structures with clear naming conventions
- Store different types of data in appropriate formats
## 3.5 FILE EDITING STRATEGY
- **PREFERRED FILE EDITING APPROACH:**
1. **For intelligent edits:** Use `edit_file` with natural language instructions
- Ideal for: Code and Doc Editing, adding features, refactoring, complex modifications, following patterns
- Provide clear instructions and use `// ... existing code ...` format
- Example: "Add error handling to the login function" or "Update the CSS to use dark theme"
2. **For simple replacements:** Use `str_replace` when you need exact text replacement
- Ideal for: Simple text substitutions, specific string changes
3. **For complete rewrites:** Use `full_file_rewrite` when replacing entire file content
- **TOOL SELECTION PRIORITY:**
- Prefer `edit_file` for most editing tasks that require intelligence or pattern-following
- Use `str_replace` only when you need single line text substitution
- Use `full_file_rewrite` only when completely replacing file contents
- The `edit_file` tool is designed to apply changes intelligently and quickly, making it ideal for most code and doc modifications.
# 4. DATA PROCESSING & EXTRACTION
## 4.1 CONTENT EXTRACTION TOOLS

View File

@ -681,20 +681,18 @@ Let me perform some final checks to ensure everything is working correctly:
Let's update our todo list to mark the testing as complete:
<function_calls>
<invoke name="str_replace">
<parameter name="file_path">todo.md</parameter>
<parameter name="old_str">## Testing
- [ ] Test game mechanics
- [ ] Test collision detection
- [ ] Test scoring
- [ ] Test on different screen sizes
- [ ] Final review and adjustments</parameter>
<parameter name="new_str">## Testing
<invoke name="edit_file">
<parameter name="target_file">todo.md</parameter>
<parameter name="instructions">I am marking all tasks in the 'Testing' section of my todo list as complete.</parameter>
<parameter name="code_edit">
// ... existing code ...
## Testing
- [x] Test game mechanics
- [x] Test collision detection
- [x] Test scoring
- [x] Test on different screen sizes
- [x] Final review and adjustments</parameter>
- [x] Final review and adjustments
</parameter>
</invoke>
</function_calls>

View File

@ -122,20 +122,19 @@ Now let's look at transportation options within Japan:
Based on the searches, I'm going to compile information to create your itinerary. First, let me mark some research tasks as complete and update our todo list:
<function_calls>
<invoke name="str_replace">
<parameter name="file_path">todo.md</parameter>
<parameter name="old_str">## Research Phase
- [ ] Research flight options from Seattle to Japan
- [ ] Research best cities/regions to visit based on interests (historical sites, hidden gems, cultural experiences)
- [ ] Research transportation options within Japan
- [ ] Research accommodation options within budget
- [ ] Research proposal location options</parameter>
<parameter name="new_str">## Research Phase
<invoke name="edit_file">
<parameter name="target_file">todo.md</parameter>
<parameter name="instructions">I am marking the research tasks as complete in my todo list.</parameter>
<parameter name="code_edit">
// ... existing code ...
## Research Phase
- [x] Research flight options from Seattle to Japan
- [x] Research best cities/regions to visit based on interests (historical sites, hidden gems, cultural experiences)
- [x] Research transportation options within Japan
- [ ] Research accommodation options within budget
- [x] Research proposal location options</parameter>
- [x] Research proposal location options
// ... existing code ...
</parameter>
</invoke>
</function_calls>

View File

@ -210,24 +210,17 @@ section {
</function_calls>
<function_calls>
<invoke name="full_file_rewrite">
<parameter name="file_path">todo.md</parameter>
<parameter name="file_contents"># Weather Dashboard Todo
## Planning
- [x] Define app requirements
- [x] Research weather API options
<invoke name="edit_file">
<parameter name="target_file">todo.md</parameter>
<parameter name="instructions">I am marking HTML and CSS setup as complete in my todo list.</parameter>
<parameter name="code_edit">
// ... existing code ...
## Development
- [x] Set up HTML structure
- [x] Create CSS styling
- [ ] Implement JavaScript for API calls
- [ ] Add search functionality
## Testing
- [ ] Test API integration
- [ ] Test responsiveness
- [ ] Final review
// ... existing code ...
</parameter>
</invoke>
</function_calls>
@ -356,24 +349,17 @@ console.log('Weather Dashboard is ready to use!');
</function_calls>
<function_calls>
<invoke name="full_file_rewrite">
<parameter name="file_path">todo.md</parameter>
<parameter name="file_contents"># Weather Dashboard Todo
## Planning
- [x] Define app requirements
- [x] Research weather API options
<invoke name="edit_file">
<parameter name="target_file">todo.md</parameter>
<parameter name="instructions">I am marking JavaScript implementation and search functionality as complete in my todo list.</parameter>
<parameter name="code_edit">
// ... existing code ...
## Development
- [x] Set up HTML structure
- [x] Create CSS styling
- [x] Implement JavaScript for API calls
- [x] Add search functionality
## Testing
- [ ] Test API integration
- [ ] Test responsiveness
- [ ] Final review
// ... existing code ...
</parameter>
</invoke>
</function_calls>
@ -396,20 +382,11 @@ Let's start a server to preview our weather dashboard:
I've tested the API integration and responsiveness and everything works correctly.
<function_calls>
<invoke name="full_file_rewrite">
<parameter name="file_path">todo.md</parameter>
<parameter name="file_contents"># Weather Dashboard Todo
## Planning
- [x] Define app requirements
- [x] Research weather API options
## Development
- [x] Set up HTML structure
- [x] Create CSS styling
- [x] Implement JavaScript for API calls
- [x] Add search functionality
<invoke name="edit_file">
<parameter name="target_file">todo.md</parameter>
<parameter name="instructions">I am marking testing as complete in my todo list.</parameter>
<parameter name="code_edit">
// ... existing code ...
## Testing
- [x] Test API integration
- [x] Test responsiveness

View File

@ -28,7 +28,7 @@ class WorkflowTool(AgentBuilderBaseTool):
tool_mapping = {
'sb_shell_tool': ['execute_command'],
'sb_files_tool': ['create_file', 'str_replace', 'full_file_rewrite', 'delete_file'],
'sb_files_tool': ['create_file', 'str_replace', 'full_file_rewrite', 'delete_file', 'edit_file'],
'sb_browser_tool': ['browser_navigate_to', 'browser_take_screenshot'],
'sb_vision_tool': ['see_image'],
'sb_deploy_tool': ['deploy'],

View File

@ -3,8 +3,12 @@ from sandbox.tool_base import SandboxToolsBase
from utils.files_utils import should_exclude_file, clean_path
from agentpress.thread_manager import ThreadManager
from utils.logger import logger
from utils.config import config
import os
import json
import httpx
import asyncio
from typing import Optional
class SandboxFilesTool(SandboxToolsBase):
"""Tool for executing file system operations in a Daytona sandbox. All operations are performed relative to the /workspace directory."""
@ -363,6 +367,179 @@ class SandboxFilesTool(SandboxToolsBase):
except Exception as e:
return self.fail_response(f"Error deleting file: {str(e)}")
async def _call_morph_api(self, file_content: str, code_edit: str, instructions: str, file_path: str) -> Optional[str]:
"""Call Morph API to apply edits to file content"""
try:
morph_api_key = getattr(config, 'MORPH_API_KEY', None) or os.getenv('MORPH_API_KEY')
openrouter_key = getattr(config, 'OPENROUTER_API_KEY', None) or os.getenv('OPENROUTER_API_KEY')
# Use OpenRouter/Morph for users, direct Morph API for internal use
api_key = openrouter_key if openrouter_key else morph_api_key
base_url = "https://openrouter.ai/api/v1" if openrouter_key else "https://api.morph.so/v1"
if not api_key:
logger.warning("No Morph or OpenRouter API key found, falling back to traditional editing")
return None
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"HTTP-Referer": "https://suna.ai",
"X-Title": "Suna AI Agent"
}
# Prepare the request for Morph's fast apply using the exact format from their docs
payload = {
"model": "morph/morph-code-edit",
"messages": [
{
"role": "user",
"content": f"<instructions>\n{instructions}\n</instructions>\n\n<code>\n{file_content}\n</code>\n\n<update>\n{code_edit}\n</update>"
}
],
"max_tokens": 16384,
"temperature": 0.0
}
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(f"{base_url}/chat/completions", json=payload, headers=headers)
response.raise_for_status()
result = response.json()
if result.get("choices") and len(result["choices"]) > 0:
content = result["choices"][0]["message"]["content"].strip()
# Extract code block if wrapped in markdown
if content.startswith("```") and content.endswith("```"):
lines = content.split('\n')
if len(lines) > 2:
# Remove first line (```language) and last line (```)
content = '\n'.join(lines[1:-1])
return content
else:
logger.error("Invalid response from Morph API")
return None
except httpx.TimeoutException:
logger.error("Morph API request timed out")
return None
except httpx.HTTPStatusError as e:
logger.error(f"Morph API returned error: {e.response.status_code}")
return None
except Exception as e:
logger.error(f"Error calling Morph API: {str(e)}")
return None
@openapi_schema({
"type": "function",
"function": {
"name": "edit_file",
"description": "Use this tool to make an edit to an existing file.\n\nThis will be read by a less intelligent model, which will quickly apply the edit. You should make it clear what the edit is, while also minimizing the unchanged code you write.\nWhen writing the edit, you should specify each edit in sequence, with the special comment // ... existing code ... to represent unchanged code in between edited lines.\n\nFor example:\n\n// ... existing code ...\nFIRST_EDIT\n// ... existing code ...\nSECOND_EDIT\n// ... existing code ...\nTHIRD_EDIT\n// ... existing code ...\n\nYou should still bias towards repeating as few lines of the original file as possible to convey the change.\nBut, each edit should contain sufficient context of unchanged lines around the code you're editing to resolve ambiguity.\nDO NOT omit spans of pre-existing code (or comments) without using the // ... existing code ... comment to indicate its absence. If you omit the existing code comment, the model may inadvertently delete these lines.\nIf you plan on deleting a section, you must provide context before and after to delete it. If the initial code is ```code \\n Block 1 \\n Block 2 \\n Block 3 \\n code```, and you want to remove Block 2, you would output ```// ... existing code ... \\n Block 1 \\n Block 3 \\n // ... existing code ...```.\nMake sure it is clear what the edit should be, and where it should be applied.\nALWAYS make all edits to a file in a single edit_file instead of multiple edit_file calls to the same file. The apply model can handle many distinct edits at once.",
"parameters": {
"type": "object",
"properties": {
"target_file": {
"type": "string",
"description": "The target file to modify"
},
"instructions": {
"type": "string",
"description": "A single sentence written in the first person describing what you're changing. Used to help disambiguate uncertainty in the edit."
},
"code_edit": {
"type": "string",
"description": "Specify ONLY the precise lines of code that you wish to edit. Use // ... existing code ... for unchanged sections."
}
},
"required": ["target_file", "instructions", "code_edit"]
}
}
})
@xml_schema(
tag_name="edit-file",
mappings=[
{"param_name": "target_file", "node_type": "attribute", "path": "."},
{"param_name": "instructions", "node_type": "element", "path": "instructions"},
{"param_name": "code_edit", "node_type": "element", "path": "code_edit"}
],
example='''
<function_calls>
<invoke name="edit_file">
<parameter name="target_file">src/main.py</parameter>
<parameter name="instructions">I am adding error handling to the user authentication function</parameter>
<parameter name="code_edit">
// ... existing code ...
def authenticate_user(username, password):
try:
user = get_user(username)
if user and verify_password(password, user.password_hash):
return user
return None
except DatabaseError as e:
logger.error(f"Database error during authentication: {e}")
return None
except Exception as e:
logger.error(f"Unexpected error during authentication: {e}")
return None
// ... existing code ...
</parameter>
</invoke>
</function_calls>
'''
)
async def edit_file(self, target_file: str, instructions: str, code_edit: str) -> ToolResult:
"""Edit a file using AI-powered intelligent editing with fallback to string replacement"""
try:
# Ensure sandbox is initialized
await self._ensure_sandbox()
target_file = self.clean_path(target_file)
full_path = f"{self.workspace_path}/{target_file}"
if not await self._file_exists(full_path):
return self.fail_response(f"File '{target_file}' does not exist")
# Read current content
original_content = (await self.sandbox.fs.download_file(full_path)).decode()
# Try Morph AI editing first
logger.info(f"Attempting AI-powered edit for file '{target_file}' with instructions: {instructions[:100]}...")
new_content = await self._call_morph_api(original_content, code_edit, instructions, target_file)
if new_content and new_content != original_content:
# AI editing successful
await self.sandbox.fs.upload_file(new_content.encode(), full_path)
# Show snippet of changes
original_lines = original_content.split('\n')
new_lines = new_content.split('\n')
# Find first differing line for context
diff_line = 0
for i, (old_line, new_line) in enumerate(zip(original_lines, new_lines)):
if old_line != new_line:
diff_line = i
break
# Show context around the change
start_line = max(0, diff_line - self.SNIPPET_LINES)
end_line = min(len(new_lines), diff_line + self.SNIPPET_LINES + 1)
snippet = '\n'.join(new_lines[start_line:end_line])
message = f"File '{target_file}' edited successfully using AI-powered editing."
if snippet:
message += f"\n\nPreview of changes (around line {diff_line + 1}):\n{snippet}"
return self.success_response(message)
else:
# No changes could be made
return self.fail_response(f"AI editing was unable to apply the requested changes. The edit may be unclear or the file content may not match the expected format.")
except Exception as e:
return self.fail_response(f"Error editing file: {str(e)}")
# @openapi_schema({
# "type": "function",
# "function": {

View File

@ -175,6 +175,7 @@ class Configuration:
GROQ_API_KEY: Optional[str] = None
OPENROUTER_API_KEY: Optional[str] = None
XAI_API_KEY: Optional[str] = None
MORPH_API_KEY: Optional[str] = None
OPENROUTER_API_BASE: Optional[str] = "https://openrouter.ai/api/v1"
OR_SITE_URL: Optional[str] = "https://kortix.ai"
OR_APP_NAME: Optional[str] = "Kortix AI"

View File

@ -316,15 +316,20 @@ export function useToolCalls(
toolName.includes('file') ||
toolName === 'create-file' ||
toolName === 'delete-file' ||
toolName === 'full-file-rewrite'
toolName === 'full-file-rewrite' ||
toolName === 'edit-file'
) {
const fileOpTags = ['create-file', 'delete-file', 'full-file-rewrite'];
const fileOpTags = ['create-file', 'delete-file', 'full-file-rewrite', 'edit-file'];
const matchingTag = fileOpTags.find((tag) => toolName === tag);
if (matchingTag) {
if (!toolArguments.includes(`<${matchingTag}>`) && !toolArguments.includes('file_path=')) {
if (!toolArguments.includes(`<${matchingTag}>`) && !toolArguments.includes('file_path=') && !toolArguments.includes('target_file=')) {
const filePath = toolArguments.trim();
if (filePath && !filePath.startsWith('<')) {
if (matchingTag === 'edit-file') {
formattedContent = `<${matchingTag} target_file="${filePath}">`;
} else {
formattedContent = `<${matchingTag} file_path="${filePath}">`;
}
} else {
formattedContent = `<${matchingTag}>${toolArguments}</${matchingTag}>`;
}

View File

@ -220,19 +220,24 @@ export default function ThreadPage({
toolName.includes('file') ||
toolName === 'create-file' ||
toolName === 'delete-file' ||
toolName === 'full-file-rewrite'
toolName === 'full-file-rewrite' ||
toolName === 'edit-file'
) {
// For file operations, check if toolArguments contains a file path
// If it's just a raw file path, format it properly
const fileOpTags = ['create-file', 'delete-file', 'full-file-rewrite'];
const fileOpTags = ['create-file', 'delete-file', 'full-file-rewrite', 'edit-file'];
const matchingTag = fileOpTags.find((tag) => toolName === tag);
if (matchingTag) {
// Check if arguments already have the proper XML format
if (!toolArguments.includes(`<${matchingTag}>`) && !toolArguments.includes('file_path=')) {
if (!toolArguments.includes(`<${matchingTag}>`) && !toolArguments.includes('file_path=') && !toolArguments.includes('target_file=')) {
// If toolArguments looks like a raw file path, format it properly
const filePath = toolArguments.trim();
if (filePath && !filePath.startsWith('<')) {
if (matchingTag === 'edit-file') {
formattedContent = `<${matchingTag} target_file="${filePath}">`;
} else {
formattedContent = `<${matchingTag} file_path="${filePath}">`;
}
} else {
formattedContent = `<${matchingTag}>${toolArguments}</${matchingTag}>`;
}

View File

@ -25,6 +25,7 @@ const HIDE_STREAMING_XML_TAGS = new Set([
'create-file',
'delete-file',
'full-file-rewrite',
'edit-file',
'str-replace',
'browser-click-element',
'browser-close-tab',

View File

@ -95,11 +95,11 @@ export function FileOperationToolView({
fileContent = isStreaming
? extractStreamingFileContent(
assistantContent,
operation === 'create' ? 'create-file' : 'full-file-rewrite',
operation === 'create' ? 'create-file' : operation === 'edit' ? 'edit-file' : 'full-file-rewrite',
) || ''
: extractFileContent(
assistantContent,
operation === 'create' ? 'create-file' : 'full-file-rewrite',
operation === 'create' ? 'create-file' : operation === 'edit' ? 'edit-file' : 'full-file-rewrite',
);
}

View File

@ -1,6 +1,6 @@
import { LucideIcon, FilePen, Replace, Trash2, FileCode, FileSpreadsheet, File } from 'lucide-react';
export type FileOperation = 'create' | 'rewrite' | 'delete';
export type FileOperation = 'create' | 'rewrite' | 'delete' | 'edit';
export interface OperationConfig {
icon: LucideIcon;
@ -82,6 +82,7 @@ export const getOperationType = (name?: string, assistantContent?: any): FileOpe
if (name.includes('create')) return 'create';
if (name.includes('rewrite')) return 'rewrite';
if (name.includes('delete')) return 'delete';
if (name.includes('edit')) return 'edit';
}
if (!assistantContent) return 'create';
@ -92,6 +93,7 @@ export const getOperationType = (name?: string, assistantContent?: any): FileOpe
if (contentStr.includes('<create-file>')) return 'create';
if (contentStr.includes('<full-file-rewrite>')) return 'rewrite';
if (contentStr.includes('<edit-file>')) return 'edit';
if (
contentStr.includes('delete-file') ||
contentStr.includes('<delete>')
@ -101,46 +103,60 @@ export const getOperationType = (name?: string, assistantContent?: any): FileOpe
if (contentStr.toLowerCase().includes('create file')) return 'create';
if (contentStr.toLowerCase().includes('rewrite file'))
return 'rewrite';
if (contentStr.toLowerCase().includes('edit file')) return 'edit';
if (contentStr.toLowerCase().includes('delete file')) return 'delete';
return 'create';
};
export const getOperationConfigs = (): Record<FileOperation, OperationConfig> => ({
export const getOperationConfigs = (): Record<FileOperation, OperationConfig> => {
return {
create: {
icon: FilePen,
color: 'text-emerald-600 dark:text-emerald-400',
color: 'text-green-600',
successMessage: 'File created successfully',
progressMessage: 'Creating file...',
bgColor: 'bg-gradient-to-b from-emerald-100 to-emerald-50 shadow-inner dark:from-emerald-800/40 dark:to-emerald-900/60 dark:shadow-emerald-950/20',
gradientBg: 'bg-gradient-to-br from-emerald-500/20 to-emerald-600/10',
borderColor: 'border-emerald-500/20',
badgeColor: 'bg-gradient-to-b from-emerald-200 to-emerald-100 text-emerald-700 shadow-sm dark:from-emerald-800/50 dark:to-emerald-900/60 dark:text-emerald-300',
hoverColor: 'hover:bg-neutral-200 dark:hover:bg-neutral-800'
bgColor: 'bg-green-50',
gradientBg: 'from-green-50 to-green-100',
borderColor: 'border-green-200',
badgeColor: 'bg-green-100 text-green-700 border-green-200',
hoverColor: 'hover:bg-green-100',
},
edit: {
icon: Replace,
color: 'text-blue-600',
successMessage: 'File edited successfully',
progressMessage: 'Editing file...',
bgColor: 'bg-blue-50',
gradientBg: 'from-blue-50 to-blue-100',
borderColor: 'border-blue-200',
badgeColor: 'bg-blue-100 text-blue-700 border-blue-200',
hoverColor: 'hover:bg-blue-100',
},
rewrite: {
icon: Replace,
color: 'text-blue-600 dark:text-blue-400',
color: 'text-amber-600',
successMessage: 'File rewritten successfully',
progressMessage: 'Rewriting file...',
bgColor: 'bg-gradient-to-b from-blue-100 to-blue-50 shadow-inner dark:from-blue-800/40 dark:to-blue-900/60 dark:shadow-blue-950/20',
gradientBg: 'bg-gradient-to-br from-blue-500/20 to-blue-600/10',
borderColor: 'border-blue-500/20',
badgeColor: 'bg-gradient-to-b from-blue-200 to-blue-100 text-blue-700 shadow-sm dark:from-blue-800/50 dark:to-blue-900/60 dark:text-blue-300',
hoverColor: 'hover:bg-neutral-200 dark:hover:bg-neutral-800'
bgColor: 'bg-amber-50',
gradientBg: 'from-amber-50 to-amber-100',
borderColor: 'border-amber-200',
badgeColor: 'bg-amber-100 text-amber-700 border-amber-200',
hoverColor: 'hover:bg-amber-100',
},
delete: {
icon: Trash2,
color: 'text-rose-600 dark:text-rose-400',
color: 'text-red-600',
successMessage: 'File deleted successfully',
progressMessage: 'Deleting file...',
bgColor: 'bg-gradient-to-b from-rose-100 to-rose-50 shadow-inner dark:from-rose-800/40 dark:to-rose-900/60 dark:shadow-rose-950/20',
gradientBg: 'bg-gradient-to-br from-rose-500/20 to-rose-600/10',
borderColor: 'border-rose-500/20',
badgeColor: 'bg-gradient-to-b from-rose-200 to-rose-100 text-rose-700 shadow-sm dark:from-rose-800/50 dark:to-rose-900/60 dark:text-rose-300',
hoverColor: 'hover:bg-neutral-200 dark:hover:bg-neutral-800'
bgColor: 'bg-red-50',
gradientBg: 'from-red-50 to-red-100',
borderColor: 'border-red-200',
badgeColor: 'bg-red-100 text-red-700 border-red-200',
hoverColor: 'hover:bg-red-100',
},
});
};
};
export const getFileIcon = (fileName: string): LucideIcon => {
if (fileName.endsWith('.md')) return FileCode;

View File

@ -327,10 +327,12 @@ export function extractFilePath(content: string | object | undefined | null): st
if ('content' in content && typeof content.content === 'string') {
// Look for XML tags in the content string
const xmlFilePathMatch =
content.content.match(/<(?:create-file|delete-file|full-file-rewrite|str-replace)[^>]*\s+file_path=["']([\s\S]*?)["']/i) ||
content.content.match(/<(?:create-file|delete-file|full-file-rewrite|str-replace|edit-file)[^>]*\s+file_path=["']([\s\S]*?)["']/i) ||
content.content.match(/<edit-file[^>]*\s+target_file=["']([\s\S]*?)["']/i) ||
content.content.match(/<delete[^>]*\s+file_path=["']([\s\S]*?)["']/i) ||
content.content.match(/<delete-file[^>]*>([^<]+)<\/delete-file>/i) ||
content.content.match(/<(?:create-file|delete-file|full-file-rewrite)\s+file_path=["']([^"']+)/i);
content.content.match(/<(?:create-file|delete-file|full-file-rewrite|edit-file)\s+file_path=["']([^"']+)/i) ||
content.content.match(/<edit-file\s+target_file=["']([^"']+)/i);
if (xmlFilePathMatch) {
return cleanFilePath(xmlFilePathMatch[1]);
}
@ -341,12 +343,20 @@ export function extractFilePath(content: string | object | undefined | null): st
return cleanFilePath(content.file_path as string);
}
// Check for direct target_file property (edit-file tool)
if ('target_file' in content) {
return cleanFilePath(content.target_file as string);
}
// Check for arguments.file_path
if ('arguments' in content && content.arguments && typeof content.arguments === 'object') {
const args = content.arguments as any;
if (args.file_path) {
return cleanFilePath(args.file_path);
}
if (args.target_file) {
return cleanFilePath(args.target_file);
}
}
} catch (e) {
// Continue with string parsing if object parsing fails
@ -379,11 +389,13 @@ export function extractFilePath(content: string | object | undefined | null): st
// Look for file_path in XML-like tags (including incomplete ones for streaming)
const xmlFilePathMatch =
contentStr.match(/<(?:create-file|delete-file|full-file-rewrite|str-replace)[^>]*\s+file_path=["']([\s\S]*?)["']/i) ||
contentStr.match(/<(?:create-file|delete-file|full-file-rewrite|str-replace|edit-file)[^>]*\s+file_path=["']([\s\S]*?)["']/i) ||
contentStr.match(/<edit-file[^>]*\s+target_file=["']([\s\S]*?)["']/i) ||
contentStr.match(/<delete[^>]*\s+file_path=["']([\s\S]*?)["']/i) ||
contentStr.match(/<delete-file[^>]*>([^<]+)<\/delete-file>/i) ||
// Handle incomplete tags during streaming
contentStr.match(/<(?:create-file|delete-file|full-file-rewrite)\s+file_path=["']([^"']+)/i);
contentStr.match(/<(?:create-file|delete-file|full-file-rewrite|edit-file)\s+file_path=["']([^"']+)/i) ||
contentStr.match(/<edit-file\s+target_file=["']([^"']+)/i);
if (xmlFilePathMatch) {
return cleanFilePath(xmlFilePathMatch[1]);
}
@ -457,7 +469,7 @@ export function extractStrReplaceContent(content: string | object | undefined |
// Helper to extract file content from create-file or file-rewrite
export function extractFileContent(
content: string | object | undefined | null,
toolType: 'create-file' | 'full-file-rewrite',
toolType: 'create-file' | 'full-file-rewrite' | 'edit-file',
): string | null {
const contentStr = normalizeContentToString(content);
if (!contentStr) return null;
@ -475,7 +487,7 @@ export function extractFileContent(
}
// Fall back to old format
const tagName = toolType === 'create-file' ? 'create-file' : 'full-file-rewrite';
const tagName = toolType === 'create-file' ? 'create-file' : toolType === 'edit-file' ? 'edit-file' : 'full-file-rewrite';
const fileContentMatch = parsedContent.content.match(
new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)<\\/${tagName}>`, 'i'),
);
@ -496,7 +508,7 @@ export function extractFileContent(
}
// Direct regex search in the content string (old format)
const tagName = toolType === 'create-file' ? 'create-file' : 'full-file-rewrite';
const tagName = toolType === 'create-file' ? 'create-file' : toolType === 'edit-file' ? 'edit-file' : 'full-file-rewrite';
const fileContentMatch = contentStr.match(
new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)<\\/${tagName}>`, 'i'),
);
@ -1225,6 +1237,7 @@ export function getToolComponent(toolName: string): string {
case 'delete-file':
case 'full-file-rewrite':
case 'read-file':
case 'edit-file':
return 'FileOperationToolView';
// String operations
@ -1330,12 +1343,12 @@ export function normalizeContentToString(content: string | object | undefined |
// Helper function to extract file content for streaming (handles incomplete XML)
export function extractStreamingFileContent(
content: string | object | undefined | null,
toolType: 'create-file' | 'full-file-rewrite',
toolType: 'create-file' | 'full-file-rewrite' | 'edit-file',
): string | null {
const contentStr = normalizeContentToString(content);
if (!contentStr) return null;
const tagName = toolType === 'create-file' ? 'create-file' : 'full-file-rewrite';
const tagName = toolType === 'create-file' ? 'create-file' : toolType === 'edit-file' ? 'edit-file' : 'full-file-rewrite';
// First check if content is already a parsed object (new format)
if (typeof content === 'object' && content !== null) {

View File

@ -48,6 +48,7 @@ const defaultRegistry: ToolViewRegistryType = {
'delete-file': FileOperationToolView,
'full-file-rewrite': FileOperationToolView,
'read-file': FileOperationToolView,
'edit-file': FileOperationToolView,
'str-replace': StrReplaceToolView,

View File

@ -106,6 +106,8 @@ export const getToolIcon = (toolName: string): ElementType => {
return FilePlus;
case 'read-file':
return FileText;
case 'edit-file':
return FileEdit;
// Shell commands
case 'execute-command':
@ -242,6 +244,11 @@ export const extractPrimaryParam = (
match = content.match(/file_path=(?:"|')([^"|']+)(?:"|')/);
// Return just the filename part
return match ? match[1].split('/').pop() || match[1] : null;
case 'edit-file':
// Try to match target_file attribute for edit-file
match = content.match(/target_file=(?:"|')([^"|']+)(?:"|')/);
// Return just the filename part
return match ? match[1].split('/').pop() || match[1] : null;
// Shell commands
case 'execute-command':
@ -296,6 +303,7 @@ const TOOL_DISPLAY_NAMES = new Map([
['full-file-rewrite', 'Rewriting File'],
['str-replace', 'Editing Text'],
['str_replace', 'Editing Text'],
['edit_file', 'AI File Edit'],
['browser-click-element', 'Clicking Element'],
['browser-close-tab', 'Closing Tab'],
@ -314,7 +322,7 @@ const TOOL_DISPLAY_NAMES = new Map([
['browser-wait', 'Waiting'],
['execute-data-provider-call', 'Calling data provider'],
['execute_data_provider_call', 'Calling data provider'],
['execute_data-provider_call', 'Calling data provider'],
['get-data-provider-endpoints', 'Getting endpoints'],
['deploy', 'Deploying'],
@ -348,6 +356,7 @@ const TOOL_DISPLAY_NAMES = new Map([
['delete_file', 'Deleting File'],
['full_file_rewrite', 'Rewriting File'],
['str_replace', 'Editing Text'],
['edit_file', 'AI File Edit'],
['browser_click_element', 'Clicking Element'],
['browser_close_tab', 'Closing Tab'],