Welcome back! In the previous chapter, Schema Definitions, we designed the "Order Form" (Input Schema) that the AI must fill out to request a notebook edit.
Now, we need to build the Chef who actually receives that order and cooks the meal.
This chapter covers NotebookEditTool Core.
Having a schema is great, but a schema is just a piece of paper. It doesn't do anything. We need a central controller that:
In our project, we use a function called buildTool to create this controller. It ties the input schema, the validation logic, and the execution logic into one neat package.
buildTool
The NotebookEditTool is defined as a single object. Think of it as a robot. We build this robot by giving it a name, a description, and a set of instructions.
Here is the skeleton of our tool in NotebookEditTool.ts:
export const NotebookEditTool = buildTool({
name: 'notebook_edit_tool',
userFacingName: 'Edit Notebook',
// Connect the schemas we made in Chapter 1
get inputSchema() { return inputSchema() },
get outputSchema() { return outputSchema() },
// ... Lifecycle methods (validate, call) go here
})
Explanation: We are telling the system, "This tool is named 'Edit Notebook', and it expects data matching inputSchema."
When the AI wants to use this tool, the request goes through a specific lifecycle. It acts like a security checkpoint at an airport.
Let's break down the two main steps: Validation and Execution.
validateInput
Before we touch any files, we must ensure the request is safe and logical. This happens in validateInput.
Check A: Is it the right file type?
We don't want the AI trying to edit a .jpg image as if it were a notebook.
async validateInput({ notebook_path }, context) {
// Check file extension
if (extname(notebook_path) !== '.ipynb') {
return {
result: false,
message: 'File must be a Jupyter notebook (.ipynb file).',
}
}
// ... more checks
}
Check B: The "Read-First" Rule This is a crucial safety feature. We require the AI to read a file before it tries to edit it. This prevents the AI from guessing what's in a file (hallucinating) and accidentally overwriting data it didn't know about.
const readTimestamp = context.readFileState.get(fullPath)
if (!readTimestamp) {
return {
result: false,
message: 'File has not been read yet. Read it first.',
}
}
Explanation: We check the tool context to see if this file is in our "recently read" list. If not, we reject the edit.
call
If validation passes, the call method runs. This is where the magic happens.
The call method orchestrates the entire editing process. Note that Jupyter Notebooks are technically just complex JSON text files.
Step A: Read and Parse
async call({ notebook_path, new_source }, context) {
// Read the raw text from the hard drive
const { content } = readFileSyncWithMetadata(fullPath)
// Parse the text into a JSON object
let notebook = jsonParse(content)
// ... proceed to edit
}
Step B: Modify the Data
Depending on what the AI asked for (replace, insert, or delete), we modify the JSON object in memory.
// Example: Replacing source code in a specific cell
const targetCell = notebook.cells[cellIndex]
targetCell.source = new_source
targetCell.execution_count = null // Reset this because code changed
Explanation: We find the specific cell in the array and update its source property with the new code provided by the AI.
Step C: Save to Disk Finally, we turn the JSON object back into text and save it.
// Convert back to string with nice indentation
const updatedContent = jsonStringify(notebook, null, 1)
// Write to disk
writeTextContent(fullPath, updatedContent, encoding, lineEndings)
return { data: { /* success message */ } }
Let's look deeper into NotebookEditTool.ts to see how we handle permissions and specific logic.
Before validateInput even runs, there is a hidden step: checkPermissions. We don't want the AI editing system files or files outside the project folder.
async checkPermissions(input, context) {
return checkWritePermissionForTool(
NotebookEditTool,
input,
context.getAppState().toolPermissionContext,
)
}
Explanation: This delegates the security check to a helper function. If the user hasn't granted write access to this folder, the tool stops here.
The core logic has to handle different edit_mode instructions differently.
notebook.cells array.This logic is vital because the AI sees a notebook as a list of cells, but the computer sees a JSON object. The Core translates the AI's intent into array operations.
In this chapter, we built the NotebookEditTool Core.
buildTool to define the tool's identity.validateInput to act as a safety guard (enforcing file types and the "read-first" rule).call to perform the actual file I/O and JSON manipulation.This Core is the bridge between the AI's abstract request and the physical file system.
However, the AI won't know how to use this tool unless we explain it clearly in natural language. That is the job of the Prompt.
Next Chapter: Tool Prompts and Metadata
Generated by Code IQ