Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export GOOSE_EDITOR_MODEL="claude-3-5-sonnet-20241022"
```bash
export GOOSE_EDITOR_API_KEY="sk-..."
export GOOSE_EDITOR_HOST="https://api.morphllm.com/v1"
export GOOSE_EDITOR_MODEL="morph-v0"
export GOOSE_EDITOR_MODEL="morph-v3-large"
```

**Relace**
Expand Down
2 changes: 1 addition & 1 deletion crates/goose-mcp/src/developer/editor_models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ pub fn create_editor_model() -> Option<EditorModel> {
// Determine which editor to use based on the host
if host.contains("relace.run") {
Some(EditorModel::Relace(RelaceEditor::new(api_key, host, model)))
} else if host.contains("api.morphllm") {
} else if host.contains("api.morphllm") || model.contains("morph") {
Some(EditorModel::MorphLLM(MorphLLMEditor::new(
api_key, host, model,
)))
Expand Down
203 changes: 198 additions & 5 deletions crates/goose-mcp/src/developer/editor_models/morphllm_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,45 @@ impl MorphLLMEditor {
model,
}
}

/// Extract content between XML tags
fn extract_tag_content(text: &str, tag_name: &str) -> Option<String> {
let start_tag = format!("<{}>", tag_name);
let end_tag = format!("</{}>", tag_name);

if let (Some(start_pos), Some(end_pos)) = (text.find(&start_tag), text.find(&end_tag)) {
if start_pos < end_pos {
let content_start = start_pos + start_tag.len();
let content = &text[content_start..end_pos];
return Some(content.trim().to_string());
}
}
None
}

fn format_user_prompt(original_code: &str, update_snippet: &str) -> String {
if let Some(code_content) = Self::extract_tag_content(update_snippet, "code") {
// Look for instruction tags which help provide hints
if let Some(instruction_content) =
Self::extract_tag_content(update_snippet, "instruction")
{
// Both code and instruction tags found
return format!(
"<instruction>{}</instruction>\n<code>{}</code>\n<update>{}</update>",
instruction_content, original_code, code_content
);
}
// Only code tags found, no instruction
return format!(
"<code>{}</code>\n<update>{}</update>",
original_code, code_content
);
}
format!(
"<code>{}</code>\n<update>{}</update>",
original_code, update_snippet
)
}
}

