@jupyterlite/ai 0.9.0 → 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 +62 -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 +80 -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,10 +1,15 @@
1
- import { CodeCell, ICodeCellModel, MarkdownCell } from '@jupyterlab/cells';
1
+ import {
2
+ CodeCell,
3
+ CodeCellModel,
4
+ ICodeCellModel,
5
+ MarkdownCell
6
+ } from '@jupyterlab/cells';
2
7
  import { IDocumentManager } from '@jupyterlab/docmanager';
3
8
  import { DocumentWidget } from '@jupyterlab/docregistry';
4
9
  import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook';
5
10
  import { KernelSpec } from '@jupyterlab/services';
6
11
 
7
- import { tool } from '@openai/agents';
12
+ import { tool } from 'ai';
8
13
 
9
14
  import { z } from 'zod';
10
15
 
@@ -91,10 +96,10 @@ export function createNotebookCreationTool(
91
96
  kernelSpecManager: KernelSpec.IManager
92
97
  ): ITool {
93
98
  return tool({
94
- name: 'create_notebook',
99
+ title: 'Create Notebook',
95
100
  description:
96
101
  'Create a new Jupyter notebook with a kernel for the specified programming language',
97
- parameters: z.object({
102
+ inputSchema: z.object({
98
103
  language: z
99
104
  .string()
100
105
  .optional()
@@ -121,49 +126,48 @@ export function createNotebookCreationTool(
121
126
  const { name } = input;
122
127
 
123
128
  if (!name) {
124
- throw new Error('A name must be provided to create a notebook');
129
+ return {
130
+ success: false,
131
+ error: 'A name must be provided to create a notebook'
132
+ };
125
133
  }
126
134
 
127
- try {
128
- // TODO: handle cwd / path?
129
- const fileName = name.endsWith('.ipynb') ? name : `${name}.ipynb`;
130
-
131
- // Create untitled notebook first
132
- const notebookModel = await docManager.newUntitled({
133
- type: 'notebook'
134
- });
135
+ // TODO: handle cwd / path?
136
+ const fileName = name.endsWith('.ipynb') ? name : `${name}.ipynb`;
135
137
 
136
- // Rename to desired filename
137
- await docManager.services.contents.rename(notebookModel.path, fileName);
138
-
139
- // Create widget with specific kernel
140
- const notebook = docManager.createNew(fileName, 'default', {
141
- name: kernel
142
- });
143
-
144
- if (!(notebook instanceof DocumentWidget)) {
145
- throw new Error('Failed to create notebook widget');
146
- }
138
+ // Create untitled notebook first
139
+ const notebookModel = await docManager.newUntitled({
140
+ type: 'notebook'
141
+ });
147
142
 
148
- await notebook.context.ready;
149
- await notebook.context.save();
143
+ // Rename to desired filename
144
+ await docManager.services.contents.rename(notebookModel.path, fileName);
150
145
 
151
- docManager.openOrReveal(fileName);
146
+ // Create widget with specific kernel
147
+ const notebook = docManager.createNew(fileName, 'default', {
148
+ name: kernel
149
+ });
152
150
 
153
- return {
154
- success: true,
155
- message: `Successfully created notebook ${fileName} with ${kernel} kernel${input.language ? ` for ${input.language}` : ''}`,
156
- notebookPath: fileName,
157
- notebookName: fileName,
158
- kernel,
159
- language: input.language
160
- };
161
- } catch (error) {
151
+ if (!(notebook instanceof DocumentWidget)) {
162
152
  return {
163
153
  success: false,
164
- error: `Failed to create notebook: ${(error as Error).message}`
154
+ error: 'Failed to create notebook widget'
165
155
  };
166
156
  }
157
+
158
+ await notebook.context.ready;
159
+ await notebook.context.save();
160
+
161
+ docManager.openOrReveal(fileName);
162
+
163
+ return {
164
+ success: true,
165
+ message: `Successfully created notebook ${fileName} with ${kernel} kernel${input.language ? ` for ${input.language}` : ''}`,
166
+ notebookPath: fileName,
167
+ notebookName: fileName,
168
+ kernel,
169
+ language: input.language
170
+ };
167
171
  }
168
172
  });
169
173
  }
@@ -176,9 +180,9 @@ export function createAddCellTool(
176
180
  notebookTracker?: INotebookTracker
177
181
  ): ITool {
178
182
  return tool({
179
- name: 'add_cell',
183
+ title: 'Add Cell',
180
184
  description: 'Add a cell to the current notebook with optional content',
181
- parameters: z.object({
185
+ inputSchema: z.object({
182
186
  notebookPath: z
183
187
  .string()
184
188
  .optional()
@@ -201,83 +205,76 @@ export function createAddCellTool(
201
205
  .default('below')
202
206
  .describe('Position relative to current cell')
203
207
  }),
204
- async execute({
208
+ execute: async ({
205
209
  notebookPath,
206
210
  content,
207
211
  cellType = 'code',
208
212
  position = 'below'
209
- }) {
210
- try {
211
- const currentWidget = await getNotebookWidget(
212
- notebookPath,
213
- docManager,
214
- notebookTracker
215
- );
216
- if (!currentWidget) {
217
- return {
218
- success: false,
219
- error: notebookPath
220
- ? `Failed to open notebook at path: ${notebookPath}`
221
- : 'No active notebook and no notebook path provided'
222
- };
223
- }
213
+ }) => {
214
+ const currentWidget = await getNotebookWidget(
215
+ notebookPath,
216
+ docManager,
217
+ notebookTracker
218
+ );
219
+ if (!currentWidget) {
220
+ return {
221
+ success: false,
222
+ error: notebookPath
223
+ ? `Failed to open notebook at path: ${notebookPath}`
224
+ : 'No active notebook and no notebook path provided'
225
+ };
226
+ }
224
227
 
225
- const notebook = currentWidget.content;
226
- const model = notebook.model;
228
+ const notebook = currentWidget.content;
229
+ const model = notebook.model;
227
230
 
228
- if (!model) {
229
- return {
230
- success: false,
231
- error: 'No notebook model available'
232
- };
233
- }
231
+ if (!model) {
232
+ return {
233
+ success: false,
234
+ error: 'No notebook model available'
235
+ };
236
+ }
234
237
 
235
- // Check if we should replace the first empty cell instead of adding
236
- const shouldReplaceFirstCell =
237
- model.cells.length === 1 &&
238
- model.cells.get(0).sharedModel.getSource().trim() === '';
238
+ // Check if we should replace the first empty cell instead of adding
239
+ const shouldReplaceFirstCell =
240
+ model.cells.length === 1 &&
241
+ model.cells.get(0).sharedModel.getSource().trim() === '';
239
242
 
240
- if (shouldReplaceFirstCell) {
241
- // Replace the first empty cell by removing it and adding new one
242
- model.sharedModel.deleteCell(0);
243
- }
243
+ if (shouldReplaceFirstCell) {
244
+ // Replace the first empty cell by removing it and adding new one
245
+ model.sharedModel.deleteCell(0);
246
+ }
244
247
 
245
- // Create the new cell using shared model
246
- const newCellData = {
247
- cell_type: cellType,
248
- source: content || '',
249
- metadata: cellType === 'code' ? { trusted: true } : {}
250
- };
248
+ // Create the new cell using shared model
249
+ const newCellData = {
250
+ cell_type: cellType,
251
+ source: content || '',
252
+ metadata: cellType === 'code' ? { trusted: true } : {}
253
+ };
251
254
 
252
- model.sharedModel.addCell(newCellData);
253
-
254
- // Execute markdown cells after creation to render them
255
- if (cellType === 'markdown' && content) {
256
- const cellIndex = model.cells.length - 1;
257
- const cellWidget = notebook.widgets[cellIndex];
258
- if (cellWidget && cellWidget instanceof MarkdownCell) {
259
- try {
260
- await cellWidget.ready;
261
- cellWidget.rendered = true;
262
- } catch (error) {
263
- console.warn('Failed to render markdown cell:', error);
264
- }
255
+ model.sharedModel.addCell(newCellData);
256
+
257
+ // Execute markdown cells after creation to render them
258
+ if (cellType === 'markdown' && content) {
259
+ const cellIndex = model.cells.length - 1;
260
+ const cellWidget = notebook.widgets[cellIndex];
261
+ if (cellWidget && cellWidget instanceof MarkdownCell) {
262
+ try {
263
+ await cellWidget.ready;
264
+ cellWidget.rendered = true;
265
+ } catch (error) {
266
+ console.warn('Failed to render markdown cell:', error);
265
267
  }
266
268
  }
267
-
268
- return {
269
- success: true,
270
- message: `${cellType} cell added successfully`,
271
- content: content || '',
272
- cellType,
273
- position
274
- };
275
- } catch (error) {
276
- return {
277
- success: false,
278
- error: `Failed to add ${cellType} cell: ${(error as Error).message}`
279
- };
280
269
  }
270
+
271
+ return {
272
+ success: true,
273
+ message: `${cellType} cell added successfully`,
274
+ content: content || '',
275
+ cellType,
276
+ position
277
+ };
281
278
  }
282
279
  });
283
280
  }
@@ -290,10 +287,10 @@ export function createGetNotebookInfoTool(
290
287
  notebookTracker?: INotebookTracker
291
288
  ): ITool {
292
289
  return tool({
293
- name: 'get_notebook_info',
290
+ title: 'Get Notebook Info',
294
291
  description:
295
292
  'Get information about a notebook including number of cells and active cell index',
296
- parameters: z.object({
293
+ inputSchema: z.object({
297
294
  notebookPath: z
298
295
  .string()
299
296
  .optional()
@@ -305,51 +302,44 @@ export function createGetNotebookInfoTool(
305
302
  execute: async (input: { notebookPath?: string | null }) => {
306
303
  const { notebookPath } = input;
307
304
 
308
- try {
309
- const currentWidget = await getNotebookWidget(
310
- notebookPath,
311
- docManager,
312
- notebookTracker
313
- );
314
- if (!currentWidget) {
315
- return JSON.stringify({
316
- success: false,
317
- error: notebookPath
318
- ? `Failed to open notebook at path: ${notebookPath}`
319
- : 'No active notebook and no notebook path provided'
320
- });
321
- }
322
-
323
- const notebook = currentWidget.content;
324
- const model = notebook.model;
325
-
326
- if (!model) {
327
- return JSON.stringify({
328
- success: false,
329
- error: 'No notebook model available'
330
- });
331
- }
332
-
333
- const cellCount = model.cells.length;
334
- const activeCellIndex = notebook.activeCellIndex;
335
- const activeCell = notebook.activeCell;
336
- const activeCellType = activeCell?.model.type || 'unknown';
337
-
305
+ const currentWidget = await getNotebookWidget(
306
+ notebookPath,
307
+ docManager,
308
+ notebookTracker
309
+ );
310
+ if (!currentWidget) {
338
311
  return JSON.stringify({
339
- success: true,
340
- notebookName: currentWidget.title.label,
341
- notebookPath: currentWidget.context.path,
342
- cellCount,
343
- activeCellIndex,
344
- activeCellType,
345
- isDirty: model.dirty
312
+ success: false,
313
+ error: notebookPath
314
+ ? `Failed to open notebook at path: ${notebookPath}`
315
+ : 'No active notebook and no notebook path provided'
346
316
  });
347
- } catch (error) {
317
+ }
318
+
319
+ const notebook = currentWidget.content;
320
+ const model = notebook.model;
321
+
322
+ if (!model) {
348
323
  return JSON.stringify({
349
324
  success: false,
350
- error: `Failed to get notebook info: ${(error as Error).message}`
325
+ error: 'No notebook model available'
351
326
  });
352
327
  }
328
+
329
+ const cellCount = model.cells.length;
330
+ const activeCellIndex = notebook.activeCellIndex;
331
+ const activeCell = notebook.activeCell;
332
+ const activeCellType = activeCell?.model.type || 'unknown';
333
+
334
+ return JSON.stringify({
335
+ success: true,
336
+ notebookName: currentWidget.title.label,
337
+ notebookPath: currentWidget.context.path,
338
+ cellCount,
339
+ activeCellIndex,
340
+ activeCellType,
341
+ isDirty: model.dirty
342
+ });
353
343
  }
354
344
  });
355
345
  }
@@ -362,10 +352,10 @@ export function createGetCellInfoTool(
362
352
  notebookTracker?: INotebookTracker
363
353
  ): ITool {
364
354
  return tool({
365
- name: 'get_cell_info',
355
+ title: 'Get Cell Info',
366
356
  description:
367
357
  'Get information about a specific cell including its type, source content, and outputs',
368
- parameters: z.object({
358
+ inputSchema: z.object({
369
359
  notebookPath: z
370
360
  .string()
371
361
  .optional()
@@ -387,70 +377,64 @@ export function createGetCellInfoTool(
387
377
  }) => {
388
378
  const { notebookPath } = input;
389
379
  let { cellIndex } = input;
390
- try {
391
- const currentWidget = await getNotebookWidget(
392
- notebookPath,
393
- docManager,
394
- notebookTracker
395
- );
396
- if (!currentWidget) {
397
- return JSON.stringify({
398
- success: false,
399
- error: notebookPath
400
- ? `Failed to open notebook at path: ${notebookPath}`
401
- : 'No active notebook and no notebook path provided'
402
- });
403
- }
404
-
405
- const notebook = currentWidget.content;
406
- const model = notebook.model;
407
-
408
- if (!model) {
409
- return JSON.stringify({
410
- success: false,
411
- error: 'No notebook model available'
412
- });
413
- }
414
380
 
415
- if (cellIndex === undefined || cellIndex === null) {
416
- cellIndex = notebook.activeCellIndex;
417
- }
418
-
419
- if (cellIndex < 0 || cellIndex >= model.cells.length) {
420
- return JSON.stringify({
421
- success: false,
422
- error: `Invalid cell index: ${cellIndex}. Notebook has ${model.cells.length} cells.`
423
- });
424
- }
425
-
426
- const cell = model.cells.get(cellIndex);
427
- const cellType = cell.type;
428
- const sharedModel = cell.sharedModel;
429
- const source = sharedModel.getSource();
381
+ const currentWidget = await getNotebookWidget(
382
+ notebookPath,
383
+ docManager,
384
+ notebookTracker
385
+ );
386
+ if (!currentWidget) {
387
+ return JSON.stringify({
388
+ success: false,
389
+ error: notebookPath
390
+ ? `Failed to open notebook at path: ${notebookPath}`
391
+ : 'No active notebook and no notebook path provided'
392
+ });
393
+ }
430
394
 
431
- // Get outputs for code cells
432
- let outputs: any[] = [];
433
- if (cellType === 'code') {
434
- const rawOutputs = sharedModel.toJSON().outputs;
435
- outputs = Array.isArray(rawOutputs) ? rawOutputs : [];
436
- }
395
+ const notebook = currentWidget.content;
396
+ const model = notebook.model;
437
397
 
398
+ if (!model) {
438
399
  return JSON.stringify({
439
- success: true,
440
- cellId: cell.id,
441
- cellIndex,
442
- cellType,
443
- source,
444
- outputs,
445
- executionCount:
446
- cellType === 'code' ? (cell as any).executionCount : null
400
+ success: false,
401
+ error: 'No notebook model available'
447
402
  });
448
- } catch (error) {
403
+ }
404
+
405
+ if (cellIndex === undefined || cellIndex === null) {
406
+ cellIndex = notebook.activeCellIndex;
407
+ }
408
+
409
+ if (cellIndex < 0 || cellIndex >= model.cells.length) {
449
410
  return JSON.stringify({
450
411
  success: false,
451
- error: `Failed to get cell info: ${(error as Error).message}`
412
+ error: `Invalid cell index: ${cellIndex}. Notebook has ${model.cells.length} cells.`
452
413
  });
453
414
  }
415
+
416
+ const cell = model.cells.get(cellIndex);
417
+ const cellType = cell.type;
418
+ const sharedModel = cell.sharedModel;
419
+ const source = sharedModel.getSource();
420
+
421
+ // Get outputs for code cells
422
+ let outputs: any[] = [];
423
+ if (cellType === 'code') {
424
+ const rawOutputs = sharedModel.toJSON().outputs;
425
+ outputs = Array.isArray(rawOutputs) ? rawOutputs : [];
426
+ }
427
+
428
+ return JSON.stringify({
429
+ success: true,
430
+ cellId: cell.id,
431
+ cellIndex,
432
+ cellType,
433
+ source,
434
+ outputs,
435
+ executionCount:
436
+ cellType === 'code' ? (cell as CodeCellModel).executionCount : null
437
+ });
454
438
  }
455
439
  });
456
440
  }
@@ -464,10 +448,10 @@ export function createSetCellContentTool(
464
448
  diffManager?: IDiffManager
465
449
  ): ITool {
466
450
  return tool({
467
- name: 'set_cell_content',
451
+ title: 'Set Cell Content',
468
452
  description:
469
453
  'Set the content of a specific cell and return both the previous and new content',
470
- parameters: z.object({
454
+ inputSchema: z.object({
471
455
  notebookPath: z
472
456
  .string()
473
457
  .optional()
@@ -499,120 +483,113 @@ export function createSetCellContentTool(
499
483
  }) => {
500
484
  const { notebookPath, cellId, cellIndex, content } = input;
501
485
 
502
- try {
503
- const notebookWidget = await getNotebookWidget(
504
- notebookPath,
505
- docManager,
506
- notebookTracker
507
- );
508
- if (!notebookWidget) {
509
- return JSON.stringify({
510
- success: false,
511
- error: notebookPath
512
- ? `Failed to open notebook at path: ${notebookPath}`
513
- : 'No active notebook and no notebook path provided'
514
- });
515
- }
486
+ const notebookWidget = await getNotebookWidget(
487
+ notebookPath,
488
+ docManager,
489
+ notebookTracker
490
+ );
491
+ if (!notebookWidget) {
492
+ return JSON.stringify({
493
+ success: false,
494
+ error: notebookPath
495
+ ? `Failed to open notebook at path: ${notebookPath}`
496
+ : 'No active notebook and no notebook path provided'
497
+ });
498
+ }
516
499
 
517
- const notebook = notebookWidget.content;
518
- const targetNotebookPath = notebookWidget.context.path;
500
+ const notebook = notebookWidget.content;
501
+ const targetNotebookPath = notebookWidget.context.path;
519
502
 
520
- const model = notebook.model;
503
+ const model = notebook.model;
521
504
 
522
- if (!model) {
505
+ if (!model) {
506
+ return JSON.stringify({
507
+ success: false,
508
+ error: 'No notebook model available'
509
+ });
510
+ }
511
+
512
+ // Determine target cell index
513
+ let targetCellIndex: number;
514
+ if (cellId !== undefined && cellId !== null) {
515
+ // Find cell by ID
516
+ targetCellIndex = -1;
517
+ for (let i = 0; i < model.cells.length; i++) {
518
+ if (model.cells.get(i).id === cellId) {
519
+ targetCellIndex = i;
520
+ break;
521
+ }
522
+ }
523
+ if (targetCellIndex === -1) {
523
524
  return JSON.stringify({
524
525
  success: false,
525
- error: 'No notebook model available'
526
+ error: `Cell with ID '${cellId}' not found in notebook`
526
527
  });
527
528
  }
528
-
529
- // Determine target cell index
530
- let targetCellIndex: number;
531
- if (cellId !== undefined && cellId !== null) {
532
- // Find cell by ID
533
- targetCellIndex = -1;
534
- for (let i = 0; i < model.cells.length; i++) {
535
- if (model.cells.get(i).id === cellId) {
536
- targetCellIndex = i;
537
- break;
538
- }
539
- }
540
- if (targetCellIndex === -1) {
541
- return JSON.stringify({
542
- success: false,
543
- error: `Cell with ID '${cellId}' not found in notebook`
544
- });
545
- }
546
- } else if (cellIndex !== undefined && cellIndex !== null) {
547
- // Use provided cell index
548
- if (cellIndex < 0 || cellIndex >= model.cells.length) {
549
- return JSON.stringify({
550
- success: false,
551
- error: `Invalid cell index: ${cellIndex}. Notebook has ${model.cells.length} cells.`
552
- });
553
- }
554
- targetCellIndex = cellIndex;
555
- } else {
556
- // Use active cell
557
- targetCellIndex = notebook.activeCellIndex;
558
- if (targetCellIndex === -1 || targetCellIndex >= model.cells.length) {
559
- return JSON.stringify({
560
- success: false,
561
- error: 'No active cell or invalid active cell index'
562
- });
563
- }
529
+ } else if (cellIndex !== undefined && cellIndex !== null) {
530
+ // Use provided cell index
531
+ if (cellIndex < 0 || cellIndex >= model.cells.length) {
532
+ return JSON.stringify({
533
+ success: false,
534
+ error: `Invalid cell index: ${cellIndex}. Notebook has ${model.cells.length} cells.`
535
+ });
564
536
  }
565
-
566
- // Get the target cell
567
- const targetCell = model.cells.get(targetCellIndex);
568
- if (!targetCell) {
537
+ targetCellIndex = cellIndex;
538
+ } else {
539
+ // Use active cell
540
+ targetCellIndex = notebook.activeCellIndex;
541
+ if (targetCellIndex === -1 || targetCellIndex >= model.cells.length) {
569
542
  return JSON.stringify({
570
543
  success: false,
571
- error: `Cell at index ${targetCellIndex} not found`
544
+ error: 'No active cell or invalid active cell index'
572
545
  });
573
546
  }
547
+ }
574
548
 
575
- const sharedModel = targetCell.sharedModel;
549
+ // Get the target cell
550
+ const targetCell = model.cells.get(targetCellIndex);
551
+ if (!targetCell) {
552
+ return JSON.stringify({
553
+ success: false,
554
+ error: `Cell at index ${targetCellIndex} not found`
555
+ });
556
+ }
576
557
 
577
- // Get previous content and type
578
- const previousContent = sharedModel.getSource();
579
- const previousCellType = targetCell.type;
580
- const retrievedCellId = targetCell.id;
558
+ const sharedModel = targetCell.sharedModel;
581
559
 
582
- sharedModel.setSource(content);
560
+ // Get previous content and type
561
+ const previousContent = sharedModel.getSource();
562
+ const previousCellType = targetCell.type;
563
+ const retrievedCellId = targetCell.id;
583
564
 
584
- // Show the cell diff using the diff manager if available
585
- if (diffManager) {
586
- await diffManager.showCellDiff({
587
- original: previousContent,
588
- modified: content,
589
- cellId: retrievedCellId,
590
- notebookPath: targetNotebookPath
591
- });
592
- }
565
+ sharedModel.setSource(content);
593
566
 
594
- return JSON.stringify({
595
- success: true,
596
- message:
597
- cellId !== undefined && cellId !== null
598
- ? `Cell with ID '${cellId}' content replaced successfully`
599
- : cellIndex !== undefined && cellIndex !== null
600
- ? `Cell ${targetCellIndex} content replaced successfully`
601
- : 'Active cell content replaced successfully',
602
- notebookPath: targetNotebookPath,
567
+ // Show the cell diff using the diff manager if available
568
+ if (diffManager) {
569
+ await diffManager.showCellDiff({
570
+ original: previousContent,
571
+ modified: content,
603
572
  cellId: retrievedCellId,
604
- cellIndex: targetCellIndex,
605
- previousContent,
606
- previousCellType,
607
- newContent: content,
608
- wasActiveCell: cellId === undefined && cellIndex === undefined
609
- });
610
- } catch (error) {
611
- return JSON.stringify({
612
- success: false,
613
- error: `Failed to replace cell content: ${(error as Error).message}`
573
+ notebookPath: targetNotebookPath
614
574
  });
615
575
  }
576
+
577
+ return JSON.stringify({
578
+ success: true,
579
+ message:
580
+ cellId !== undefined && cellId !== null
581
+ ? `Cell with ID '${cellId}' content replaced successfully`
582
+ : cellIndex !== undefined && cellIndex !== null
583
+ ? `Cell ${targetCellIndex} content replaced successfully`
584
+ : 'Active cell content replaced successfully',
585
+ notebookPath: targetNotebookPath,
586
+ cellId: retrievedCellId,
587
+ cellIndex: targetCellIndex,
588
+ previousContent,
589
+ previousCellType,
590
+ newContent: content,
591
+ wasActiveCell: cellId === undefined && cellIndex === undefined
592
+ });
616
593
  }
617
594
  });
618
595
  }
@@ -625,9 +602,9 @@ export function createRunCellTool(
625
602
  notebookTracker?: INotebookTracker
626
603
  ): ITool {
627
604
  return tool({
628
- name: 'run_cell',
605
+ title: 'Run Cell',
629
606
  description: 'Run a specific cell in the notebook by index',
630
- parameters: z.object({
607
+ inputSchema: z.object({
631
608
  notebookPath: z
632
609
  .string()
633
610
  .optional()
@@ -649,85 +626,70 @@ export function createRunCellTool(
649
626
  }) => {
650
627
  const { notebookPath, cellIndex, recordTiming = true } = input;
651
628
 
652
- try {
653
- const currentWidget = await getNotebookWidget(
654
- notebookPath,
655
- docManager,
656
- notebookTracker
657
- );
658
- if (!currentWidget) {
659
- return JSON.stringify({
660
- success: false,
661
- error: notebookPath
662
- ? `Failed to open notebook at path: ${notebookPath}`
663
- : 'No active notebook and no notebook path provided'
664
- });
665
- }
666
-
667
- const notebook = currentWidget.content;
668
- const model = notebook.model;
629
+ const currentWidget = await getNotebookWidget(
630
+ notebookPath,
631
+ docManager,
632
+ notebookTracker
633
+ );
634
+ if (!currentWidget) {
635
+ return JSON.stringify({
636
+ success: false,
637
+ error: notebookPath
638
+ ? `Failed to open notebook at path: ${notebookPath}`
639
+ : 'No active notebook and no notebook path provided'
640
+ });
641
+ }
669
642
 
670
- if (!model) {
671
- return JSON.stringify({
672
- success: false,
673
- error: 'No notebook model available'
674
- });
675
- }
643
+ const notebook = currentWidget.content;
644
+ const model = notebook.model;
676
645
 
677
- if (cellIndex < 0 || cellIndex >= model.cells.length) {
678
- return JSON.stringify({
679
- success: false,
680
- error: `Invalid cell index: ${cellIndex}. Notebook has ${model.cells.length} cells.`
681
- });
682
- }
646
+ if (!model) {
647
+ return JSON.stringify({
648
+ success: false,
649
+ error: 'No notebook model available'
650
+ });
651
+ }
683
652
 
684
- // Get the target cell widget
685
- const cellWidget = notebook.widgets[cellIndex];
686
- if (!cellWidget) {
687
- return JSON.stringify({
688
- success: false,
689
- error: `Cell widget at index ${cellIndex} not found`
690
- });
691
- }
653
+ if (cellIndex < 0 || cellIndex >= model.cells.length) {
654
+ return JSON.stringify({
655
+ success: false,
656
+ error: `Invalid cell index: ${cellIndex}. Notebook has ${model.cells.length} cells.`
657
+ });
658
+ }
692
659
 
693
- // Execute using shared model approach (non-disruptive)
694
- try {
695
- if (cellWidget instanceof CodeCell) {
696
- // Use direct CodeCell.execute() method
697
- const sessionCtx = currentWidget.sessionContext;
698
- await CodeCell.execute(cellWidget, sessionCtx, {
699
- recordTiming,
700
- deletedCells: model.deletedCells
701
- });
702
-
703
- const codeModel = cellWidget.model as ICodeCellModel;
704
- return JSON.stringify({
705
- success: true,
706
- message: `Cell ${cellIndex} executed successfully`,
707
- cellIndex,
708
- executionCount: codeModel.executionCount,
709
- hasOutput: codeModel.outputs.length > 0
710
- });
711
- } else {
712
- // For non-code cells, just return success
713
- return JSON.stringify({
714
- success: true,
715
- message: `Cell ${cellIndex} is not a code cell, no execution needed`,
716
- cellIndex,
717
- cellType: cellWidget.model.type
718
- });
719
- }
720
- } catch (error) {
721
- return JSON.stringify({
722
- success: false,
723
- error: `Failed to execute cell: ${(error as Error).message}`,
724
- cellIndex
725
- });
726
- }
727
- } catch (error) {
660
+ // Get the target cell widget
661
+ const cellWidget = notebook.widgets[cellIndex];
662
+ if (!cellWidget) {
728
663
  return JSON.stringify({
729
664
  success: false,
730
- error: `Failed to run cell: ${(error as Error).message}`
665
+ error: `Cell widget at index ${cellIndex} not found`
666
+ });
667
+ }
668
+
669
+ // Execute using shared model approach (non-disruptive)
670
+ if (cellWidget instanceof CodeCell) {
671
+ // Use direct CodeCell.execute() method
672
+ const sessionCtx = currentWidget.sessionContext;
673
+ await CodeCell.execute(cellWidget, sessionCtx, {
674
+ recordTiming,
675
+ deletedCells: model.deletedCells
676
+ });
677
+
678
+ const codeModel = cellWidget.model as ICodeCellModel;
679
+ return JSON.stringify({
680
+ success: true,
681
+ message: `Cell ${cellIndex} executed successfully`,
682
+ cellIndex,
683
+ executionCount: codeModel.executionCount,
684
+ hasOutput: codeModel.outputs.length > 0
685
+ });
686
+ } else {
687
+ // For non-code cells, just return success
688
+ return JSON.stringify({
689
+ success: true,
690
+ message: `Cell ${cellIndex} is not a code cell, no execution needed`,
691
+ cellIndex,
692
+ cellType: cellWidget.model.type
731
693
  });
732
694
  }
733
695
  }
@@ -742,9 +704,9 @@ export function createDeleteCellTool(
742
704
  notebookTracker?: INotebookTracker
743
705
  ): ITool {
744
706
  return tool({
745
- name: 'delete_cell',
707
+ title: 'Delete Cell',
746
708
  description: 'Delete a specific cell from the notebook by index',
747
- parameters: z.object({
709
+ inputSchema: z.object({
748
710
  notebookPath: z
749
711
  .string()
750
712
  .optional()
@@ -760,62 +722,55 @@ export function createDeleteCellTool(
760
722
  }) => {
761
723
  const { notebookPath, cellIndex } = input;
762
724
 
763
- try {
764
- const currentWidget = await getNotebookWidget(
765
- notebookPath,
766
- docManager,
767
- notebookTracker
768
- );
769
- if (!currentWidget) {
770
- return JSON.stringify({
771
- success: false,
772
- error: notebookPath
773
- ? `Failed to open notebook at path: ${notebookPath}`
774
- : 'No active notebook and no notebook path provided'
775
- });
776
- }
777
-
778
- const notebook = currentWidget.content;
779
- const model = notebook.model;
780
-
781
- if (!model) {
782
- return JSON.stringify({
783
- success: false,
784
- error: 'No notebook model available'
785
- });
786
- }
787
-
788
- if (cellIndex < 0 || cellIndex >= model.cells.length) {
789
- return JSON.stringify({
790
- success: false,
791
- error: `Invalid cell index: ${cellIndex}. Notebook has ${model.cells.length} cells.`
792
- });
793
- }
725
+ const currentWidget = await getNotebookWidget(
726
+ notebookPath,
727
+ docManager,
728
+ notebookTracker
729
+ );
730
+ if (!currentWidget) {
731
+ return JSON.stringify({
732
+ success: false,
733
+ error: notebookPath
734
+ ? `Failed to open notebook at path: ${notebookPath}`
735
+ : 'No active notebook and no notebook path provided'
736
+ });
737
+ }
794
738
 
795
- // Validate cell exists
796
- const targetCell = model.cells.get(cellIndex);
797
- if (!targetCell) {
798
- return JSON.stringify({
799
- success: false,
800
- error: `Cell at index ${cellIndex} not found`
801
- });
802
- }
739
+ const notebook = currentWidget.content;
740
+ const model = notebook.model;
803
741
 
804
- // Delete cell using shared model (non-disruptive)
805
- model.sharedModel.deleteCell(cellIndex);
742
+ if (!model) {
743
+ return JSON.stringify({
744
+ success: false,
745
+ error: 'No notebook model available'
746
+ });
747
+ }
806
748
 
749
+ if (cellIndex < 0 || cellIndex >= model.cells.length) {
807
750
  return JSON.stringify({
808
- success: true,
809
- message: `Cell ${cellIndex} deleted successfully`,
810
- cellIndex,
811
- remainingCells: model.cells.length
751
+ success: false,
752
+ error: `Invalid cell index: ${cellIndex}. Notebook has ${model.cells.length} cells.`
812
753
  });
813
- } catch (error) {
754
+ }
755
+
756
+ // Validate cell exists
757
+ const targetCell = model.cells.get(cellIndex);
758
+ if (!targetCell) {
814
759
  return JSON.stringify({
815
760
  success: false,
816
- error: `Failed to delete cell: ${(error as Error).message}`
761
+ error: `Cell at index ${cellIndex} not found`
817
762
  });
818
763
  }
764
+
765
+ // Delete cell using shared model (non-disruptive)
766
+ model.sharedModel.deleteCell(cellIndex);
767
+
768
+ return JSON.stringify({
769
+ success: true,
770
+ message: `Cell ${cellIndex} deleted successfully`,
771
+ cellIndex,
772
+ remainingCells: model.cells.length
773
+ });
819
774
  }
820
775
  });
821
776
  }
@@ -828,10 +783,10 @@ export function createExecuteActiveCellTool(
828
783
  notebookTracker?: INotebookTracker
829
784
  ): ITool {
830
785
  return tool({
831
- name: 'execute_active_cell',
786
+ title: 'Execute Active Cell',
832
787
  description:
833
788
  'Execute the currently active cell in the notebook without disrupting user focus',
834
- parameters: z.object({
789
+ inputSchema: z.object({
835
790
  notebookPath: z
836
791
  .string()
837
792
  .optional()
@@ -856,76 +811,69 @@ export function createExecuteActiveCellTool(
856
811
  }) => {
857
812
  const { notebookPath, code, recordTiming = true } = input;
858
813
 
859
- try {
860
- const currentWidget = await getNotebookWidget(
861
- notebookPath,
862
- docManager,
863
- notebookTracker
864
- );
865
- if (!currentWidget) {
866
- return JSON.stringify({
867
- success: false,
868
- error: notebookPath
869
- ? `Failed to open notebook at path: ${notebookPath}`
870
- : 'No active notebook and no notebook path provided'
871
- });
872
- }
873
-
874
- const notebook = currentWidget.content;
875
- const model = notebook.model;
876
- const activeCellIndex = notebook.activeCellIndex;
877
-
878
- if (!model || activeCellIndex === -1) {
879
- return JSON.stringify({
880
- success: false,
881
- error: 'No notebook model or active cell available'
882
- });
883
- }
884
-
885
- const activeCell = model.cells.get(activeCellIndex);
886
- if (!activeCell) {
887
- return JSON.stringify({
888
- success: false,
889
- error: 'Active cell not found'
890
- });
891
- }
892
-
893
- // Set code content if provided
894
- if (code) {
895
- activeCell.sharedModel.setSource(code);
896
- }
814
+ const currentWidget = await getNotebookWidget(
815
+ notebookPath,
816
+ docManager,
817
+ notebookTracker
818
+ );
819
+ if (!currentWidget) {
820
+ return JSON.stringify({
821
+ success: false,
822
+ error: notebookPath
823
+ ? `Failed to open notebook at path: ${notebookPath}`
824
+ : 'No active notebook and no notebook path provided'
825
+ });
826
+ }
897
827
 
898
- // Get the cell widget for execution
899
- const cellWidget = notebook.widgets[activeCellIndex];
900
- if (!cellWidget || !(cellWidget instanceof CodeCell)) {
901
- return JSON.stringify({
902
- success: false,
903
- error: 'Active cell is not a code cell'
904
- });
905
- }
828
+ const notebook = currentWidget.content;
829
+ const model = notebook.model;
830
+ const activeCellIndex = notebook.activeCellIndex;
906
831
 
907
- // Execute using shared model approach (non-disruptive)
908
- const sessionCtx = currentWidget.sessionContext;
909
- await CodeCell.execute(cellWidget, sessionCtx, {
910
- recordTiming,
911
- deletedCells: model.deletedCells
832
+ if (!model || activeCellIndex === -1) {
833
+ return JSON.stringify({
834
+ success: false,
835
+ error: 'No notebook model or active cell available'
912
836
  });
837
+ }
913
838
 
914
- const codeModel = cellWidget.model as ICodeCellModel;
839
+ const activeCell = model.cells.get(activeCellIndex);
840
+ if (!activeCell) {
915
841
  return JSON.stringify({
916
- success: true,
917
- message: 'Code executed successfully in active cell',
918
- cellIndex: activeCellIndex,
919
- executionCount: codeModel.executionCount,
920
- hasOutput: codeModel.outputs.length > 0,
921
- code: code || activeCell.sharedModel.getSource()
842
+ success: false,
843
+ error: 'Active cell not found'
922
844
  });
923
- } catch (error) {
845
+ }
846
+
847
+ // Set code content if provided
848
+ if (code) {
849
+ activeCell.sharedModel.setSource(code);
850
+ }
851
+
852
+ // Get the cell widget for execution
853
+ const cellWidget = notebook.widgets[activeCellIndex];
854
+ if (!cellWidget || !(cellWidget instanceof CodeCell)) {
924
855
  return JSON.stringify({
925
856
  success: false,
926
- error: `Failed to execute code: ${(error as Error).message}`
857
+ error: 'Active cell is not a code cell'
927
858
  });
928
859
  }
860
+
861
+ // Execute using shared model approach (non-disruptive)
862
+ const sessionCtx = currentWidget.sessionContext;
863
+ await CodeCell.execute(cellWidget, sessionCtx, {
864
+ recordTiming,
865
+ deletedCells: model.deletedCells
866
+ });
867
+
868
+ const codeModel = cellWidget.model as ICodeCellModel;
869
+ return JSON.stringify({
870
+ success: true,
871
+ message: 'Code executed successfully in active cell',
872
+ cellIndex: activeCellIndex,
873
+ executionCount: codeModel.executionCount,
874
+ hasOutput: codeModel.outputs.length > 0,
875
+ code: code || activeCell.sharedModel.getSource()
876
+ });
929
877
  }
930
878
  });
931
879
  }
@@ -938,9 +886,9 @@ export function createSaveNotebookTool(
938
886
  notebookTracker?: INotebookTracker
939
887
  ): ITool {
940
888
  return tool({
941
- name: 'save_notebook',
889
+ title: 'Save Notebook',
942
890
  description: 'Save a specific notebook to disk',
943
- parameters: z.object({
891
+ inputSchema: z.object({
944
892
  notebookPath: z
945
893
  .string()
946
894
  .optional()
@@ -952,35 +900,28 @@ export function createSaveNotebookTool(
952
900
  execute: async (input: { notebookPath?: string | null }) => {
953
901
  const { notebookPath } = input;
954
902
 
955
- try {
956
- const currentWidget = await getNotebookWidget(
957
- notebookPath,
958
- docManager,
959
- notebookTracker
960
- );
961
- if (!currentWidget) {
962
- return JSON.stringify({
963
- success: false,
964
- error: notebookPath
965
- ? `Failed to open notebook at path: ${notebookPath}`
966
- : 'No active notebook and no notebook path provided'
967
- });
968
- }
969
-
970
- await currentWidget.context.save();
971
-
972
- return JSON.stringify({
973
- success: true,
974
- message: 'Notebook saved successfully',
975
- notebookName: currentWidget.title.label,
976
- notebookPath: currentWidget.context.path
977
- });
978
- } catch (error) {
903
+ const currentWidget = await getNotebookWidget(
904
+ notebookPath,
905
+ docManager,
906
+ notebookTracker
907
+ );
908
+ if (!currentWidget) {
979
909
  return JSON.stringify({
980
910
  success: false,
981
- error: `Failed to save notebook: ${(error as Error).message}`
911
+ error: notebookPath
912
+ ? `Failed to open notebook at path: ${notebookPath}`
913
+ : 'No active notebook and no notebook path provided'
982
914
  });
983
915
  }
916
+
917
+ await currentWidget.context.save();
918
+
919
+ return JSON.stringify({
920
+ success: true,
921
+ message: 'Notebook saved successfully',
922
+ notebookName: currentWidget.title.label,
923
+ notebookPath: currentWidget.context.path
924
+ });
984
925
  }
985
926
  });
986
927
  }