@jupyterlite/ai 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +5 -214
  2. package/lib/agent.d.ts +58 -66
  3. package/lib/agent.js +274 -300
  4. package/lib/approval-buttons.d.ts +19 -82
  5. package/lib/approval-buttons.js +36 -289
  6. package/lib/chat-model-registry.d.ts +6 -0
  7. package/lib/chat-model-registry.js +4 -1
  8. package/lib/chat-model.d.ts +19 -54
  9. package/lib/chat-model.js +243 -303
  10. package/lib/components/clear-button.d.ts +6 -1
  11. package/lib/components/clear-button.js +8 -3
  12. package/lib/components/completion-status.d.ts +5 -0
  13. package/lib/components/completion-status.js +5 -4
  14. package/lib/components/model-select.d.ts +6 -1
  15. package/lib/components/model-select.js +9 -8
  16. package/lib/components/stop-button.d.ts +6 -1
  17. package/lib/components/stop-button.js +8 -3
  18. package/lib/components/token-usage-display.d.ts +5 -0
  19. package/lib/components/token-usage-display.js +2 -2
  20. package/lib/components/tool-select.d.ts +6 -1
  21. package/lib/components/tool-select.js +6 -5
  22. package/lib/index.js +58 -38
  23. package/lib/models/settings-model.d.ts +1 -1
  24. package/lib/providers/built-in-providers.js +38 -19
  25. package/lib/providers/models.d.ts +3 -3
  26. package/lib/providers/provider-registry.d.ts +3 -4
  27. package/lib/providers/provider-registry.js +1 -4
  28. package/lib/tokens.d.ts +5 -6
  29. package/lib/tools/commands.d.ts +2 -1
  30. package/lib/tools/commands.js +37 -46
  31. package/lib/tools/file.js +49 -73
  32. package/lib/tools/notebook.js +370 -445
  33. package/lib/widgets/ai-settings.d.ts +6 -0
  34. package/lib/widgets/ai-settings.js +72 -71
  35. package/lib/widgets/main-area-chat.d.ts +2 -0
  36. package/lib/widgets/main-area-chat.js +5 -2
  37. package/lib/widgets/provider-config-dialog.d.ts +2 -0
  38. package/lib/widgets/provider-config-dialog.js +34 -34
  39. package/package.json +12 -12
  40. package/src/agent.ts +342 -361
  41. package/src/approval-buttons.ts +43 -389
  42. package/src/chat-model-registry.ts +9 -1
  43. package/src/chat-model.ts +355 -370
  44. package/src/completion/completion-provider.ts +2 -3
  45. package/src/components/clear-button.tsx +16 -3
  46. package/src/components/completion-status.tsx +18 -4
  47. package/src/components/model-select.tsx +21 -8
  48. package/src/components/stop-button.tsx +16 -3
  49. package/src/components/token-usage-display.tsx +14 -2
  50. package/src/components/tool-select.tsx +23 -5
  51. package/src/index.ts +75 -36
  52. package/src/models/settings-model.ts +1 -1
  53. package/src/providers/built-in-providers.ts +38 -19
  54. package/src/providers/models.ts +3 -3
  55. package/src/providers/provider-registry.ts +4 -8
  56. package/src/tokens.ts +5 -6
  57. package/src/tools/commands.ts +39 -50
  58. package/src/tools/file.ts +49 -75
  59. package/src/tools/notebook.ts +451 -510
  60. package/src/widgets/ai-settings.tsx +153 -84
  61. package/src/widgets/main-area-chat.ts +8 -2
  62. package/src/widgets/provider-config-dialog.tsx +54 -41
  63. package/style/base.css +13 -73
  64. package/lib/mcp/browser.d.ts +0 -68
  65. package/lib/mcp/browser.js +0 -138
  66. package/src/mcp/browser.ts +0 -220
@@ -1,7 +1,7 @@
1
1
  import { CodeCell, MarkdownCell } from '@jupyterlab/cells';
2
2
  import { DocumentWidget } from '@jupyterlab/docregistry';
3
3
  import { NotebookPanel } from '@jupyterlab/notebook';
4
- import { tool } from '@openai/agents';
4
+ import { tool } from 'ai';
5
5
  import { z } from 'zod';
6
6
  /**
7
7
  * Find a kernel name that matches the specified language
@@ -64,9 +64,9 @@ async function getNotebookWidget(notebookPath, docManager, notebookTracker) {
64
64
  */
