Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions proto/cline/models.proto
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ message ModelsApiConfiguration {
optional string qwen_code_oauth_path = 70;
optional string dify_api_key = 71;
optional string dify_base_url = 72;
optional string morph_api_key = 73;
optional string morph_api_url = 74;

// Plan mode configurations
optional ApiProvider plan_mode_api_provider = 100;
Expand Down
2 changes: 2 additions & 0 deletions proto/cline/state.proto
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ message ApiConfiguration {
optional string qwen_code_oauth_path = 63;
optional string dify_api_key = 64;
optional string dify_base_url = 65;
optional string morph_api_key = 66;
optional string morph_api_url = 67;

// Plan mode configurations
optional string plan_mode_api_provider = 100;
Expand Down
3 changes: 3 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ export enum Environment {
local = "local",
}

export const DEFAULT_MORPH_API_URL = "https://api.morphllm.com/v1"
export const DEFAULT_MORPH_MODEL = "morph-v3-fast"

interface EnvironmentConfig {
appBaseUrl: string
apiBaseUrl: string
Expand Down
4 changes: 4 additions & 0 deletions src/core/assistant-message/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const toolUseNames = [
"read_file",
"write_to_file",
"replace_in_file",
"edit_file",
"search_files",
"list_files",
"list_code_definition_names",
Expand Down Expand Up @@ -40,6 +41,9 @@ export const toolParamNames = [
"path",
"content",
"diff",
"target_file",
"instructions",
"code_edit",
"regex",
"file_pattern",
"recursive",
Expand Down
44 changes: 28 additions & 16 deletions src/core/assistant-message/parse-assistant-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,26 +102,38 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess
currentCharIndex - toolCloseTag.length + 1, // To before the tool closing tag
)

// Check if content parameter needs special handling (write_to_file/new_rule)
// This check is important if the closing </content> tag was missed by the parameter parsing logic
// Special handling for multi-line content parameters (write_to_file/new_rule/edit_file)
// This check ensures correct parsing even if the closing tag was missed by the standard parameter parsing logic
// (e.g., if content is empty or parsing logic prioritizes tool close)
const contentParamName: ToolParamName = "content"
if (
currentToolUse.name === "write_to_file" /* || currentToolUse.name === "new_rule" */ &&
toolContentSlice.includes(`<${contentParamName}>`)
) {
const contentStartTag = `<${contentParamName}>`
const contentEndTag = `</${contentParamName}>`
const contentStart = toolContentSlice.indexOf(contentStartTag)
// Use lastIndexOf for robustness against nested tags
const contentEnd = toolContentSlice.lastIndexOf(contentEndTag)

if (contentStart !== -1 && contentEnd !== -1 && contentEnd > contentStart) {
const contentValue = toolContentSlice.slice(contentStart + contentStartTag.length, contentEnd).trim()
currentToolUse.params[contentParamName] = contentValue

const handleSpecialContentParam = (paramName: ToolParamName) => {
if (toolContentSlice.includes(`<${paramName}>`)) {
const startTag = `<${paramName}>`
const endTag = `</${paramName}>`
const start = toolContentSlice.indexOf(startTag)
// Use lastIndexOf for robustness against potentially nested tags within the content
const end = toolContentSlice.lastIndexOf(endTag)

if (start !== -1 && end !== -1 && end > start) {
// Only update if the parameter hasn't already been captured by the primary logic
if (currentToolUse!.params[paramName] === undefined) {
const value = toolContentSlice.slice(start + startTag.length, end).trim()
currentToolUse!.params[paramName] = value
}
}
}
}

if (currentToolUse.name === "write_to_file" /* || currentToolUse.name === "new_rule" */) {
handleSpecialContentParam("content")
} else if (currentToolUse.name === "edit_file") {
handleSpecialContentParam("code_edit")
// 'instructions' and 'target_file' are typically handled by the standard logic as they are single-line,
// but we include them here as a safeguard against malformed XML.
handleSpecialContentParam("instructions")
handleSpecialContentParam("target_file")
}

currentToolUse.partial = false // Mark as complete
contentBlocks.push(currentToolUse)
currentToolUse = undefined // Reset state
Expand Down
2 changes: 2 additions & 0 deletions src/core/controller/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,8 @@ export class Controller {
mcpResponsesCollapsed,
terminalOutputLineLimit,
customPrompt,
// expose replace_in_file toggle to webview
enableSearchReplaceTool: this.stateManager.getGlobalStateKey("enableSearchReplaceTool"),
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/core/controller/state/updateSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ export async function updateSettings(controller: Controller, request: UpdateSett
controller.stateManager.setGlobalState("enableCheckpointsSetting", request.enableCheckpointsSetting)
}

// New: Update enableSearchReplaceTool setting
if ((request as any).enableSearchReplaceTool !== undefined) {
controller.stateManager.setGlobalState(
"enableSearchReplaceTool" as any,
Boolean((request as any).enableSearchReplaceTool) as unknown as never,
)
}

// Update MCP marketplace setting
if (request.mcpMarketplaceEnabled !== undefined) {
controller.stateManager.setGlobalState("mcpMarketplaceEnabled", request.mcpMarketplaceEnabled)
Expand Down
103 changes: 62 additions & 41 deletions src/core/prompts/system-prompt/components/editing_files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,57 +4,84 @@ import type { PromptVariant, SystemPromptContext } from "../types"

const EDITING_FILES_TEMPLATE_TEXT = `EDITING FILES

You have access to two tools for working with files: **write_to_file** and **replace_in_file**. Understanding their roles and selecting the right one for the job will help ensure efficient and accurate modifications.
You have access to three tools for working with files: **edit_file**, **replace_in_file**, and **write_to_file**. Your default tool for any modification to an existing file must be **edit_file**. The other tools should only be used as fallbacks in specific scenarios.

# write_to_file
**Decision Hierarchy: Always prefer \`edit_file\` > \`replace_in_file\` > \`write_to_file\` for existing files.**

## Purpose
# edit_file (Primary Tool)

- Create a new file, or overwrite the entire contents of an existing file.
## Purpose
Make semantic, targeted edits to existing files by specifying only the changed lines and using placeholders for unchanged code. This is the safest and most efficient method.

## When to Use

- Initial file creation, such as when scaffolding a new project.
- Overwriting large boilerplate files where you want to replace the entire content at once.
- When the complexity or number of changes would make replace_in_file unwieldy or error-prone.
- When you need to completely restructure a file's content or change its fundamental organization.
- **This is your default tool for all edits to existing files.**
- Use for single or multiple changes within a file, from simple line edits to complex structural refactoring.

## Important Considerations
- Use placeholders to represent unchanged sections (e.g., \`// ... existing code ...\`). Provide minimal but sufficient surrounding context (1-3 lines) before and after your changes to ensure accuracy.
- Combine all required changes for a single file into one \`edit_file\` call. The tool is designed to handle multiple distinct edits at once.

- Using write_to_file requires providing the file's complete final content.
- If you only need to make small changes to an existing file, consider using replace_in_file instead to avoid unnecessarily rewriting the entire file.
- While write_to_file should not be your default choice, don't hesitate to use it when the situation truly calls for it.
# replace_in_file (Fallback Tool)

# replace_in_file
## Purpose & When to Use
Use this tool **only as a fallback** to \`edit_file\` if it has failed. It performs a simple search and replace.

## Purpose
# write_to_file (Use with Caution)

## Purpose & When to Use
- **Creating new files.**
- **Complete rewrites:** Only use this to overwrite an existing file when the changes are so extensive that both \`edit_file\` and \`replace_in_file\` are impractical or impossible.

# Choosing the Appropriate Tool

- Make targeted edits to specific parts of an existing file without overwriting the entire file.
- **Always default to \`edit_file\` for any modification.** It is the most robust and preferred method.

# Auto-formatting Considerations

- After using edit_file, the user's editor may automatically format the file
- This auto-formatting may modify the file contents, for example:
- Breaking single lines into multiple lines
- Adjusting indentation to match project style (e.g. 2 spaces vs 4 spaces vs tabs)
- Converting single quotes to double quotes (or vice versa based on project preferences)
- Organizing imports (e.g. sorting, grouping by type)
- Adding/removing trailing commas in objects and arrays
- Enforcing consistent brace style (e.g. same-line vs new-line)
- Standardizing semicolon usage (adding or removing based on style)
- The edit_file tool response will include the final state of the file after any auto-formatting
- Use this final state as your reference point for any subsequent edits.`

const EDITING_FILES_NO_REPLACE_TEXT = `EDITING FILES

You have access to two tools for working with files: **edit_file** and **write_to_file**. The **replace_in_file** (Search/Replace) tool is disabled by default and not available unless explicitly enabled in settings.

**Decision Hierarchy: Always prefer \`edit_file\` > \`write_to_file\` for existing files.**

# edit_file (Primary Tool)

## Purpose
Make semantic, targeted edits to existing files by specifying only the changed lines and using placeholders for unchanged code. This is the safest and most efficient method.

## When to Use
- **This is your default tool for all edits to existing files.**
- Use for single or multiple changes within a file, from simple line edits to complex structural refactoring.

- Small, localized changes like updating a few lines, function implementations, changing variable names, modifying a section of text, etc.
- Targeted improvements where only specific portions of the file's content needs to be altered.
- Especially useful for long files where much of the file will remain unchanged.
## Important Considerations
- Use placeholders to represent unchanged sections (e.g., \`// ... existing code ...\`). Provide minimal but sufficient surrounding context (1-3 lines) before and after your changes to ensure accuracy.
- Combine all required changes for a single file into one \`edit_file\` call. The tool is designed to handle multiple distinct edits at once.

## Advantages
# write_to_file (Use with Caution)

- More efficient for minor edits, since you don't need to supply the entire file content.
- Reduces the chance of errors that can occur when overwriting large files.
## Purpose & When to Use
- **Creating new files.**
- **Complete rewrites:** Only use this to overwrite an existing file when the changes are so extensive that \`edit_file\` is impractical.

# Choosing the Appropriate Tool

- **Default to replace_in_file** for most changes. It's the safer, more precise option that minimizes potential issues.
- **Use write_to_file** when:
- Creating new files
- The changes are so extensive that using replace_in_file would be more complex or risky
- You need to completely reorganize or restructure a file
- The file is relatively small and the changes affect most of its content
- You're generating boilerplate or template files
- **Always default to \`edit_file\` for any modification.** It is the most robust and preferred method.

# Auto-formatting Considerations

- After using either write_to_file or replace_in_file, the user's editor may automatically format the file
- After using edit_file, the user's editor may automatically format the file
- This auto-formatting may modify the file contents, for example:
- Breaking single lines into multiple lines
- Adjusting indentation to match project style (e.g. 2 spaces vs 4 spaces vs tabs)
Expand All @@ -63,19 +90,13 @@ You have access to two tools for working with files: **write_to_file** and **rep
- Adding/removing trailing commas in objects and arrays
- Enforcing consistent brace style (e.g. same-line vs new-line)
- Standardizing semicolon usage (adding or removing based on style)
- The write_to_file and replace_in_file tool responses will include the final state of the file after any auto-formatting
- Use this final state as your reference point for any subsequent edits. This is ESPECIALLY important when crafting SEARCH blocks for replace_in_file which require the content to match what's in the file exactly.

# Workflow Tips

1. Before editing, assess the scope of your changes and decide which tool to use.
2. For targeted edits, apply replace_in_file with carefully crafted SEARCH/REPLACE blocks. If you need multiple changes, you can stack multiple SEARCH/REPLACE blocks within a single replace_in_file call.
3. For major overhauls or initial file creation, rely on write_to_file.
4. Once the file has been edited with either write_to_file or replace_in_file, the system will provide you with the final state of the modified file. Use this updated content as the reference point for any subsequent SEARCH/REPLACE operations, since it reflects any auto-formatting or user-applied changes.
By thoughtfully selecting between write_to_file and replace_in_file, you can make your file editing process smoother, safer, and more efficient.`
- The edit_file tool response will include the final state of the file after any auto-formatting
- Use this final state as your reference point for any subsequent edits.`

export async function getEditingFilesSection(variant: PromptVariant, _context: SystemPromptContext): Promise<string> {
const template = variant.componentOverrides?.[SystemPromptSection.EDITING_FILES]?.template || EDITING_FILES_TEMPLATE_TEXT
export async function getEditingFilesSection(variant: PromptVariant, context: SystemPromptContext): Promise<string> {
const template =
variant.componentOverrides?.[SystemPromptSection.EDITING_FILES]?.template ||
(context.enableSearchReplaceTool ? EDITING_FILES_TEMPLATE_TEXT : EDITING_FILES_NO_REPLACE_TEXT)

return new TemplateEngine().resolve(template, {})
}
54 changes: 23 additions & 31 deletions src/core/prompts/system-prompt/components/tool_use/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const FOCUS_CHAIN_EXAMPLE_EDIT = `<task_progress>

const TOOL_USE_EXAMPLES_TEMPLATE_TEXT = `# Tool Use Examples

To make changes to code, always use the edit_file tool.

## Example 1: Requesting to execute a command

<execute_command>
Expand Down Expand Up @@ -87,43 +89,33 @@ const TOOL_USE_EXAMPLES_TEMPLATE_TEXT = `# Tool Use Examples
</context>
</new_task>

## Example 4: Requesting to make targeted edits to a file
## Example 4: Requesting to make edits to a file

<replace_in_file>
<path>src/components/App.tsx</path>
<diff>
------- SEARCH
import React from 'react';
=======
<edit_file>
<target_file>src/components/App.tsx</target_file>
<instructions>I will add a loading state and update submit logic</instructions>
<code_edit>
import React, { useState } from 'react';
+++++++ REPLACE

------- SEARCH
function handleSubmit() {
saveData();
setLoading(false);
}
function App() {
const [loading, setLoading] = useState(false);

=======
+++++++ REPLACE
async function handleSubmit() {
setLoading(true);
// ... existing code ...
setLoading(false);
}

------- SEARCH
return (
<div>
=======
function handleSubmit() {
saveData();
setLoading(false);
return (
<div>
{/* ... existing code ... */}
</div>
);
}
</code_edit>
{{FOCUS_CHAIN_EXAMPLE_EDIT}}</edit_file>

return (
<div>
+++++++ REPLACE
</diff>
{{FOCUS_CHAIN_EXAMPLE_EDIT}}</replace_in_file>


## Example 5: Requesting to use an MCP tool
## Example 6: Requesting to use an MCP tool

<use_mcp_tool>
<server_name>weather-server</server_name>
Expand All @@ -136,7 +128,7 @@ return (
</arguments>
</use_mcp_tool>

## Example 6: Another example of using an MCP tool (where the server name is a unique identifier such as a URL)
## Example 7: Another example of using an MCP tool (where the server name is a unique identifier such as a URL)

<use_mcp_tool>
<server_name>github.com/modelcontextprotocol/servers/tree/main/src/github</server_name>
Expand Down
Loading
Loading