mirror of https://github.com/kortix-ai/suna.git
edit_file
This commit is contained in:
parent
7f51144781
commit
b1ac5793ba
|
@ -37,6 +37,7 @@ You have the ability to execute operations using both Python and CLI tools:
|
||||||
- Converting between file formats
|
- Converting between file formats
|
||||||
- Searching through file contents
|
- Searching through file contents
|
||||||
- Batch processing multiple files
|
- Batch processing multiple files
|
||||||
|
- AI-powered intelligent file editing with natural language instructions
|
||||||
|
|
||||||
### 2.3.2 DATA PROCESSING
|
### 2.3.2 DATA PROCESSING
|
||||||
- Scraping and extracting data from websites
|
- 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
|
- Create organized file structures with clear naming conventions
|
||||||
- Store different types of data in appropriate formats
|
- 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. DATA PROCESSING & EXTRACTION
|
||||||
|
|
||||||
## 4.1 CONTENT EXTRACTION TOOLS
|
## 4.1 CONTENT EXTRACTION TOOLS
|
||||||
|
@ -769,6 +785,28 @@ Based on the searches, I'm going to compile information to create your itinerary
|
||||||
</invoke>
|
</invoke>
|
||||||
</function_calls>
|
</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
|
## 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:
|
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
|
<parameter name="old_str"># Japan Trip Planning Todo
|
||||||
|
|
||||||
## Research Phase
|
## Research Phase
|
||||||
- [ ] Research flight options from Seattle to Japan
|
- [x] Research flight options from Seattle to Japan
|
||||||
- [ ] Research best cities/regions to visit based on interests (historical sites, hidden gems, cultural experiences)
|
- [x] Research best cities/regions to visit based on interests (historical sites, hidden gems, cultural experiences)
|
||||||
- [ ] Research transportation options within Japan
|
- [x] Research transportation options within Japan
|
||||||
- [ ] Research accommodation options within budget
|
- [ ] Research accommodation options within budget
|
||||||
- [ ] Research proposal location options
|
- [x] Research proposal location options
|
||||||
|
|
||||||
## Itinerary Creation
|
## Itinerary Creation
|
||||||
- [ ] Create detailed day-by-day itinerary
|
- [x] Create detailed day-by-day itinerary
|
||||||
- [ ] Balance activities between major attractions and hidden gems
|
- [x] Balance activities between major attractions and hidden gems
|
||||||
- [ ] Include cultural experiences (kendo, tea ceremonies, Zen meditation)
|
- [x] Include cultural experiences (kendo, tea ceremonies, Zen meditation)
|
||||||
- [ ] Include visit to Nara for deer park
|
- [x] Include visit to Nara for deer park
|
||||||
- [ ] Plan walking tours of cities
|
- [x] Plan walking tours of cities
|
||||||
- [ ] Identify and integrate romantic proposal location
|
- [x] Identify and integrate romantic proposal location
|
||||||
- [ ] Calculate estimated costs for activities, accommodations, and transportation
|
- [x] Calculate estimated costs for activities, accommodations, and transportation
|
||||||
|
|
||||||
## Travel Handbook Development
|
## Travel Handbook Development
|
||||||
- [ ] Create HTML structure for travel handbook
|
- [x] Create HTML structure for travel handbook
|
||||||
- [ ] Add CSS styling for handbook
|
- [x] Add CSS styling for handbook
|
||||||
- [ ] Create maps section with key locations
|
- [x] Create maps section with key locations
|
||||||
- [ ] Create attraction descriptions
|
- [x] Create attraction descriptions
|
||||||
- [ ] Compile essential Japanese phrases
|
- [x] Compile essential Japanese phrases
|
||||||
- [ ] Write travel tips section
|
- [x] Write travel tips section
|
||||||
- [ ] Add budget management section
|
- [x] Add budget management section
|
||||||
- [ ] Test handbook functionality
|
- [x] Test handbook functionality
|
||||||
|
|
||||||
## Final Review
|
## Final Review
|
||||||
- [ ] Review itinerary for balance and pacing
|
- [x] Review itinerary for balance and pacing
|
||||||
- [ ] Ensure all interests are addressed
|
- [x] Ensure all interests are addressed
|
||||||
- [ ] Verify budget alignment
|
- [x] Verify budget alignment
|
||||||
- [ ] Finalize proposal location recommendation
|
- [x] Finalize proposal location recommendation
|
||||||
- [ ] Proofread and finalize all documents</parameter>
|
- [x] Proofread and finalize all documents</parameter>
|
||||||
<parameter name="new_str"># Japan Trip Planning Todo
|
<parameter name="new_str"># Japan Trip Planning Todo
|
||||||
|
|
||||||
## Research Phase
|
## Research Phase
|
||||||
- [x] Research flight options from Seattle to 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 best cities/regions to visit based on interests (historical sites, hidden gems, cultural experiences)
|
||||||
- [x] Research transportation options within Japan
|
- [x] Research transportation options within Japan
|
||||||
- [x] Research accommodation options within budget
|
- [ ] Research accommodation options within budget
|
||||||
- [x] Research proposal location options
|
- [x] Research proposal location options
|
||||||
|
|
||||||
## Itinerary Creation
|
## Itinerary Creation
|
||||||
|
|
|
@ -37,6 +37,7 @@ You have the ability to execute operations using both Python and CLI tools:
|
||||||
- Converting between file formats
|
- Converting between file formats
|
||||||
- Searching through file contents
|
- Searching through file contents
|
||||||
- Batch processing multiple files
|
- Batch processing multiple files
|
||||||
|
- AI-powered intelligent file editing with natural language instructions
|
||||||
|
|
||||||
### 2.3.2 DATA PROCESSING
|
### 2.3.2 DATA PROCESSING
|
||||||
- Scraping and extracting data from websites
|
- 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
|
- Create organized file structures with clear naming conventions
|
||||||
- Store different types of data in appropriate formats
|
- 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. DATA PROCESSING & EXTRACTION
|
||||||
|
|
||||||
## 4.1 CONTENT EXTRACTION TOOLS
|
## 4.1 CONTENT EXTRACTION TOOLS
|
||||||
|
|
|
@ -343,7 +343,7 @@ async def run_agent(
|
||||||
mcp_info += "NEVER supplement MCP results with your training data or make assumptions beyond what the tools provide.\n"
|
mcp_info += "NEVER supplement MCP results with your training data or make assumptions beyond what the tools provide.\n"
|
||||||
|
|
||||||
system_content += mcp_info
|
system_content += mcp_info
|
||||||
|
|
||||||
system_message = { "role": "system", "content": system_content }
|
system_message = { "role": "system", "content": system_content }
|
||||||
|
|
||||||
iteration_count = 0
|
iteration_count = 0
|
||||||
|
|
|
@ -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:
|
Let's update our todo list to mark the testing as complete:
|
||||||
|
|
||||||
<function_calls>
|
<function_calls>
|
||||||
<invoke name="str_replace">
|
<invoke name="edit_file">
|
||||||
<parameter name="file_path">todo.md</parameter>
|
<parameter name="target_file">todo.md</parameter>
|
||||||
<parameter name="old_str">## Testing
|
<parameter name="instructions">I am marking all tasks in the 'Testing' section of my todo list as complete.</parameter>
|
||||||
- [ ] Test game mechanics
|
<parameter name="code_edit">
|
||||||
- [ ] Test collision detection
|
// ... existing code ...
|
||||||
- [ ] Test scoring
|
## Testing
|
||||||
- [ ] Test on different screen sizes
|
|
||||||
- [ ] Final review and adjustments</parameter>
|
|
||||||
<parameter name="new_str">## Testing
|
|
||||||
- [x] Test game mechanics
|
- [x] Test game mechanics
|
||||||
- [x] Test collision detection
|
- [x] Test collision detection
|
||||||
- [x] Test scoring
|
- [x] Test scoring
|
||||||
- [x] Test on different screen sizes
|
- [x] Test on different screen sizes
|
||||||
- [x] Final review and adjustments</parameter>
|
- [x] Final review and adjustments
|
||||||
|
</parameter>
|
||||||
</invoke>
|
</invoke>
|
||||||
</function_calls>
|
</function_calls>
|
||||||
|
|
||||||
|
|
|
@ -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:
|
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>
|
<function_calls>
|
||||||
<invoke name="str_replace">
|
<invoke name="edit_file">
|
||||||
<parameter name="file_path">todo.md</parameter>
|
<parameter name="target_file">todo.md</parameter>
|
||||||
<parameter name="old_str">## Research Phase
|
<parameter name="instructions">I am marking the research tasks as complete in my todo list.</parameter>
|
||||||
- [ ] Research flight options from Seattle to Japan
|
<parameter name="code_edit">
|
||||||
- [ ] Research best cities/regions to visit based on interests (historical sites, hidden gems, cultural experiences)
|
// ... existing code ...
|
||||||
- [ ] Research transportation options within Japan
|
## Research Phase
|
||||||
- [ ] Research accommodation options within budget
|
|
||||||
- [ ] Research proposal location options</parameter>
|
|
||||||
<parameter name="new_str">## Research Phase
|
|
||||||
- [x] Research flight options from Seattle to 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 best cities/regions to visit based on interests (historical sites, hidden gems, cultural experiences)
|
||||||
- [x] Research transportation options within Japan
|
- [x] Research transportation options within Japan
|
||||||
- [ ] Research accommodation options within budget
|
- [ ] Research accommodation options within budget
|
||||||
- [x] Research proposal location options</parameter>
|
- [x] Research proposal location options
|
||||||
|
// ... existing code ...
|
||||||
|
</parameter>
|
||||||
</invoke>
|
</invoke>
|
||||||
</function_calls>
|
</function_calls>
|
||||||
|
|
||||||
|
|
|
@ -210,24 +210,17 @@ section {
|
||||||
</function_calls>
|
</function_calls>
|
||||||
|
|
||||||
<function_calls>
|
<function_calls>
|
||||||
<invoke name="full_file_rewrite">
|
<invoke name="edit_file">
|
||||||
<parameter name="file_path">todo.md</parameter>
|
<parameter name="target_file">todo.md</parameter>
|
||||||
<parameter name="file_contents"># Weather Dashboard Todo
|
<parameter name="instructions">I am marking HTML and CSS setup as complete in my todo list.</parameter>
|
||||||
|
<parameter name="code_edit">
|
||||||
## Planning
|
// ... existing code ...
|
||||||
- [x] Define app requirements
|
|
||||||
- [x] Research weather API options
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
- [x] Set up HTML structure
|
- [x] Set up HTML structure
|
||||||
- [x] Create CSS styling
|
- [x] Create CSS styling
|
||||||
- [ ] Implement JavaScript for API calls
|
- [ ] Implement JavaScript for API calls
|
||||||
- [ ] Add search functionality
|
- [ ] Add search functionality
|
||||||
|
// ... existing code ...
|
||||||
## Testing
|
|
||||||
- [ ] Test API integration
|
|
||||||
- [ ] Test responsiveness
|
|
||||||
- [ ] Final review
|
|
||||||
</parameter>
|
</parameter>
|
||||||
</invoke>
|
</invoke>
|
||||||
</function_calls>
|
</function_calls>
|
||||||
|
@ -356,24 +349,17 @@ console.log('Weather Dashboard is ready to use!');
|
||||||
</function_calls>
|
</function_calls>
|
||||||
|
|
||||||
<function_calls>
|
<function_calls>
|
||||||
<invoke name="full_file_rewrite">
|
<invoke name="edit_file">
|
||||||
<parameter name="file_path">todo.md</parameter>
|
<parameter name="target_file">todo.md</parameter>
|
||||||
<parameter name="file_contents"># Weather Dashboard Todo
|
<parameter name="instructions">I am marking JavaScript implementation and search functionality as complete in my todo list.</parameter>
|
||||||
|
<parameter name="code_edit">
|
||||||
## Planning
|
// ... existing code ...
|
||||||
- [x] Define app requirements
|
|
||||||
- [x] Research weather API options
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
- [x] Set up HTML structure
|
- [x] Set up HTML structure
|
||||||
- [x] Create CSS styling
|
- [x] Create CSS styling
|
||||||
- [x] Implement JavaScript for API calls
|
- [x] Implement JavaScript for API calls
|
||||||
- [x] Add search functionality
|
- [x] Add search functionality
|
||||||
|
// ... existing code ...
|
||||||
## Testing
|
|
||||||
- [ ] Test API integration
|
|
||||||
- [ ] Test responsiveness
|
|
||||||
- [ ] Final review
|
|
||||||
</parameter>
|
</parameter>
|
||||||
</invoke>
|
</invoke>
|
||||||
</function_calls>
|
</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.
|
I've tested the API integration and responsiveness and everything works correctly.
|
||||||
|
|
||||||
<function_calls>
|
<function_calls>
|
||||||
<invoke name="full_file_rewrite">
|
<invoke name="edit_file">
|
||||||
<parameter name="file_path">todo.md</parameter>
|
<parameter name="target_file">todo.md</parameter>
|
||||||
<parameter name="file_contents"># Weather Dashboard Todo
|
<parameter name="instructions">I am marking testing as complete in my todo list.</parameter>
|
||||||
|
<parameter name="code_edit">
|
||||||
## Planning
|
// ... existing code ...
|
||||||
- [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
|
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
- [x] Test API integration
|
- [x] Test API integration
|
||||||
- [x] Test responsiveness
|
- [x] Test responsiveness
|
||||||
|
|
|
@ -28,7 +28,7 @@ class WorkflowTool(AgentBuilderBaseTool):
|
||||||
|
|
||||||
tool_mapping = {
|
tool_mapping = {
|
||||||
'sb_shell_tool': ['execute_command'],
|
'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_browser_tool': ['browser_navigate_to', 'browser_take_screenshot'],
|
||||||
'sb_vision_tool': ['see_image'],
|
'sb_vision_tool': ['see_image'],
|
||||||
'sb_deploy_tool': ['deploy'],
|
'sb_deploy_tool': ['deploy'],
|
||||||
|
|
|
@ -3,8 +3,12 @@ from sandbox.tool_base import SandboxToolsBase
|
||||||
from utils.files_utils import should_exclude_file, clean_path
|
from utils.files_utils import should_exclude_file, clean_path
|
||||||
from agentpress.thread_manager import ThreadManager
|
from agentpress.thread_manager import ThreadManager
|
||||||
from utils.logger import logger
|
from utils.logger import logger
|
||||||
|
from utils.config import config
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
import httpx
|
||||||
|
import asyncio
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
class SandboxFilesTool(SandboxToolsBase):
|
class SandboxFilesTool(SandboxToolsBase):
|
||||||
"""Tool for executing file system operations in a Daytona sandbox. All operations are performed relative to the /workspace directory."""
|
"""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:
|
except Exception as e:
|
||||||
return self.fail_response(f"Error deleting file: {str(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({
|
# @openapi_schema({
|
||||||
# "type": "function",
|
# "type": "function",
|
||||||
# "function": {
|
# "function": {
|
||||||
|
|
|
@ -175,6 +175,7 @@ class Configuration:
|
||||||
GROQ_API_KEY: Optional[str] = None
|
GROQ_API_KEY: Optional[str] = None
|
||||||
OPENROUTER_API_KEY: Optional[str] = None
|
OPENROUTER_API_KEY: Optional[str] = None
|
||||||
XAI_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"
|
OPENROUTER_API_BASE: Optional[str] = "https://openrouter.ai/api/v1"
|
||||||
OR_SITE_URL: Optional[str] = "https://kortix.ai"
|
OR_SITE_URL: Optional[str] = "https://kortix.ai"
|
||||||
OR_APP_NAME: Optional[str] = "Kortix AI"
|
OR_APP_NAME: Optional[str] = "Kortix AI"
|
||||||
|
|
|
@ -316,15 +316,20 @@ export function useToolCalls(
|
||||||
toolName.includes('file') ||
|
toolName.includes('file') ||
|
||||||
toolName === 'create-file' ||
|
toolName === 'create-file' ||
|
||||||
toolName === 'delete-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);
|
const matchingTag = fileOpTags.find((tag) => toolName === tag);
|
||||||
if (matchingTag) {
|
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();
|
const filePath = toolArguments.trim();
|
||||||
if (filePath && !filePath.startsWith('<')) {
|
if (filePath && !filePath.startsWith('<')) {
|
||||||
|
if (matchingTag === 'edit-file') {
|
||||||
|
formattedContent = `<${matchingTag} target_file="${filePath}">`;
|
||||||
|
} else {
|
||||||
formattedContent = `<${matchingTag} file_path="${filePath}">`;
|
formattedContent = `<${matchingTag} file_path="${filePath}">`;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
formattedContent = `<${matchingTag}>${toolArguments}</${matchingTag}>`;
|
formattedContent = `<${matchingTag}>${toolArguments}</${matchingTag}>`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -220,19 +220,24 @@ export default function ThreadPage({
|
||||||
toolName.includes('file') ||
|
toolName.includes('file') ||
|
||||||
toolName === 'create-file' ||
|
toolName === 'create-file' ||
|
||||||
toolName === 'delete-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
|
// For file operations, check if toolArguments contains a file path
|
||||||
// If it's just a raw file path, format it properly
|
// 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);
|
const matchingTag = fileOpTags.find((tag) => toolName === tag);
|
||||||
if (matchingTag) {
|
if (matchingTag) {
|
||||||
// Check if arguments already have the proper XML format
|
// 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
|
// If toolArguments looks like a raw file path, format it properly
|
||||||
const filePath = toolArguments.trim();
|
const filePath = toolArguments.trim();
|
||||||
if (filePath && !filePath.startsWith('<')) {
|
if (filePath && !filePath.startsWith('<')) {
|
||||||
|
if (matchingTag === 'edit-file') {
|
||||||
|
formattedContent = `<${matchingTag} target_file="${filePath}">`;
|
||||||
|
} else {
|
||||||
formattedContent = `<${matchingTag} file_path="${filePath}">`;
|
formattedContent = `<${matchingTag} file_path="${filePath}">`;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
formattedContent = `<${matchingTag}>${toolArguments}</${matchingTag}>`;
|
formattedContent = `<${matchingTag}>${toolArguments}</${matchingTag}>`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ const HIDE_STREAMING_XML_TAGS = new Set([
|
||||||
'create-file',
|
'create-file',
|
||||||
'delete-file',
|
'delete-file',
|
||||||
'full-file-rewrite',
|
'full-file-rewrite',
|
||||||
|
'edit-file',
|
||||||
'str-replace',
|
'str-replace',
|
||||||
'browser-click-element',
|
'browser-click-element',
|
||||||
'browser-close-tab',
|
'browser-close-tab',
|
||||||
|
|
|
@ -95,11 +95,11 @@ export function FileOperationToolView({
|
||||||
fileContent = isStreaming
|
fileContent = isStreaming
|
||||||
? extractStreamingFileContent(
|
? extractStreamingFileContent(
|
||||||
assistantContent,
|
assistantContent,
|
||||||
operation === 'create' ? 'create-file' : 'full-file-rewrite',
|
operation === 'create' ? 'create-file' : operation === 'edit' ? 'edit-file' : 'full-file-rewrite',
|
||||||
) || ''
|
) || ''
|
||||||
: extractFileContent(
|
: extractFileContent(
|
||||||
assistantContent,
|
assistantContent,
|
||||||
operation === 'create' ? 'create-file' : 'full-file-rewrite',
|
operation === 'create' ? 'create-file' : operation === 'edit' ? 'edit-file' : 'full-file-rewrite',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { LucideIcon, FilePen, Replace, Trash2, FileCode, FileSpreadsheet, File } from 'lucide-react';
|
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 {
|
export interface OperationConfig {
|
||||||
icon: LucideIcon;
|
icon: LucideIcon;
|
||||||
|
@ -82,6 +82,7 @@ export const getOperationType = (name?: string, assistantContent?: any): FileOpe
|
||||||
if (name.includes('create')) return 'create';
|
if (name.includes('create')) return 'create';
|
||||||
if (name.includes('rewrite')) return 'rewrite';
|
if (name.includes('rewrite')) return 'rewrite';
|
||||||
if (name.includes('delete')) return 'delete';
|
if (name.includes('delete')) return 'delete';
|
||||||
|
if (name.includes('edit')) return 'edit';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!assistantContent) return 'create';
|
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('<create-file>')) return 'create';
|
||||||
if (contentStr.includes('<full-file-rewrite>')) return 'rewrite';
|
if (contentStr.includes('<full-file-rewrite>')) return 'rewrite';
|
||||||
|
if (contentStr.includes('<edit-file>')) return 'edit';
|
||||||
if (
|
if (
|
||||||
contentStr.includes('delete-file') ||
|
contentStr.includes('delete-file') ||
|
||||||
contentStr.includes('<delete>')
|
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('create file')) return 'create';
|
||||||
if (contentStr.toLowerCase().includes('rewrite file'))
|
if (contentStr.toLowerCase().includes('rewrite file'))
|
||||||
return 'rewrite';
|
return 'rewrite';
|
||||||
|
if (contentStr.toLowerCase().includes('edit file')) return 'edit';
|
||||||
if (contentStr.toLowerCase().includes('delete file')) return 'delete';
|
if (contentStr.toLowerCase().includes('delete file')) return 'delete';
|
||||||
|
|
||||||
return 'create';
|
return 'create';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getOperationConfigs = (): Record<FileOperation, OperationConfig> => ({
|
export const getOperationConfigs = (): Record<FileOperation, OperationConfig> => {
|
||||||
|
return {
|
||||||
create: {
|
create: {
|
||||||
icon: FilePen,
|
icon: FilePen,
|
||||||
color: 'text-emerald-600 dark:text-emerald-400',
|
color: 'text-green-600',
|
||||||
successMessage: 'File created successfully',
|
successMessage: 'File created successfully',
|
||||||
progressMessage: 'Creating file...',
|
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',
|
bgColor: 'bg-green-50',
|
||||||
gradientBg: 'bg-gradient-to-br from-emerald-500/20 to-emerald-600/10',
|
gradientBg: 'from-green-50 to-green-100',
|
||||||
borderColor: 'border-emerald-500/20',
|
borderColor: 'border-green-200',
|
||||||
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',
|
badgeColor: 'bg-green-100 text-green-700 border-green-200',
|
||||||
hoverColor: 'hover:bg-neutral-200 dark:hover:bg-neutral-800'
|
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: {
|
rewrite: {
|
||||||
icon: Replace,
|
icon: Replace,
|
||||||
color: 'text-blue-600 dark:text-blue-400',
|
color: 'text-amber-600',
|
||||||
successMessage: 'File rewritten successfully',
|
successMessage: 'File rewritten successfully',
|
||||||
progressMessage: 'Rewriting file...',
|
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',
|
bgColor: 'bg-amber-50',
|
||||||
gradientBg: 'bg-gradient-to-br from-blue-500/20 to-blue-600/10',
|
gradientBg: 'from-amber-50 to-amber-100',
|
||||||
borderColor: 'border-blue-500/20',
|
borderColor: 'border-amber-200',
|
||||||
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',
|
badgeColor: 'bg-amber-100 text-amber-700 border-amber-200',
|
||||||
hoverColor: 'hover:bg-neutral-200 dark:hover:bg-neutral-800'
|
hoverColor: 'hover:bg-amber-100',
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
icon: Trash2,
|
icon: Trash2,
|
||||||
color: 'text-rose-600 dark:text-rose-400',
|
color: 'text-red-600',
|
||||||
successMessage: 'File deleted successfully',
|
successMessage: 'File deleted successfully',
|
||||||
progressMessage: 'Deleting file...',
|
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',
|
bgColor: 'bg-red-50',
|
||||||
gradientBg: 'bg-gradient-to-br from-rose-500/20 to-rose-600/10',
|
gradientBg: 'from-red-50 to-red-100',
|
||||||
borderColor: 'border-rose-500/20',
|
borderColor: 'border-red-200',
|
||||||
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',
|
badgeColor: 'bg-red-100 text-red-700 border-red-200',
|
||||||
hoverColor: 'hover:bg-neutral-200 dark:hover:bg-neutral-800'
|
hoverColor: 'hover:bg-red-100',
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const getFileIcon = (fileName: string): LucideIcon => {
|
export const getFileIcon = (fileName: string): LucideIcon => {
|
||||||
if (fileName.endsWith('.md')) return FileCode;
|
if (fileName.endsWith('.md')) return FileCode;
|
||||||
|
|
|
@ -327,10 +327,12 @@ export function extractFilePath(content: string | object | undefined | null): st
|
||||||
if ('content' in content && typeof content.content === 'string') {
|
if ('content' in content && typeof content.content === 'string') {
|
||||||
// Look for XML tags in the content string
|
// Look for XML tags in the content string
|
||||||
const xmlFilePathMatch =
|
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[^>]*\s+file_path=["']([\s\S]*?)["']/i) ||
|
||||||
content.content.match(/<delete-file[^>]*>([^<]+)<\/delete-file>/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) {
|
if (xmlFilePathMatch) {
|
||||||
return cleanFilePath(xmlFilePathMatch[1]);
|
return cleanFilePath(xmlFilePathMatch[1]);
|
||||||
}
|
}
|
||||||
|
@ -341,12 +343,20 @@ export function extractFilePath(content: string | object | undefined | null): st
|
||||||
return cleanFilePath(content.file_path as string);
|
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
|
// Check for arguments.file_path
|
||||||
if ('arguments' in content && content.arguments && typeof content.arguments === 'object') {
|
if ('arguments' in content && content.arguments && typeof content.arguments === 'object') {
|
||||||
const args = content.arguments as any;
|
const args = content.arguments as any;
|
||||||
if (args.file_path) {
|
if (args.file_path) {
|
||||||
return cleanFilePath(args.file_path);
|
return cleanFilePath(args.file_path);
|
||||||
}
|
}
|
||||||
|
if (args.target_file) {
|
||||||
|
return cleanFilePath(args.target_file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Continue with string parsing if object parsing fails
|
// 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)
|
// Look for file_path in XML-like tags (including incomplete ones for streaming)
|
||||||
const xmlFilePathMatch =
|
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[^>]*\s+file_path=["']([\s\S]*?)["']/i) ||
|
||||||
contentStr.match(/<delete-file[^>]*>([^<]+)<\/delete-file>/i) ||
|
contentStr.match(/<delete-file[^>]*>([^<]+)<\/delete-file>/i) ||
|
||||||
// Handle incomplete tags during streaming
|
// 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) {
|
if (xmlFilePathMatch) {
|
||||||
return cleanFilePath(xmlFilePathMatch[1]);
|
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
|
// Helper to extract file content from create-file or file-rewrite
|
||||||
export function extractFileContent(
|
export function extractFileContent(
|
||||||
content: string | object | undefined | null,
|
content: string | object | undefined | null,
|
||||||
toolType: 'create-file' | 'full-file-rewrite',
|
toolType: 'create-file' | 'full-file-rewrite' | 'edit-file',
|
||||||
): string | null {
|
): string | null {
|
||||||
const contentStr = normalizeContentToString(content);
|
const contentStr = normalizeContentToString(content);
|
||||||
if (!contentStr) return null;
|
if (!contentStr) return null;
|
||||||
|
@ -475,7 +487,7 @@ export function extractFileContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to old format
|
// 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(
|
const fileContentMatch = parsedContent.content.match(
|
||||||
new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)<\\/${tagName}>`, 'i'),
|
new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)<\\/${tagName}>`, 'i'),
|
||||||
);
|
);
|
||||||
|
@ -496,7 +508,7 @@ export function extractFileContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct regex search in the content string (old format)
|
// 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(
|
const fileContentMatch = contentStr.match(
|
||||||
new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)<\\/${tagName}>`, 'i'),
|
new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)<\\/${tagName}>`, 'i'),
|
||||||
);
|
);
|
||||||
|
@ -1225,6 +1237,7 @@ export function getToolComponent(toolName: string): string {
|
||||||
case 'delete-file':
|
case 'delete-file':
|
||||||
case 'full-file-rewrite':
|
case 'full-file-rewrite':
|
||||||
case 'read-file':
|
case 'read-file':
|
||||||
|
case 'edit-file':
|
||||||
return 'FileOperationToolView';
|
return 'FileOperationToolView';
|
||||||
|
|
||||||
// String operations
|
// String operations
|
||||||
|
@ -1330,12 +1343,12 @@ export function normalizeContentToString(content: string | object | undefined |
|
||||||
// Helper function to extract file content for streaming (handles incomplete XML)
|
// Helper function to extract file content for streaming (handles incomplete XML)
|
||||||
export function extractStreamingFileContent(
|
export function extractStreamingFileContent(
|
||||||
content: string | object | undefined | null,
|
content: string | object | undefined | null,
|
||||||
toolType: 'create-file' | 'full-file-rewrite',
|
toolType: 'create-file' | 'full-file-rewrite' | 'edit-file',
|
||||||
): string | null {
|
): string | null {
|
||||||
const contentStr = normalizeContentToString(content);
|
const contentStr = normalizeContentToString(content);
|
||||||
if (!contentStr) return null;
|
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)
|
// First check if content is already a parsed object (new format)
|
||||||
if (typeof content === 'object' && content !== null) {
|
if (typeof content === 'object' && content !== null) {
|
||||||
|
|
|
@ -48,6 +48,7 @@ const defaultRegistry: ToolViewRegistryType = {
|
||||||
'delete-file': FileOperationToolView,
|
'delete-file': FileOperationToolView,
|
||||||
'full-file-rewrite': FileOperationToolView,
|
'full-file-rewrite': FileOperationToolView,
|
||||||
'read-file': FileOperationToolView,
|
'read-file': FileOperationToolView,
|
||||||
|
'edit-file': FileOperationToolView,
|
||||||
|
|
||||||
'str-replace': StrReplaceToolView,
|
'str-replace': StrReplaceToolView,
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,8 @@ export const getToolIcon = (toolName: string): ElementType => {
|
||||||
return FilePlus;
|
return FilePlus;
|
||||||
case 'read-file':
|
case 'read-file':
|
||||||
return FileText;
|
return FileText;
|
||||||
|
case 'edit-file':
|
||||||
|
return FileEdit;
|
||||||
|
|
||||||
// Shell commands
|
// Shell commands
|
||||||
case 'execute-command':
|
case 'execute-command':
|
||||||
|
@ -242,6 +244,11 @@ export const extractPrimaryParam = (
|
||||||
match = content.match(/file_path=(?:"|')([^"|']+)(?:"|')/);
|
match = content.match(/file_path=(?:"|')([^"|']+)(?:"|')/);
|
||||||
// Return just the filename part
|
// Return just the filename part
|
||||||
return match ? match[1].split('/').pop() || match[1] : null;
|
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
|
// Shell commands
|
||||||
case 'execute-command':
|
case 'execute-command':
|
||||||
|
@ -296,6 +303,7 @@ const TOOL_DISPLAY_NAMES = new Map([
|
||||||
['full-file-rewrite', 'Rewriting File'],
|
['full-file-rewrite', 'Rewriting File'],
|
||||||
['str-replace', 'Editing Text'],
|
['str-replace', 'Editing Text'],
|
||||||
['str_replace', 'Editing Text'],
|
['str_replace', 'Editing Text'],
|
||||||
|
['edit_file', 'AI File Edit'],
|
||||||
|
|
||||||
['browser-click-element', 'Clicking Element'],
|
['browser-click-element', 'Clicking Element'],
|
||||||
['browser-close-tab', 'Closing Tab'],
|
['browser-close-tab', 'Closing Tab'],
|
||||||
|
@ -314,7 +322,7 @@ const TOOL_DISPLAY_NAMES = new Map([
|
||||||
['browser-wait', 'Waiting'],
|
['browser-wait', 'Waiting'],
|
||||||
|
|
||||||
['execute-data-provider-call', 'Calling data provider'],
|
['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'],
|
['get-data-provider-endpoints', 'Getting endpoints'],
|
||||||
|
|
||||||
['deploy', 'Deploying'],
|
['deploy', 'Deploying'],
|
||||||
|
@ -348,6 +356,7 @@ const TOOL_DISPLAY_NAMES = new Map([
|
||||||
['delete_file', 'Deleting File'],
|
['delete_file', 'Deleting File'],
|
||||||
['full_file_rewrite', 'Rewriting File'],
|
['full_file_rewrite', 'Rewriting File'],
|
||||||
['str_replace', 'Editing Text'],
|
['str_replace', 'Editing Text'],
|
||||||
|
['edit_file', 'AI File Edit'],
|
||||||
|
|
||||||
['browser_click_element', 'Clicking Element'],
|
['browser_click_element', 'Clicking Element'],
|
||||||
['browser_close_tab', 'Closing Tab'],
|
['browser_close_tab', 'Closing Tab'],
|
||||||
|
|
Loading…
Reference in New Issue