65
65
  export function createNotebookCreationTool(docManager, kernelSpecManager) {
66
66
  return tool({
67
- name: 'create_notebook',
67
+ title: 'Create Notebook',
68
68
  description: 'Create a new Jupyter notebook with a kernel for the specified programming language',
69
- parameters: z.object({
69
+ inputSchema: z.object({
70
70
  language: z
71
71
  .string()
72
72
  .optional()
@@ -82,42 +82,40 @@ export function createNotebookCreationTool(docManager, kernelSpecManager) {
82
82
  const kernel = await findKernelByLanguage(kernelSpecManager, input.language);
83
83
  const { name } = input;
84
84
  if (!name) {
85
- throw new Error('A name must be provided to create a notebook');
86
- }
87
- try {
88
- // TODO: handle cwd / path?
89
- const fileName = name.endsWith('.ipynb') ? name : `${name}.ipynb`;
90
- // Create untitled notebook first
91
- const notebookModel = await docManager.newUntitled({
92
- type: 'notebook'
93
- });
94
- // Rename to desired filename
95
- await docManager.services.contents.rename(notebookModel.path, fileName);
96
- // Create widget with specific kernel
97
- const notebook = docManager.createNew(fileName, 'default', {
98
- name: kernel
99
- });
100
- if (!(notebook instanceof DocumentWidget)) {
101
- throw new Error('Failed to create notebook widget');
102
- }
103
- await notebook.context.ready;
104
- await notebook.context.save();
105
- docManager.openOrReveal(fileName);
106
85
  return {
107
- success: true,
108
- message: `Successfully created notebook ${fileName} with ${kernel} kernel${input.language ? ` for ${input.language}` : ''}`,
109
- notebookPath: fileName,
110
- notebookName: fileName,
111
- kernel,
112
- language: input.language
86
+ success: false,
87
+ error: 'A name must be provided to create a notebook'
113
88
  };
114
89
  }
115
- catch (error) {
90
+ // TODO: handle cwd / path?
91
+ const fileName = name.endsWith('.ipynb') ? name : `${name}.ipynb`;
92
+ // Create untitled notebook first
93
+ const notebookModel = await docManager.newUntitled({
94
+ type: 'notebook'
95
+ });
96
+ // Rename to desired filename
97
+ await docManager.services.contents.rename(notebookModel.path, fileName);
98
+ // Create widget with specific kernel
99
+ const notebook = docManager.createNew(fileName, 'default', {
100
+ name: kernel
101
+ });
102
+ if (!(notebook instanceof DocumentWidget)) {
116
103
  return {
117
104
  success: false,
118
- error: `Failed to create notebook: ${error.message}`
105
+ error: 'Failed to create notebook widget'
119
106
  };
120
107
  }
108
+ await notebook.context.ready;
109
+ await notebook.context.save();
110
+ docManager.openOrReveal(fileName);
111
+ return {
112
+ success: true,
113
+ message: `Successfully created notebook ${fileName} with ${kernel} kernel${input.language ? ` for ${input.language}` : ''}`,
114
+ notebookPath: fileName,
115
+ notebookName: fileName,
116
+ kernel,
117
+ language: input.language
118
+ };
121
119
  }
122
120
  });
123
121
  }
@@ -126,9 +124,9 @@ export function createNotebookCreationTool(docManager, kernelSpecManager) {
126
124
  */
127
125
  export function createAddCellTool(docManager, notebookTracker) {
128
126
  return tool({
129
- name: 'add_cell',
127
+ title: 'Add Cell',
130
128
  description: 'Add a cell to the current notebook with optional content',
131
- parameters: z.object({
129
+ inputSchema: z.object({
132
130
  notebookPath: z
133
131
  .string()
134
132
  .optional()
@@ -149,67 +147,59 @@ export function createAddCellTool(docManager, notebookTracker) {
149
147
  .default('below')
150
148
  .describe('Position relative to current cell')
151
149
  }),
152
- async execute({ notebookPath, content, cellType = 'code', position = 'below' }) {
153
- try {
154
- const currentWidget = await getNotebookWidget(notebookPath, docManager, notebookTracker);
155
- if (!currentWidget) {
156
- return {
157
- success: false,
158
- error: notebookPath
159
- ? `Failed to open notebook at path: ${notebookPath}`
160
- : 'No active notebook and no notebook path provided'
161
- };
162
- }
163
- const notebook = currentWidget.content;
164
- const model = notebook.model;
165
- if (!model) {
166
- return {
167
- success: false,
168
- error: 'No notebook model available'
169
- };
170
- }
171
- // Check if we should replace the first empty cell instead of adding
172
- const shouldReplaceFirstCell = model.cells.length === 1 &&
173
- model.cells.get(0).sharedModel.getSource().trim() === '';
174
- if (shouldReplaceFirstCell) {
175
- // Replace the first empty cell by removing it and adding new one
176
- model.sharedModel.deleteCell(0);
177
- }
178
- // Create the new cell using shared model
179
- const newCellData = {
180
- cell_type: cellType,
181
- source: content || '',
182
- metadata: cellType === 'code' ? { trusted: true } : {}
183
- };
184
- model.sharedModel.addCell(newCellData);
185
- // Execute markdown cells after creation to render them
186
- if (cellType === 'markdown' && content) {
187
- const cellIndex = model.cells.length - 1;
188
- const cellWidget = notebook.widgets[cellIndex];
189
- if (cellWidget && cellWidget instanceof MarkdownCell) {
190
- try {
191
- await cellWidget.ready;
192
- cellWidget.rendered = true;
193
- }
194
- catch (error) {
195
- console.warn('Failed to render markdown cell:', error);
196
- }
197
- }
198
- }
150
+ execute: async ({ notebookPath, content, cellType = 'code', position = 'below' }) => {
151
+ const currentWidget = await getNotebookWidget(notebookPath, docManager, notebookTracker);
152
+ if (!currentWidget) {
199
153
  return {
200
- success: true,
201
- message: `${cellType} cell added successfully`,
202
- content: content || '',
203
- cellType,
204
- position
154
+ success: false,
155
+ error: notebookPath
156
+ ? `Failed to open notebook at path: ${notebookPath}`
157
+ : 'No active notebook and no notebook path provided'
205
158
  };
206
159
  }
207
- catch (error) {
160
+ const notebook = currentWidget.content;
161
+ const model = notebook.model;
162
+ if (!model) {
208
163
  return {
209
164
  success: false,
210
- error: `Failed to add ${cellType} cell: ${error.message}`
165
+ error: 'No notebook model available'
211
166
  };
212
167
  }
168
+ // Check if we should replace the first empty cell instead of adding
169
+ const shouldReplaceFirstCell = model.cells.length === 1 &&
170
+ model.cells.get(0).sharedModel.getSource().trim() === '';
171
+ if (shouldReplaceFirstCell) {
172
+ // Replace the first empty cell by removing it and adding new one
173
+ model.sharedModel.deleteCell(0);
174
+ }
175
+ // Create the new cell using shared model
176
+ const newCellData = {
177
+ cell_type: cellType,
178
+ source: content || '',
179
+ metadata: cellType === 'code' ? { trusted: true } : {}
180
+ };
181
+ model.sharedModel.addCell(newCellData);
182
+ // Execute markdown cells after creation to render them
183
+ if (cellType === 'markdown' && content) {
184
+ const cellIndex = model.cells.length - 1;
185
+ const cellWidget = notebook.widgets[cellIndex];
186
+ if (cellWidget && cellWidget instanceof MarkdownCell) {
187
+ try {
188
+ await cellWidget.ready;
189
+ cellWidget.rendered = true;
190
+ }
191
+ catch (error) {
192
+ console.warn('Failed to render markdown cell:', error);
193
+ }
194
+ }
195
+ }
196
+ return {
197
+ success: true,
198
+ message: `${cellType} cell added successfully`,
199
+ content: content || '',
200
+ cellType,
201
+ position
202
+ };
213
203
  }
214
204
  });
215
205
  }
@@ -218,9 +208,9 @@ export function createAddCellTool(docManager, notebookTracker) {
218
208
  */
219
209
  export function createGetNotebookInfoTool(docManager, notebookTracker) {
220
210
  return tool({
221
- name: 'get_notebook_info',
211
+ title: 'Get Notebook Info',
222
212
  description: 'Get information about a notebook including number of cells and active cell index',
223
- parameters: z.object({
213
+ inputSchema: z.object({
224
214
  notebookPath: z
225
215
  .string()
226
216
  .optional()
@@ -229,44 +219,36 @@ export function createGetNotebookInfoTool(docManager, notebookTracker) {
229
219
  }),
230
220
  execute: async (input) => {
231
221
  const { notebookPath } = input;
232
- try {
233
- const currentWidget = await getNotebookWidget(notebookPath, docManager, notebookTracker);
234
- if (!currentWidget) {
235
- return JSON.stringify({
236
- success: false,
237
- error: notebookPath
238
- ? `Failed to open notebook at path: ${notebookPath}`
239
- : 'No active notebook and no notebook path provided'
240
- });
241
- }
242
- const notebook = currentWidget.content;
243
- const model = notebook.model;
244
- if (!model) {
245
- return JSON.stringify({
246
- success: false,
247
- error: 'No notebook model available'
248
- });
249
- }
250
- const cellCount = model.cells.length;
251
- const activeCellIndex = notebook.activeCellIndex;
252
- const activeCell = notebook.activeCell;
253
- const activeCellType = activeCell?.model.type || 'unknown';
222
+ const currentWidget = await getNotebookWidget(notebookPath, docManager, notebookTracker);
223
+ if (!currentWidget) {
254
224
  return JSON.stringify({
255
- success: true,
256
- notebookName: currentWidget.title.label,
257
- notebookPath: currentWidget.context.path,
258
- cellCount,
259
- activeCellIndex,
260
- activeCellType,
261
- isDirty: model.dirty
225
+ success: false,
226
+ error: notebookPath
227
+ ? `Failed to open notebook at path: ${notebookPath}`
228
+ : 'No active notebook and no notebook path provided'
262
229
  });
263
230
  }
264
- catch (error) {
231
+ const notebook = currentWidget.content;
232
+ const model = notebook.model;
233
+ if (!model) {
265
234
  return JSON.stringify({
266
235
  success: false,
267
- error: `Failed to get notebook info: ${error.message}`
236
+ error: 'No notebook model available'
268
237
  });
269
238
  }
239
+ const cellCount = model.cells.length;
240
+ const activeCellIndex = notebook.activeCellIndex;
241
+ const activeCell = notebook.activeCell;
242
+ const activeCellType = activeCell?.model.type || 'unknown';
243
+ return JSON.stringify({
244
+ success: true,
245
+ notebookName: currentWidget.title.label,
246
+ notebookPath: currentWidget.context.path,
247
+ cellCount,
248
+ activeCellIndex,
249
+ activeCellType,
250
+ isDirty: model.dirty
251
+ });
270
252
  }
271
253
  });
272
254
  }
@@ -275,9 +257,9 @@ export function createGetNotebookInfoTool(docManager, notebookTracker) {
275
257
  */
276
258
  export function createGetCellInfoTool(docManager, notebookTracker) {
277
259
  return tool({
278
- name: 'get_cell_info',
260
+ title: 'Get Cell Info',
279
261
  description: 'Get information about a specific cell including its type, source content, and outputs',
280
- parameters: z.object({
262
+ inputSchema: z.object({
281
263
  notebookPath: z
282
264
  .string()
283
265
  .optional()
@@ -292,59 +274,51 @@ export function createGetCellInfoTool(docManager, notebookTracker) {
292
274
  execute: async (input) => {
293
275
  const { notebookPath } = input;
294
276
  let { cellIndex } = input;
295
- try {
296
- const currentWidget = await getNotebookWidget(notebookPath, docManager, notebookTracker);
297
- if (!currentWidget) {
298
- return JSON.stringify({
299
- success: false,
300
- error: notebookPath
301
- ? `Failed to open notebook at path: ${notebookPath}`
302
- : 'No active notebook and no notebook path provided'
303
- });
304
- }
305
- const notebook = currentWidget.content;
306
- const model = notebook.model;
307
- if (!model) {
308
- return JSON.stringify({
309
- success: false,
310
- error: 'No notebook model available'
311
- });
312
- }
313
- if (cellIndex === undefined || cellIndex === null) {
314
- cellIndex = notebook.activeCellIndex;
315
- }
316
- if (cellIndex < 0 || cellIndex >= model.cells.length) {
317
- return JSON.stringify({
318
- success: false,
319
- error: `Invalid cell index: ${cellIndex}. Notebook has ${model.cells.length} cells.`
320
- });
321
- }
322
- const cell = model.cells.get(cellIndex);
323
- const cellType = cell.type;
324
- const sharedModel = cell.sharedModel;
325
- const source = sharedModel.getSource();
326
- // Get outputs for code cells
327
- let outputs = [];
328
- if (cellType === 'code') {
329
- const rawOutputs = sharedModel.toJSON().outputs;
330
- outputs = Array.isArray(rawOutputs) ? rawOutputs : [];
331
- }
277
+ const currentWidget = await getNotebookWidget(notebookPath, docManager, notebookTracker);
278
+ if (!currentWidget) {
332
279
  return JSON.stringify({
333
- success: true,
334
- cellId: cell.id,
335
- cellIndex,
336
- cellType,
337
- source,
338
- outputs,
339
- executionCount: cellType === 'code' ? cell.executionCount : null
280
+ success: false,
281
+ error: notebookPath
282
+ ? `Failed to open notebook at path: ${notebookPath}`
283
+ : 'No active notebook and no notebook path provided'
340
284
  });
341
285
  }
342
- catch (error) {
286
+ const notebook = currentWidget.content;
287
+ const model = notebook.model;
288
+ if (!model) {
343
289
  return JSON.stringify({
344
290
  success: false,
345
- error: `Failed to get cell info: ${error.message}`
291
+ error: 'No notebook model available'
346
292
  });
347
293
  }
294
+ if (cellIndex === undefined || cellIndex === null) {
295
+ cellIndex = notebook.activeCellIndex;
296
+ }
297
+ if (cellIndex < 0 || cellIndex >= model.cells.length) {
298
+ return JSON.stringify({
299
+ success: false,
300
+ error: `Invalid cell index: ${cellIndex}. Notebook has ${model.cells.length} cells.`
301
+ });
302
+ }
303
+ const cell = model.cells.get(cellIndex);
304
+ const cellType = cell.type;
305
+ const sharedModel = cell.sharedModel;
306
+ const source = sharedModel.getSource();
307
+ // Get outputs for code cells
308
+ let outputs = [];
309
+ if (cellType === 'code') {
310
+ const rawOutputs = sharedModel.toJSON().outputs;
311
+ outputs = Array.isArray(rawOutputs) ? rawOutputs : [];
312
+ }
313
+ return JSON.stringify({
314
+ success: true,
315
+ cellId: cell.id,
316
+ cellIndex,
317
+ cellType,
318
+ source,
319
+ outputs,
320
+ executionCount: cellType === 'code' ? cell.executionCount : null
321
+ });
348
322
  }
349
323
  });
350
324
  }
@@ -353,9 +327,9 @@ export function createGetCellInfoTool(docManager, notebookTracker) {
353
327
  */
354
328
  export function createSetCellContentTool(docManager, notebookTracker, diffManager) {
355
329
  return tool({
356
- name: 'set_cell_content',
330
+ title: 'Set Cell Content',
357
331
  description: 'Set the content of a specific cell and return both the previous and new content',
358
- parameters: z.object({
332
+ inputSchema: z.object({
359
333
  notebookPath: z
360
334
  .string()
361
335
  .optional()
@@ -375,108 +349,100 @@ export function createSetCellContentTool(docManager, notebookTracker, diffManage
375
349
  }),
376
350
  execute: async (input) => {
377
351
  const { notebookPath, cellId, cellIndex, content } = input;
378
- try {
379
- const notebookWidget = await getNotebookWidget(notebookPath, docManager, notebookTracker);
380
- if (!notebookWidget) {
352
+ const notebookWidget = await getNotebookWidget(notebookPath, docManager, notebookTracker);
353
+ if (!notebookWidget) {
354
+ return JSON.stringify({
355
+ success: false,
356
+ error: notebookPath
357
+ ? `Failed to open notebook at path: ${notebookPath}`
358
+ : 'No active notebook and no notebook path provided'
359
+ });
360
+ }
361
+ const notebook = notebookWidget.content;
362
+ const targetNotebookPath = notebookWidget.context.path;
363
+ const model = notebook.model;
364
+ if (!model) {
365
+ return JSON.stringify({
366
+ success: false,
367
+ error: 'No notebook model available'
368
+ });
369
+ }
370
+ // Determine target cell index
371
+ let targetCellIndex;
372
+ if (cellId !== undefined && cellId !== null) {
373
+ // Find cell by ID
374
+ targetCellIndex = -1;
375
+ for (let i = 0; i < model.cells.length; i++) {
376
+ if (model.cells.get(i).id === cellId) {
377
+ targetCellIndex = i;
378
+ break;
379
+ }
380
+ }
381
+ if (targetCellIndex === -1) {
381
382
  return JSON.stringify({
382
383
  success: false,
383
- error: notebookPath
384
- ? `Failed to open notebook at path: ${notebookPath}`
385
- : 'No active notebook and no notebook path provided'
384
+ error: `Cell with ID '${cellId}' not found in notebook`
386
385
  });
387
386
  }
388
- const notebook = notebookWidget.content;
389
- const targetNotebookPath = notebookWidget.context.path;
390
- const model = notebook.model;
391
- if (!model) {
387
+ }
388
+ else if (cellIndex !== undefined && cellIndex !== null) {
389
+ // Use provided cell index
390
+ if (cellIndex < 0 || cellIndex >= model.cells.length) {
392
391
  return JSON.stringify({
393
392
  success: false,
394
- error: 'No notebook model available'
393
+ error: `Invalid cell index: ${cellIndex}. Notebook has ${model.cells.length} cells.`
395
394
  });
396
395
  }
397
- // Determine target cell index
398
- let targetCellIndex;
399
- if (cellId !== undefined && cellId !== null) {
400
- // Find cell by ID
401
- targetCellIndex = -1;
402
- for (let i = 0; i < model.cells.length; i++) {
403
- if (model.cells.get(i).id === cellId) {
404
- targetCellIndex = i;
405
- break;
406
- }
407
- }
408
- if (targetCellIndex === -1) {
409
- return JSON.stringify({
410
- success: false,
411
- error: `Cell with ID '${cellId}' not found in notebook`
412
- });
413
- }
414
- }
415
- else if (cellIndex !== undefined && cellIndex !== null) {
416
- // Use provided cell index
417
- if (cellIndex < 0 || cellIndex >= model.cells.length) {
418
- return JSON.stringify({
419
- success: false,
420
- error: `Invalid cell index: ${cellIndex}. Notebook has ${model.cells.length} cells.`
421
- });
422
- }
423
- targetCellIndex = cellIndex;
424
- }
425
- else {
426
- // Use active cell
427
- targetCellIndex = notebook.activeCellIndex;
428
- if (targetCellIndex === -1 || targetCellIndex >= model.cells.length) {
429
- return JSON.stringify({
430
- success: false,
431
- error: 'No active cell or invalid active cell index'
432
- });
433
- }
434
- }
435
- // Get the target cell
436
- const targetCell = model.cells.get(targetCellIndex);
437
- if (!targetCell) {
396
+ targetCellIndex = cellIndex;
397
+ }
398
+ else {
399
+ // Use active cell
400
+ targetCellIndex = notebook.activeCellIndex;
401
+ if (targetCellIndex === -1 || targetCellIndex >= model.cells.length) {
438
402
  return JSON.stringify({
439
403
  success: false,
440
- error: `Cell at index ${targetCellIndex} not found`
441
- });
442
- }
443
- const sharedModel = targetCell.sharedModel;
444
- // Get previous content and type
445
- const previousContent = sharedModel.getSource();
446
- const previousCellType = targetCell.type;
447
- const retrievedCellId = targetCell.id;
448
- sharedModel.setSource(content);
449
- // Show the cell diff using the diff manager if available
450
- if (diffManager) {
451
- await diffManager.showCellDiff({
452
- original: previousContent,
453
- modified: content,
454
- cellId: retrievedCellId,
455
- notebookPath: targetNotebookPath
404
+ error: 'No active cell or invalid active cell index'
456
405
  });
457
406
  }
458
- return JSON.stringify({
459
- success: true,
460
- message: cellId !== undefined && cellId !== null
461
- ? `Cell with ID '${cellId}' content replaced successfully`
462
- : cellIndex !== undefined && cellIndex !== null
463
- ? `Cell ${targetCellIndex} content replaced successfully`
464
- : 'Active cell content replaced successfully',
465
- notebookPath: targetNotebookPath,
466
- cellId: retrievedCellId,
467
- cellIndex: targetCellIndex,
468
- previousContent,
469
- previousCellType,
470
- newContent: content,
471
- wasActiveCell: cellId === undefined && cellIndex === undefined
472
- });
473
407
  }
474
- catch (error) {
408
+ // Get the target cell
409
+ const targetCell = model.cells.get(targetCellIndex);
410
+ if (!targetCell) {
475
411
  return JSON.stringify({
476
412
  success: false,
477
- error: `Failed to replace cell content: ${error.message}`
413
+ error: `Cell at index ${targetCellIndex} not found`
414
+ });
415
+ }
416
+ const sharedModel = targetCell.sharedModel;
417
+ // Get previous content and type
418
+ const previousContent = sharedModel.getSource();
419
+ const previousCellType = targetCell.type;
420
+ const retrievedCellId = targetCell.id;
421
+ sharedModel.setSource(content);
422
+ // Show the cell diff using the diff manager if available
423
+ if (diffManager) {
424
+ await diffManager.showCellDiff({
425
+ original: previousContent,
426
+ modified: content,
427
+ cellId: retrievedCellId,
428
+ notebookPath: targetNotebookPath
478
429
  });
479
430
  }
431
+ return JSON.stringify({
432
+ success: true,
433
+ message: cellId !== undefined && cellId !== null
434
+ ? `Cell with ID '${cellId}' content replaced successfully`
435
+ : cellIndex !== undefined && cellIndex !== null
436
+ ? `Cell ${targetCellIndex} content replaced successfully`
437
+ : 'Active cell content replaced successfully',
438
+ notebookPath: targetNotebookPath,
439
+ cellId: retrievedCellId,
440
+ cellIndex: targetCellIndex,
441
+ previousContent,
442
+ previousCellType,
443
+ newContent: content,
444
+ wasActiveCell: cellId === undefined && cellIndex === undefined
445
+ });
480
446
  }
481
447
  });
482
448
  }
@@ -485,9 +451,9 @@ export function createSetCellContentTool(docManager, notebookTracker, diffManage
485
451
  */
486
452
  export function createRunCellTool(docManager, notebookTracker) {
487
453
  return tool({
488
- name: 'run_cell',
454
+ title: 'Run Cell',
489
455
  description: 'Run a specific cell in the notebook by index',
490
- parameters: z.object({
456
+ inputSchema: z.object({
491
457
  notebookPath: z
492
458
  .string()
493
459
  .optional()
@@ -502,78 +468,61 @@ export function createRunCellTool(docManager, notebookTracker) {
502
468
  needsApproval: true,
503
469
  execute: async (input) => {
504
470
  const { notebookPath, cellIndex, recordTiming = true } = input;
505
- try {
506
- const currentWidget = await getNotebookWidget(notebookPath, docManager, notebookTracker);
507
- if (!currentWidget) {
508
- return JSON.stringify({
509
- success: false,
510
- error: notebookPath
511
- ? `Failed to open notebook at path: ${notebookPath}`
512
- : 'No active notebook and no notebook path provided'
513
- });
514
- }
515
- const notebook = currentWidget.content;
516
- const model = notebook.model;
517
- if (!model) {
518
- return JSON.stringify({
519
- success: false,
520
- error: 'No notebook model available'
521
- });
522
- }
523
- if (cellIndex < 0 || cellIndex >= model.cells.length) {
524
- return JSON.stringify({
525
- success: false,
526
- error: `Invalid cell index: ${cellIndex}. Notebook has ${model.cells.length} cells.`
527
- });
528
- }
529
- // Get the target cell widget
530
- const cellWidget = notebook.widgets[cellIndex];
531
- if (!cellWidget) {
532
- return JSON.stringify({
533
- success: false,
534
- error: `Cell widget at index ${cellIndex} not found`
535
- });
536
- }
537
- // Execute using shared model approach (non-disruptive)
538
- try {
539
- if (cellWidget instanceof CodeCell) {
540
- // Use direct CodeCell.execute() method
541
- const sessionCtx = currentWidget.sessionContext;
542
- await CodeCell.execute(cellWidget, sessionCtx, {
543
- recordTiming,
544
- deletedCells: model.deletedCells
545
- });
546
- const codeModel = cellWidget.model;
547
- return JSON.stringify({
548
- success: true,
549
- message: `Cell ${cellIndex} executed successfully`,
550
- cellIndex,
551
- executionCount: codeModel.executionCount,
552
- hasOutput: codeModel.outputs.length > 0
553
- });
554
- }
555
- else {
556
- // For non-code cells, just return success
557
- return JSON.stringify({
558
- success: true,
559
- message: `Cell ${cellIndex} is not a code cell, no execution needed`,
560
- cellIndex,
561
- cellType: cellWidget.model.type
562
- });
563
- }
564
- }
565
- catch (error) {
566
- return JSON.stringify({
567
- success: false,
568
- error: `Failed to execute cell: ${error.message}`,
569
- cellIndex
570
- });
571
- }
471
+ const currentWidget = await getNotebookWidget(notebookPath, docManager, notebookTracker);
472
+ if (!currentWidget) {
473
+ return JSON.stringify({
474
+ success: false,
475
+ error: notebookPath
476
+ ? `Failed to open notebook at path: ${notebookPath}`
477
+ : 'No active notebook and no notebook path provided'
478
+ });
479
+ }
480
+ const notebook = currentWidget.content;
481
+ const model = notebook.model;
482
+ if (!model) {
483
+ return JSON.stringify({
484
+ success: false,
485
+ error: 'No notebook model available'
486
+ });
572
487
  }
573
- catch (error) {
488
+ if (cellIndex < 0 || cellIndex >= model.cells.length) {
574
489
  return JSON.stringify({
575
490
  success: false,
576
- error: `Failed to run cell: ${error.message}`
491
+ error: `Invalid cell index: ${cellIndex}. Notebook has ${model.cells.length} cells.`
492
+ });
493
+ }
494
+ // Get the target cell widget
495
+ const cellWidget = notebook.widgets[cellIndex];
496
+ if (!cellWidget) {
497
+ return JSON.stringify({
498
+ success: false,
499
+ error: `Cell widget at index ${cellIndex} not found`
500
+ });
501
+ }
502
+ // Execute using shared model approach (non-disruptive)
503
+ if (cellWidget instanceof CodeCell) {
504
+ // Use direct CodeCell.execute() method
505
+ const sessionCtx = currentWidget.sessionContext;
506
+ await CodeCell.execute(cellWidget, sessionCtx, {
507
+ recordTiming,
508
+ deletedCells: model.deletedCells
509
+ });
510
+ const codeModel = cellWidget.model;
511
+ return JSON.stringify({
512
+ success: true,
513
+ message: `Cell ${cellIndex} executed successfully`,
514
+ cellIndex,
515
+ executionCount: codeModel.executionCount,
516
+ hasOutput: codeModel.outputs.length > 0
517
+ });
518
+ }
519
+ else {
520
+ // For non-code cells, just return success
521
+ return JSON.stringify({
522
+ success: true,
523
+ message: `Cell ${cellIndex} is not a code cell, no execution needed`,
524
+ cellIndex,
525
+ cellType: cellWidget.model.type
577
526
  });
578
527
  }
579
528
  }
@@ -584,9 +533,9 @@ export function createRunCellTool(docManager, notebookTracker) {
584
533
  */
585
534
  export function createDeleteCellTool(docManager, notebookTracker) {
586
535
  return tool({
587
- name: 'delete_cell',
536
+ title: 'Delete Cell',
588
537
  description: 'Delete a specific cell from the notebook by index',
589
- parameters: z.object({
538
+ inputSchema: z.object({
590
539
  notebookPath: z
591
540
  .string()
592
541
  .optional()
@@ -596,53 +545,45 @@ export function createDeleteCellTool(docManager, notebookTracker) {
596
545
  }),
597
546
  execute: async (input) => {
598
547
  const { notebookPath, cellIndex } = input;
599
- try {
600
- const currentWidget = await getNotebookWidget(notebookPath, docManager, notebookTracker);
601
- if (!currentWidget) {
602
- return JSON.stringify({
603
- success: false,
604
- error: notebookPath
605
- ? `Failed to open notebook at path: ${notebookPath}`
606
- : 'No active notebook and no notebook path provided'
607
- });
608
- }
609
- const notebook = currentWidget.content;
610
- const model = notebook.model;
611
- if (!model) {
612
- return JSON.stringify({
613
- success: false,
614
- error: 'No notebook model available'
615
- });
616
- }
617
- if (cellIndex < 0 || cellIndex >= model.cells.length) {
618
- return JSON.stringify({
619
- success: false,
620
- error: `Invalid cell index: ${cellIndex}. Notebook has ${model.cells.length} cells.`
621
- });
622
- }
623
- // Validate cell exists
624
- const targetCell = model.cells.get(cellIndex);
625
- if (!targetCell) {
626
- return JSON.stringify({
627
- success: false,
628
- error: `Cell at index ${cellIndex} not found`
629
- });
630
- }
631
- // Delete cell using shared model (non-disruptive)
632
- model.sharedModel.deleteCell(cellIndex);
548
+ const currentWidget = await getNotebookWidget(notebookPath, docManager, notebookTracker);
549
+ if (!currentWidget) {
633
550
  return JSON.stringify({
634
- success: true,
635
- message: `Cell ${cellIndex} deleted successfully`,
636
- cellIndex,
637
- remainingCells: model.cells.length
551
+ success: false,
552
+ error: notebookPath
553
+ ? `Failed to open notebook at path: ${notebookPath}`
554
+ : 'No active notebook and no notebook path provided'
555
+ });
556
+ }
557
+ const notebook = currentWidget.content;
558
+ const model = notebook.model;
559
+ if (!model) {
560
+ return JSON.stringify({
561
+ success: false,
562
+ error: 'No notebook model available'
638
563
  });
639
564
  }
640
- catch (error) {
565
+ if (cellIndex < 0 || cellIndex >= model.cells.length) {
641
566
  return JSON.stringify({
642
567
  success: false,
643
- error: `Failed to delete cell: ${error.message}`
568
+ error: `Invalid cell index: ${cellIndex}. Notebook has ${model.cells.length} cells.`
644
569
  });
645
570
  }
571
+ // Validate cell exists
572
+ const targetCell = model.cells.get(cellIndex);
573
+ if (!targetCell) {
574
+ return JSON.stringify({
575
+ success: false,
576
+ error: `Cell at index ${cellIndex} not found`
577
+ });
578
+ }
579
+ // Delete cell using shared model (non-disruptive)
580
+ model.sharedModel.deleteCell(cellIndex);
581
+ return JSON.stringify({
582
+ success: true,
583
+ message: `Cell ${cellIndex} deleted successfully`,
584
+ cellIndex,
585
+ remainingCells: model.cells.length
586
+ });
646
587
  }
647
588
  });
648
589
  }
@@ -651,9 +592,9 @@ export function createDeleteCellTool(docManager, notebookTracker) {
651
592
  */
652
593
  export function createExecuteActiveCellTool(docManager, notebookTracker) {
653
594
  return tool({
654
- name: 'execute_active_cell',
595
+ title: 'Execute Active Cell',
655
596
  description: 'Execute the currently active cell in the notebook without disrupting user focus',
656
- parameters: z.object({
597
+ inputSchema: z.object({
657
598
  notebookPath: z
658
599
  .string()
659
600
  .optional()
@@ -671,66 +612,58 @@ export function createExecuteActiveCellTool(docManager, notebookTracker) {
671
612
  }),
672
613
  execute: async (input) => {
673
614
  const { notebookPath, code, recordTiming = true } = input;
674
- try {
675
- const currentWidget = await getNotebookWidget(notebookPath, docManager, notebookTracker);
676
- if (!currentWidget) {
677
- return JSON.stringify({
678
- success: false,
679
- error: notebookPath
680
- ? `Failed to open notebook at path: ${notebookPath}`
681
- : 'No active notebook and no notebook path provided'
682
- });
683
- }
684
- const notebook = currentWidget.content;
685
- const model = notebook.model;
686
- const activeCellIndex = notebook.activeCellIndex;
687
- if (!model || activeCellIndex === -1) {
688
- return JSON.stringify({
689
- success: false,
690
- error: 'No notebook model or active cell available'
691
- });
692
- }
693
- const activeCell = model.cells.get(activeCellIndex);
694
- if (!activeCell) {
695
- return JSON.stringify({
696
- success: false,
697
- error: 'Active cell not found'
698
- });
699
- }
700
- // Set code content if provided
701
- if (code) {
702
- activeCell.sharedModel.setSource(code);
703
- }
704
- // Get the cell widget for execution
705
- const cellWidget = notebook.widgets[activeCellIndex];
706
- if (!cellWidget || !(cellWidget instanceof CodeCell)) {
707
- return JSON.stringify({
708
- success: false,
709
- error: 'Active cell is not a code cell'
710
- });
711
- }
712
- // Execute using shared model approach (non-disruptive)
713
- const sessionCtx = currentWidget.sessionContext;
714
- await CodeCell.execute(cellWidget, sessionCtx, {
715
- recordTiming,
716
- deletedCells: model.deletedCells
615
+ const currentWidget = await getNotebookWidget(notebookPath, docManager, notebookTracker);
616
+ if (!currentWidget) {
617
+ return JSON.stringify({
618
+ success: false,
619
+ error: notebookPath
620
+ ? `Failed to open notebook at path: ${notebookPath}`
621
+ : 'No active notebook and no notebook path provided'
717
622
  });
718
- const codeModel = cellWidget.model;
623
+ }
624
+ const notebook = currentWidget.content;
625
+ const model = notebook.model;
626
+ const activeCellIndex = notebook.activeCellIndex;
627
+ if (!model || activeCellIndex === -1) {
719
628
  return JSON.stringify({
720
- success: true,
721
- message: 'Code executed successfully in active cell',
722
- cellIndex: activeCellIndex,
723
- executionCount: codeModel.executionCount,
724
- hasOutput: codeModel.outputs.length > 0,
725
- code: code || activeCell.sharedModel.getSource()
629
+ success: false,
630
+ error: 'No notebook model or active cell available'
726
631
  });
727
632
  }
728
- catch (error) {
633
+ const activeCell = model.cells.get(activeCellIndex);
634
+ if (!activeCell) {
729
635
  return JSON.stringify({
730
636
  success: false,
731
- error: `Failed to execute code: ${error.message}`
637
+ error: 'Active cell not found'
732
638
  });
733
639
  }
640
+ // Set code content if provided
641
+ if (code) {
642
+ activeCell.sharedModel.setSource(code);
643
+ }
644
+ // Get the cell widget for execution
645
+ const cellWidget = notebook.widgets[activeCellIndex];
646
+ if (!cellWidget || !(cellWidget instanceof CodeCell)) {
647
+ return JSON.stringify({
648
+ success: false,
649
+ error: 'Active cell is not a code cell'
650
+ });
651
+ }
652
+ // Execute using shared model approach (non-disruptive)
653
+ const sessionCtx = currentWidget.sessionContext;
654
+ await CodeCell.execute(cellWidget, sessionCtx, {
655
+ recordTiming,
656
+ deletedCells: model.deletedCells
657
+ });
658
+ const codeModel = cellWidget.model;
659
+ return JSON.stringify({
660
+ success: true,
661
+ message: 'Code executed successfully in active cell',
662
+ cellIndex: activeCellIndex,
663
+ executionCount: codeModel.executionCount,
664
+ hasOutput: codeModel.outputs.length > 0,
665
+ code: code || activeCell.sharedModel.getSource()
666
+ });
734
667
  }
735
668
  });
736
669
  }
@@ -739,9 +672,9 @@ export function createExecuteActiveCellTool(docManager, notebookTracker) {
739
672
  */
740
673
  export function createSaveNotebookTool(docManager, notebookTracker) {
741
674
  return tool({
742
- name: 'save_notebook',
675
+ title: 'Save Notebook',
743
676
  description: 'Save a specific notebook to disk',
744
- parameters: z.object({
677
+ inputSchema: z.object({
745
678
  notebookPath: z
746
679
  .string()
747
680
  .optional()
@@ -750,30 +683,22 @@ export function createSaveNotebookTool(docManager, notebookTracker) {
750
683
  }),
751
684
  execute: async (input) => {
752
685
  const { notebookPath } = input;
753
- try {
754
- const currentWidget = await getNotebookWidget(notebookPath, docManager, notebookTracker);
755
- if (!currentWidget) {
756
- return JSON.stringify({
757
- success: false,
758
- error: notebookPath
759
- ? `Failed to open notebook at path: ${notebookPath}`
760
- : 'No active notebook and no notebook path provided'
761
- });
762
- }
763
- await currentWidget.context.save();
764
- return JSON.stringify({
765
- success: true,
766
- message: 'Notebook saved successfully',
767
- notebookName: currentWidget.title.label,
768
- notebookPath: currentWidget.context.path
769
- });
770
- }
771
- catch (error) {
686
+ const currentWidget = await getNotebookWidget(notebookPath, docManager, notebookTracker);
687
+ if (!currentWidget) {
772
688
  return JSON.stringify({
773
689
  success: false,
774
- error: `Failed to save notebook: ${error.message}`
690
+ error: notebookPath
691
+ ? `Failed to open notebook at path: ${notebookPath}`
692
+ : 'No active notebook and no notebook path provided'
775
693
  });
776
694
  }
695
+ await currentWidget.context.save();
696
+ return JSON.stringify({
697
+ success: true,
698
+ message: 'Notebook saved successfully',
699
+ notebookName: currentWidget.title.label,
700
+ notebookPath: currentWidget.context.path
701
+ });
777
702
  }
778
703
  });
779
704
  }