mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-04 02:52:55 +08:00
fix(tools): reject empty old_string in EditNotebook
StrReplace rejects an empty `old_string`, but the EditNotebook cell-edit
path did not. Because `str::match_indices("")` matches at every byte
boundary, editing a non-empty cell with an empty `old_string` failed with
a misleading "old_string is not unique in the notebook cell; found N
occurrences" error, and editing an empty cell silently prepended
`new_string`.
Add the same guard StrReplace already uses so both edit tools reject an
empty `old_string` consistently.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
76003a907a
commit
97ee138de8
@@ -183,6 +183,9 @@ fn edit_notebook(call: &ToolCall, before: &str) -> std::result::Result<String, S
|
||||
.unwrap_or_default();
|
||||
let old =
|
||||
normalize_newlines(&string(call, "old_string").map_err(|error| error.to_string())?);
|
||||
if old.is_empty() {
|
||||
return Err("old_string must not be empty".into());
|
||||
}
|
||||
let occurrences = source.match_indices(&old).count();
|
||||
let edited = match occurrences {
|
||||
0 => return Err("old_string was not found in the notebook cell".into()),
|
||||
@@ -241,3 +244,49 @@ fn normalized(value: &str) -> String {
|
||||
.flat_map(char::to_lowercase)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use super::edit_notebook;
|
||||
use crate::model::ToolCall;
|
||||
|
||||
fn notebook_call(old_string: &str) -> ToolCall {
|
||||
ToolCall {
|
||||
index: 0,
|
||||
call_id: "call".into(),
|
||||
model_call_id: "model".into(),
|
||||
name: "EditNotebook".into(),
|
||||
arguments_text: String::new(),
|
||||
arguments: json!({
|
||||
"target_notebook": "/notebook.ipynb",
|
||||
"cell_idx": 0,
|
||||
"old_string": old_string,
|
||||
"new_string": "replacement",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn single_cell_notebook() -> String {
|
||||
json!({
|
||||
"cells": [{"cell_type": "code", "source": ["print('hi')\n"]}],
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_notebook_rejects_empty_old_string() {
|
||||
// StrReplace rejects an empty old_string; EditNotebook must do the same
|
||||
// instead of prepending new_string (empty cell) or reporting a
|
||||
// misleading "not unique" error (non-empty cell).
|
||||
let error = edit_notebook(¬ebook_call(""), &single_cell_notebook()).unwrap_err();
|
||||
assert_eq!(error, "old_string must not be empty");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_notebook_replaces_a_unique_old_string() {
|
||||
let edited = edit_notebook(¬ebook_call("hi"), &single_cell_notebook()).unwrap();
|
||||
assert!(edited.contains("print('replacement')"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user