impl EditorModelImpl for MorphLLMEditor {
Expand All @@ -42,11 +81,8 @@ impl EditorModelImpl for MorphLLMEditor {
// Create the client
let client = Client::new();

// Format the prompt as specified in the Python example
let user_prompt = format!(
"<code>{}</code>\n<update>{}</update>",
original_code, update_snippet
);
// Parse update_snippet for <code> and <instruction> tags
let user_prompt = Self::format_user_prompt(original_code, update_snippet);

// Prepare the request body for OpenAI-compatible API
let body = json!({
Expand Down Expand Up @@ -99,6 +135,26 @@ impl EditorModelImpl for MorphLLMEditor {
fn get_str_replace_description(&self) -> &'static str {
"Use the edit_file to propose an edit to an existing file.
This 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.

**IMPORTANT**: in the new_str parameter, you must also provide an `instruction` - a single sentence written in the first person describing what you are going to do for the sketched edit.
This instruction helps the less intelligent model understand and apply your edit correctly.

Examples of good instructions:
- I am adding error handling to the user authentication function and removing the old authentication method
- The instruction should be specific enough to disambiguate any uncertainty in your edit.


The format for new_str should be like this example:

<code>
new code here you want to add
</code>
<instruction>
adding new code with error handling
</instruction>

provide this to new_str as a single string.

When writing the edit, you should specify each edit in sequence, with the special comment // ... existing code ... to represent unchanged code in between edited lines.

For example:
Expand All @@ -117,3 +173,140 @@ impl EditorModelImpl for MorphLLMEditor {
"
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_extract_tag_content_valid() {
let text = "<code>fn main() {}</code>";
let result = MorphLLMEditor::extract_tag_content(text, "code");
assert_eq!(result, Some("fn main() {}".to_string()));
}

#[test]
fn test_extract_tag_content_with_whitespace() {
let text = "<instruction> I am adding a print statement </instruction>";
let result = MorphLLMEditor::extract_tag_content(text, "instruction");
assert_eq!(result, Some("I am adding a print statement".to_string()));
}

#[test]
fn test_extract_tag_content_invalid_order() {
let text = "</code>Invalid<code>";
let result = MorphLLMEditor::extract_tag_content(text, "code");
assert_eq!(result, None);
}

#[test]
fn test_extract_tag_content_missing_end_tag() {
let text = "<code>fn main() {}";
let result = MorphLLMEditor::extract_tag_content(text, "code");
assert_eq!(result, None);
}

#[test]
fn test_extract_tag_content_missing_start_tag() {
let text = "fn main() {}</code>";
let result = MorphLLMEditor::extract_tag_content(text, "code");
assert_eq!(result, None);
}

#[test]
fn test_extract_tag_content_nested_tags() {
let text = "<code>fn main() { <code>nested</code> }</code>";
let result = MorphLLMEditor::extract_tag_content(text, "code");
assert_eq!(result, Some("fn main() { <code>nested".to_string()));
}

#[test]
fn test_format_user_prompt_no_tags() {
let original_code = "fn main() {}";
let update_snippet = "Add error handling";
let result = MorphLLMEditor::format_user_prompt(original_code, update_snippet);
assert_eq!(
result,
"<code>fn main() {}</code>\n<update>Add error handling</update>"
);
}

#[test]
fn test_format_user_prompt_with_code_tags_only() {
let original_code = "fn main() {}";
let update_snippet = "<code>fn main() { println!(\"Hello\"); }</code>";
let result = MorphLLMEditor::format_user_prompt(original_code, update_snippet);
assert_eq!(
result,
"<code>fn main() {}</code>\n<update>fn main() { println!(\"Hello\"); }</update>"
);
}

#[test]
fn test_format_user_prompt_with_both_tags() {
let original_code = "fn main() {}";
let update_snippet = "<code>fn main() { println!(\"Hello\"); }</code><instruction>I am adding a print statement</instruction>";
let result = MorphLLMEditor::format_user_prompt(original_code, update_snippet);
assert_eq!(
result,
"<instruction>I am adding a print statement</instruction>\n<code>fn main() {}</code>\n<update>fn main() { println!(\"Hello\"); }</update>"
);
}

#[test]
fn test_format_user_prompt_with_whitespace() {
let original_code = "fn main() {}";
let update_snippet = "<code> fn main() { println!(\"Hello\"); } </code><instruction> I am adding a print statement </instruction>";
let result = MorphLLMEditor::format_user_prompt(original_code, update_snippet);
assert_eq!(
result,
"<instruction>I am adding a print statement</instruction>\n<code>fn main() {}</code>\n<update>fn main() { println!(\"Hello\"); }</update>"
);
}

#[test]
fn test_format_user_prompt_invalid_code_tags() {
let original_code = "fn main() {}";
let update_snippet = "</code>Invalid<code>";
let result = MorphLLMEditor::format_user_prompt(original_code, update_snippet);
assert_eq!(
result,
"<code>fn main() {}</code>\n<update></code>Invalid<code></update>"
);
}

#[test]
fn test_format_user_prompt_invalid_instruction_tags() {
let original_code = "fn main() {}";
let update_snippet =
"<code>fn main() { println!(\"Hello\"); }</code></instruction>Invalid<instruction>";
let result = MorphLLMEditor::format_user_prompt(original_code, update_snippet);
assert_eq!(
result,
"<code>fn main() {}</code>\n<update>fn main() { println!(\"Hello\"); }</update>"
);
}

#[test]
fn test_format_user_prompt_nested_tags() {
let original_code = "fn main() {}";
let update_snippet = "<code>fn main() { <code>nested</code> }</code>";
let result = MorphLLMEditor::format_user_prompt(original_code, update_snippet);
// Should use the first occurrence of <code> and find its matching </code>
assert_eq!(
result,
"<code>fn main() {}</code>\n<update>fn main() { <code>nested</update>"
);
}

#[test]
fn test_format_user_prompt_tags_in_different_order() {
let original_code = "fn main() {}";
let update_snippet = "<instruction>I am adding a print statement</instruction><code>fn main() { println!(\"Hello\"); }</code>";
let result = MorphLLMEditor::format_user_prompt(original_code, update_snippet);
assert_eq!(
result,
"<instruction>I am adding a print statement</instruction>\n<code>fn main() {}</code>\n<update>fn main() { println!(\"Hello\"); }</update>"
);
}
}
8 changes: 5 additions & 3 deletions crates/goose-mcp/src/developer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,11 +270,13 @@ impl DeveloperRouter {

To use the write command, you must specify `file_text` which will become the new content of the file. Be careful with
existing files! This is a full overwrite, so you must include everything - not just sections you are modifying.

To use the edit_file command, you must specify both `old_str` and `new_str` - {}.


To use the insert command, you must specify both `insert_line` (the line number after which to insert, 0 for beginning)
and `new_str` (the text to insert).

To use the edit_file command, you must specify both `old_str` and `new_str`
{}

"#, editor.get_str_replace_description()},
"edit_file",
)
Expand Down
Loading