(
{ notebook_path, cell_type, cell_id, edit_mode = 'replace' },
toolUseContext: ToolUseContext,
)
| 174 | renderToolUseErrorMessage, |
| 175 | renderToolResultMessage, |
| 176 | async validateInput( |
| 177 | { notebook_path, cell_type, cell_id, edit_mode = 'replace' }, |
| 178 | toolUseContext: ToolUseContext, |
| 179 | ) { |
| 180 | const fullPath = isAbsolute(notebook_path) |
| 181 | ? notebook_path |
| 182 | : resolve(getCwd(), notebook_path) |
| 183 | |
| 184 | // SECURITY: Skip filesystem operations for UNC paths to prevent NTLM credential leaks. |
| 185 | if (fullPath.startsWith('\\\\') || fullPath.startsWith('//')) { |
| 186 | return { result: true } |
| 187 | } |
| 188 | |
| 189 | if (extname(fullPath) !== '.ipynb') { |
| 190 | return { |
| 191 | result: false, |
| 192 | message: |
| 193 | 'File must be a Jupyter notebook (.ipynb file). For editing other file types, use the FileEdit tool.', |
| 194 | errorCode: 2, |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | if ( |
| 199 | edit_mode !== 'replace' && |
| 200 | edit_mode !== 'insert' && |
| 201 | edit_mode !== 'delete' |
| 202 | ) { |
| 203 | return { |
| 204 | result: false, |
| 205 | message: 'Edit mode must be replace, insert, or delete.', |
| 206 | errorCode: 4, |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | if (edit_mode === 'insert' && !cell_type) { |
| 211 | return { |
| 212 | result: false, |
| 213 | message: 'Cell type is required when using edit_mode=insert.', |
| 214 | errorCode: 5, |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | // Require Read-before-Edit (matches FileEditTool/FileWriteTool). Without |
| 219 | // this, the model could edit a notebook it never saw, or edit against a |
| 220 | // stale view after an external change — silent data loss. |
| 221 | const readTimestamp = toolUseContext.readFileState.get(fullPath) |
| 222 | if (!readTimestamp) { |
| 223 | return { |
| 224 | result: false, |
| 225 | message: |
| 226 | 'File has not been read yet. Read it first before writing to it.', |
| 227 | errorCode: 9, |
| 228 | } |
| 229 | } |
| 230 | if (getFileModificationTime(fullPath) > readTimestamp.timestamp) { |
| 231 | return { |
| 232 | result: false, |
| 233 | message: |
nothing calls this directly
no test coverage detected