@jupyterlite/ai 0.9.0-a3 → 0.9.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.
- package/README.md +20 -89
- package/lib/agent.d.ts +10 -4
- package/lib/agent.js +30 -17
- package/lib/chat-model.d.ts +6 -0
- package/lib/chat-model.js +144 -17
- package/lib/completion/completion-provider.js +1 -13
- package/lib/components/completion-status.d.ts +20 -0
- package/lib/components/completion-status.js +51 -0
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.js +1 -0
- package/lib/components/model-select.js +1 -2
- package/lib/diff-manager.d.ts +25 -0
- package/lib/diff-manager.js +60 -0
- package/lib/icons.d.ts +0 -1
- package/lib/icons.js +2 -6
- package/lib/index.d.ts +2 -2
- package/lib/index.js +54 -23
- package/lib/models/settings-model.d.ts +4 -0
- package/lib/models/settings-model.js +24 -2
- package/lib/providers/built-in-providers.d.ts +0 -4
- package/lib/providers/built-in-providers.js +17 -23
- package/lib/tokens.d.ts +74 -0
- package/lib/tokens.js +4 -0
- package/lib/tools/commands.js +36 -35
- package/lib/tools/file.d.ts +10 -1
- package/lib/tools/file.js +235 -146
- package/lib/tools/notebook.d.ts +2 -3
- package/lib/tools/notebook.js +11 -11
- package/lib/widgets/ai-settings.js +78 -13
- package/lib/widgets/provider-config-dialog.js +15 -8
- package/package.json +5 -3
- package/schema/settings-model.json +25 -0
- package/src/agent.ts +35 -20
- package/src/chat-model.ts +182 -19
- package/src/completion/completion-provider.ts +1 -14
- package/src/components/completion-status.tsx +79 -0
- package/src/components/index.ts +1 -0
- package/src/components/model-select.tsx +0 -3
- package/src/diff-manager.ts +81 -0
- package/src/icons.ts +2 -7
- package/src/index.ts +74 -24
- package/src/models/settings-model.ts +28 -2
- package/src/providers/built-in-providers.ts +17 -24
- package/src/tokens.ts +78 -0
- package/src/tools/commands.ts +45 -40
- package/src/tools/file.ts +295 -164
- package/src/tools/notebook.ts +13 -14
- package/src/widgets/ai-settings.tsx +184 -35
- package/src/widgets/provider-config-dialog.tsx +43 -16
- package/style/base.css +14 -0
package/src/tools/file.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { PathExt } from '@jupyterlab/coreutils';
|
|
1
2
|
import { CommandRegistry } from '@lumino/commands';
|
|
2
3
|
import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
4
|
+
import { IDocumentWidget } from '@jupyterlab/docregistry';
|
|
5
|
+
import { IEditorTracker } from '@jupyterlab/fileeditor';
|
|
3
6
|
|
|
4
7
|
import { tool } from '@openai/agents';
|
|
5
8
|
|
|
6
9
|
import { z } from 'zod';
|
|
7
10
|
|
|
8
|
-
import { ITool } from '../tokens';
|
|
11
|
+
import { IDiffManager, ITool } from '../tokens';
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* Create a tool for creating new files of various types
|
|
@@ -18,16 +21,11 @@ export function createNewFileTool(docManager: IDocumentManager): ITool {
|
|
|
18
21
|
parameters: z.object({
|
|
19
22
|
fileName: z.string().describe('Name of the file to create'),
|
|
20
23
|
fileType: z
|
|
21
|
-
.
|
|
22
|
-
'text',
|
|
23
|
-
'python',
|
|
24
|
-
'markdown',
|
|
25
|
-
'json',
|
|
26
|
-
'javascript',
|
|
27
|
-
'typescript'
|
|
28
|
-
])
|
|
24
|
+
.string()
|
|
29
25
|
.default('text')
|
|
30
|
-
.describe(
|
|
26
|
+
.describe(
|
|
27
|
+
'Type of file to create. Common examples: text, python, markdown, json, javascript, typescript, yaml, julia, r, csv'
|
|
28
|
+
),
|
|
31
29
|
content: z
|
|
32
30
|
.string()
|
|
33
31
|
.optional()
|
|
@@ -39,96 +37,66 @@ export function createNewFileTool(docManager: IDocumentManager): ITool {
|
|
|
39
37
|
.nullable()
|
|
40
38
|
.describe('Directory where to create the file (optional)')
|
|
41
39
|
}),
|
|
40
|
+
errorFunction: (context, error) => {
|
|
41
|
+
return JSON.stringify({
|
|
42
|
+
success: false,
|
|
43
|
+
error: `Failed to create file: ${error instanceof Error ? error.message : String(error)}`
|
|
44
|
+
});
|
|
45
|
+
},
|
|
42
46
|
execute: async (input: {
|
|
43
47
|
fileName: string;
|
|
44
|
-
fileType?:
|
|
45
|
-
| 'text'
|
|
46
|
-
| 'python'
|
|
47
|
-
| 'markdown'
|
|
48
|
-
| 'json'
|
|
49
|
-
| 'javascript'
|
|
50
|
-
| 'typescript';
|
|
48
|
+
fileType?: string;
|
|
51
49
|
content?: string | null;
|
|
52
50
|
cwd?: string | null;
|
|
53
51
|
}) => {
|
|
54
52
|
const { fileName, content = '', cwd, fileType = 'text' } = input;
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const extensions: Record<string, string> = {
|
|
59
|
-
python: 'py',
|
|
60
|
-
markdown: 'md',
|
|
61
|
-
json: 'json',
|
|
62
|
-
text: 'txt',
|
|
63
|
-
javascript: 'js',
|
|
64
|
-
typescript: 'ts'
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const ext = extensions[fileType] || 'txt';
|
|
68
|
-
|
|
69
|
-
// If fileName already has an extension, use it as-is, otherwise add the extension
|
|
70
|
-
const fullFileName = fileName.includes('.')
|
|
71
|
-
? fileName
|
|
72
|
-
: `${fileName}.${ext}`;
|
|
73
|
-
|
|
74
|
-
// For Python files, ensure .py extension if fileType is python
|
|
75
|
-
const finalFileName =
|
|
76
|
-
fileType === 'python' &&
|
|
77
|
-
!fileName.endsWith('.py') &&
|
|
78
|
-
!fileName.includes('.')
|
|
79
|
-
? `${fileName}.py`
|
|
80
|
-
: fullFileName;
|
|
81
|
-
|
|
82
|
-
const fullPath = cwd ? `${cwd}/${finalFileName}` : finalFileName;
|
|
83
|
-
|
|
84
|
-
// Create file with content using document manager
|
|
85
|
-
const model = await docManager.services.contents.newUntitled({
|
|
86
|
-
path: cwd || '',
|
|
87
|
-
type: 'file',
|
|
88
|
-
ext
|
|
89
|
-
});
|
|
54
|
+
const registeredFileType = docManager.registry.getFileType(fileType);
|
|
55
|
+
const ext = registeredFileType?.extensions[0] || '.txt';
|
|
90
56
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (model.name !== finalFileName) {
|
|
94
|
-
const renamed = await docManager.services.contents.rename(
|
|
95
|
-
model.path,
|
|
96
|
-
fullPath
|
|
97
|
-
);
|
|
98
|
-
finalPath = renamed.path;
|
|
99
|
-
}
|
|
57
|
+
const existingExt = PathExt.extname(fileName);
|
|
58
|
+
const fullFileName = existingExt ? fileName : `${fileName}${ext}`;
|
|
100
59
|
|
|
101
|
-
|
|
102
|
-
if (content) {
|
|
103
|
-
await docManager.services.contents.save(finalPath, {
|
|
104
|
-
type: 'file',
|
|
105
|
-
format: 'text',
|
|
106
|
-
content
|
|
107
|
-
});
|
|
108
|
-
}
|
|
60
|
+
const fullPath = cwd ? `${cwd}/${fullFileName}` : fullFileName;
|
|
109
61
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
62
|
+
const model = await docManager.services.contents.newUntitled({
|
|
63
|
+
path: cwd || '',
|
|
64
|
+
type: 'file',
|
|
65
|
+
ext
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
let finalPath = model.path;
|
|
69
|
+
if (model.name !== fullFileName) {
|
|
70
|
+
const renamed = await docManager.services.contents.rename(
|
|
71
|
+
model.path,
|
|
72
|
+
fullPath
|
|
73
|
+
);
|
|
74
|
+
finalPath = renamed.path;
|
|
75
|
+
}
|
|
116
76
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
hasContent: !!content,
|
|
124
|
-
opened
|
|
125
|
-
};
|
|
126
|
-
} catch (error) {
|
|
127
|
-
return {
|
|
128
|
-
success: false,
|
|
129
|
-
error: `Failed to create file: ${(error as Error).message}`
|
|
130
|
-
};
|
|
77
|
+
if (content) {
|
|
78
|
+
await docManager.services.contents.save(finalPath, {
|
|
79
|
+
type: 'file',
|
|
80
|
+
format: 'text',
|
|
81
|
+
content
|
|
82
|
+
});
|
|
131
83
|
}
|
|
84
|
+
|
|
85
|
+
let opened = false;
|
|
86
|
+
if (!docManager.findWidget(finalPath)) {
|
|
87
|
+
docManager.openOrReveal(finalPath);
|
|
88
|
+
opened = true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
success: true,
|
|
93
|
+
message: `${fileType} file '${fullFileName}' created and opened successfully`,
|
|
94
|
+
fileName: fullFileName,
|
|
95
|
+
filePath: finalPath,
|
|
96
|
+
fileType,
|
|
97
|
+
hasContent: !!content,
|
|
98
|
+
opened
|
|
99
|
+
};
|
|
132
100
|
}
|
|
133
101
|
});
|
|
134
102
|
}
|
|
@@ -143,31 +111,27 @@ export function createOpenFileTool(docManager: IDocumentManager): ITool {
|
|
|
143
111
|
parameters: z.object({
|
|
144
112
|
filePath: z.string().describe('Path to the file to open')
|
|
145
113
|
}),
|
|
114
|
+
errorFunction: (context, error) => {
|
|
115
|
+
return JSON.stringify({
|
|
116
|
+
success: false,
|
|
117
|
+
error: `Failed to open file: ${error instanceof Error ? error.message : String(error)}`
|
|
118
|
+
});
|
|
119
|
+
},
|
|
146
120
|
execute: async (input: { filePath: string }) => {
|
|
147
121
|
const { filePath } = input;
|
|
148
122
|
|
|
149
|
-
|
|
150
|
-
const widget = docManager.openOrReveal(filePath);
|
|
151
|
-
|
|
152
|
-
if (!widget) {
|
|
153
|
-
return {
|
|
154
|
-
success: false,
|
|
155
|
-
error: `Failed to open file: ${filePath}`
|
|
156
|
-
};
|
|
157
|
-
}
|
|
123
|
+
const widget = docManager.openOrReveal(filePath);
|
|
158
124
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
message: `File '${filePath}' opened successfully`,
|
|
162
|
-
filePath,
|
|
163
|
-
widgetId: widget.id
|
|
164
|
-
};
|
|
165
|
-
} catch (error) {
|
|
166
|
-
return {
|
|
167
|
-
success: false,
|
|
168
|
-
error: `Failed to open file: ${(error as Error).message}`
|
|
169
|
-
};
|
|
125
|
+
if (!widget) {
|
|
126
|
+
throw new Error(`Could not open file: ${filePath}`);
|
|
170
127
|
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
success: true,
|
|
131
|
+
message: `File '${filePath}' opened successfully`,
|
|
132
|
+
filePath,
|
|
133
|
+
widgetId: widget.id
|
|
134
|
+
};
|
|
171
135
|
}
|
|
172
136
|
});
|
|
173
137
|
}
|
|
@@ -182,23 +146,22 @@ export function createDeleteFileTool(docManager: IDocumentManager): ITool {
|
|
|
182
146
|
parameters: z.object({
|
|
183
147
|
filePath: z.string().describe('Path to the file to delete')
|
|
184
148
|
}),
|
|
149
|
+
errorFunction: (context, error) => {
|
|
150
|
+
return JSON.stringify({
|
|
151
|
+
success: false,
|
|
152
|
+
error: `Failed to delete file: ${error instanceof Error ? error.message : String(error)}`
|
|
153
|
+
});
|
|
154
|
+
},
|
|
185
155
|
execute: async (input: { filePath: string }) => {
|
|
186
156
|
const { filePath } = input;
|
|
187
157
|
|
|
188
|
-
|
|
189
|
-
await docManager.services.contents.delete(filePath);
|
|
158
|
+
await docManager.services.contents.delete(filePath);
|
|
190
159
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
} catch (error) {
|
|
197
|
-
return {
|
|
198
|
-
success: false,
|
|
199
|
-
error: `Failed to delete file: ${(error as Error).message}`
|
|
200
|
-
};
|
|
201
|
-
}
|
|
160
|
+
return {
|
|
161
|
+
success: true,
|
|
162
|
+
message: `File '${filePath}' deleted successfully`,
|
|
163
|
+
filePath
|
|
164
|
+
};
|
|
202
165
|
}
|
|
203
166
|
});
|
|
204
167
|
}
|
|
@@ -214,24 +177,23 @@ export function createRenameFileTool(docManager: IDocumentManager): ITool {
|
|
|
214
177
|
oldPath: z.string().describe('Current path of the file'),
|
|
215
178
|
newPath: z.string().describe('New path/name for the file')
|
|
216
179
|
}),
|
|
180
|
+
errorFunction: (context, error) => {
|
|
181
|
+
return JSON.stringify({
|
|
182
|
+
success: false,
|
|
183
|
+
error: `Failed to rename file: ${error instanceof Error ? error.message : String(error)}`
|
|
184
|
+
});
|
|
185
|
+
},
|
|
217
186
|
execute: async (input: { oldPath: string; newPath: string }) => {
|
|
218
187
|
const { oldPath, newPath } = input;
|
|
219
188
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
};
|
|
229
|
-
} catch (error) {
|
|
230
|
-
return {
|
|
231
|
-
success: false,
|
|
232
|
-
error: `Failed to rename file: ${(error as Error).message}`
|
|
233
|
-
};
|
|
234
|
-
}
|
|
189
|
+
await docManager.services.contents.rename(oldPath, newPath);
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
success: true,
|
|
193
|
+
message: `File renamed from '${oldPath}' to '${newPath}' successfully`,
|
|
194
|
+
oldPath,
|
|
195
|
+
newPath
|
|
196
|
+
};
|
|
235
197
|
}
|
|
236
198
|
});
|
|
237
199
|
}
|
|
@@ -249,24 +211,23 @@ export function createCopyFileTool(docManager: IDocumentManager): ITool {
|
|
|
249
211
|
.string()
|
|
250
212
|
.describe('Destination path for the copied file')
|
|
251
213
|
}),
|
|
214
|
+
errorFunction: (context, error) => {
|
|
215
|
+
return JSON.stringify({
|
|
216
|
+
success: false,
|
|
217
|
+
error: `Failed to copy file: ${error instanceof Error ? error.message : String(error)}`
|
|
218
|
+
});
|
|
219
|
+
},
|
|
252
220
|
execute: async (input: { sourcePath: string; destinationPath: string }) => {
|
|
253
221
|
const { sourcePath, destinationPath } = input;
|
|
254
222
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
};
|
|
264
|
-
} catch (error) {
|
|
265
|
-
return {
|
|
266
|
-
success: false,
|
|
267
|
-
error: `Failed to copy file: ${(error as Error).message}`
|
|
268
|
-
};
|
|
269
|
-
}
|
|
223
|
+
await docManager.services.contents.copy(sourcePath, destinationPath);
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
success: true,
|
|
227
|
+
message: `File copied from '${sourcePath}' to '${destinationPath}' successfully`,
|
|
228
|
+
sourcePath,
|
|
229
|
+
destinationPath
|
|
230
|
+
};
|
|
270
231
|
}
|
|
271
232
|
});
|
|
272
233
|
}
|
|
@@ -283,25 +244,195 @@ export function createNavigateToDirectoryTool(
|
|
|
283
244
|
parameters: z.object({
|
|
284
245
|
directoryPath: z.string().describe('Path to the directory to navigate to')
|
|
285
246
|
}),
|
|
247
|
+
errorFunction: (context, error) => {
|
|
248
|
+
return JSON.stringify({
|
|
249
|
+
success: false,
|
|
250
|
+
error: `Failed to navigate to directory: ${error instanceof Error ? error.message : String(error)}`
|
|
251
|
+
});
|
|
252
|
+
},
|
|
286
253
|
execute: async (input: { directoryPath: string }) => {
|
|
287
254
|
const { directoryPath } = input;
|
|
288
255
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
256
|
+
await commands.execute('filebrowser:go-to-path', {
|
|
257
|
+
path: directoryPath
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
success: true,
|
|
262
|
+
message: `Navigated to directory '${directoryPath}' successfully`,
|
|
263
|
+
directoryPath
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Create a tool for getting file information and content
|
|
271
|
+
*/
|
|
272
|
+
export function createGetFileInfoTool(
|
|
273
|
+
docManager: IDocumentManager,
|
|
274
|
+
editorTracker?: IEditorTracker
|
|
275
|
+
): ITool {
|
|
276
|
+
return tool({
|
|
277
|
+
name: 'get_file_info',
|
|
278
|
+
description:
|
|
279
|
+
'Get information about a file including its path, name, extension, and content. Works with text-based files like Python files, markdown, JSON, etc. For Jupyter notebooks, use dedicated notebook tools instead. If no file path is provided, returns information about the currently active file in the editor.',
|
|
280
|
+
parameters: z.object({
|
|
281
|
+
filePath: z
|
|
282
|
+
.string()
|
|
283
|
+
.optional()
|
|
284
|
+
.nullable()
|
|
285
|
+
.describe(
|
|
286
|
+
'Path to the file to read (e.g., "script.py", "README.md", "config.json"). If not provided, uses the currently active file in the editor.'
|
|
287
|
+
)
|
|
288
|
+
}),
|
|
289
|
+
errorFunction: (context, error) => {
|
|
290
|
+
return JSON.stringify({
|
|
291
|
+
success: false,
|
|
292
|
+
error: `Failed to get file info: ${error instanceof Error ? error.message : String(error)}`
|
|
293
|
+
});
|
|
294
|
+
},
|
|
295
|
+
execute: async (input: { filePath?: string | null }) => {
|
|
296
|
+
const { filePath } = input;
|
|
297
|
+
|
|
298
|
+
let widget: IDocumentWidget | null = null;
|
|
299
|
+
|
|
300
|
+
if (filePath) {
|
|
301
|
+
widget =
|
|
302
|
+
docManager.findWidget(filePath) ??
|
|
303
|
+
docManager.openOrReveal(filePath) ??
|
|
304
|
+
null;
|
|
305
|
+
|
|
306
|
+
if (!widget) {
|
|
307
|
+
throw new Error(`Failed to open file at path: ${filePath}`);
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
widget = editorTracker?.currentWidget ?? null;
|
|
311
|
+
|
|
312
|
+
if (!widget) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
'No active file in the editor and no file path provided'
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (!widget.context) {
|
|
320
|
+
throw new Error('Widget is not a document');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
await widget.context.ready;
|
|
324
|
+
|
|
325
|
+
const model = widget.context.model;
|
|
326
|
+
|
|
327
|
+
if (!model) {
|
|
328
|
+
throw new Error('File model not available');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const sharedModel = model.sharedModel;
|
|
332
|
+
const content = sharedModel.getSource();
|
|
333
|
+
const resolvedFilePath = widget.context.path;
|
|
334
|
+
const fileName = widget.title.label;
|
|
335
|
+
const fileExtension = PathExt.extname(resolvedFilePath) || 'unknown';
|
|
336
|
+
|
|
337
|
+
return JSON.stringify({
|
|
338
|
+
success: true,
|
|
339
|
+
filePath: resolvedFilePath,
|
|
340
|
+
fileName,
|
|
341
|
+
fileExtension,
|
|
342
|
+
content,
|
|
343
|
+
isDirty: model.dirty,
|
|
344
|
+
readOnly: model.readOnly,
|
|
345
|
+
widgetType: widget.constructor.name
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Create a tool for setting the content of a file
|
|
353
|
+
*/
|
|
354
|
+
export function createSetFileContentTool(
|
|
355
|
+
docManager: IDocumentManager,
|
|
356
|
+
diffManager?: IDiffManager
|
|
357
|
+
): ITool {
|
|
358
|
+
return tool({
|
|
359
|
+
name: 'set_file_content',
|
|
360
|
+
description:
|
|
361
|
+
'Set or update the content of an existing file. This will replace the entire content of the file. For Jupyter notebooks, use dedicated notebook tools instead.',
|
|
362
|
+
parameters: z.object({
|
|
363
|
+
filePath: z
|
|
364
|
+
.string()
|
|
365
|
+
.describe(
|
|
366
|
+
'Path to the file to update (e.g., "script.py", "README.md", "config.json")'
|
|
367
|
+
),
|
|
368
|
+
content: z.string().describe('The new content to set for the file'),
|
|
369
|
+
save: z
|
|
370
|
+
.boolean()
|
|
371
|
+
.optional()
|
|
372
|
+
.default(true)
|
|
373
|
+
.describe('Whether to save the file after updating (default: true)')
|
|
374
|
+
}),
|
|
375
|
+
errorFunction: (context, error) => {
|
|
376
|
+
return JSON.stringify({
|
|
377
|
+
success: false,
|
|
378
|
+
error: `Failed to set file content: ${error instanceof Error ? error.message : String(error)}`
|
|
379
|
+
});
|
|
380
|
+
},
|
|
381
|
+
execute: async (input: {
|
|
382
|
+
filePath: string;
|
|
383
|
+
content: string;
|
|
384
|
+
save?: boolean;
|
|
385
|
+
}) => {
|
|
386
|
+
const { filePath, content, save = true } = input;
|
|
387
|
+
|
|
388
|
+
let widget = docManager.findWidget(filePath);
|
|
389
|
+
|
|
390
|
+
if (!widget) {
|
|
391
|
+
widget = docManager.openOrReveal(filePath);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (!widget) {
|
|
395
|
+
throw new Error(`Failed to open file at path: ${filePath}`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
await widget.context.ready;
|
|
399
|
+
|
|
400
|
+
const model = widget.context.model;
|
|
401
|
+
|
|
402
|
+
if (!model) {
|
|
403
|
+
throw new Error('File model not available');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (model.readOnly) {
|
|
407
|
+
throw new Error('File is read-only and cannot be modified');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const sharedModel = model.sharedModel;
|
|
411
|
+
const originalContent = sharedModel.getSource();
|
|
412
|
+
|
|
413
|
+
sharedModel.setSource(content);
|
|
414
|
+
|
|
415
|
+
// Show the file diff using the diff manager if available
|
|
416
|
+
if (diffManager) {
|
|
417
|
+
await diffManager.showFileDiff({
|
|
418
|
+
original: String(originalContent),
|
|
419
|
+
modified: content,
|
|
420
|
+
filePath
|
|
292
421
|
});
|
|
422
|
+
}
|
|
293
423
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
message: `Navigated to directory '${directoryPath}' successfully`,
|
|
297
|
-
directoryPath
|
|
298
|
-
};
|
|
299
|
-
} catch (error) {
|
|
300
|
-
return {
|
|
301
|
-
success: false,
|
|
302
|
-
error: `Failed to navigate to directory: ${(error as Error).message}`
|
|
303
|
-
};
|
|
424
|
+
if (save) {
|
|
425
|
+
await widget.context.save();
|
|
304
426
|
}
|
|
427
|
+
|
|
428
|
+
return JSON.stringify({
|
|
429
|
+
success: true,
|
|
430
|
+
filePath,
|
|
431
|
+
fileName: widget.title.label,
|
|
432
|
+
contentLength: content.length,
|
|
433
|
+
saved: save,
|
|
434
|
+
isDirty: model.dirty
|
|
435
|
+
});
|
|
305
436
|
}
|
|
306
437
|
});
|
|
307
438
|
}
|
package/src/tools/notebook.ts
CHANGED
|
@@ -3,13 +3,12 @@ import { IDocumentManager } from '@jupyterlab/docmanager';
|
|
|
3
3
|
import { DocumentWidget } from '@jupyterlab/docregistry';
|
|
4
4
|
import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook';
|
|
5
5
|
import { KernelSpec } from '@jupyterlab/services';
|
|
6
|
-
import { CommandRegistry } from '@lumino/commands';
|
|
7
6
|
|
|
8
7
|
import { tool } from '@openai/agents';
|
|
9
8
|
|
|
10
9
|
import { z } from 'zod';
|
|
11
10
|
|
|
12
|
-
import { ITool } from '../tokens';
|
|
11
|
+
import { IDiffManager, ITool } from '../tokens';
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* Find a kernel name that matches the specified language
|
|
@@ -198,6 +197,7 @@ export function createAddCellTool(
|
|
|
198
197
|
.describe('Type of cell to add'),
|
|
199
198
|
position: z
|
|
200
199
|
.enum(['above', 'below'])
|
|
200
|
+
.optional()
|
|
201
201
|
.default('below')
|
|
202
202
|
.describe('Position relative to current cell')
|
|
203
203
|
}),
|
|
@@ -460,8 +460,8 @@ export function createGetCellInfoTool(
|
|
|
460
460
|
*/
|
|
461
461
|
export function createSetCellContentTool(
|
|
462
462
|
docManager: IDocumentManager,
|
|
463
|
-
|
|
464
|
-
|
|
463
|
+
notebookTracker?: INotebookTracker,
|
|
464
|
+
diffManager?: IDiffManager
|
|
465
465
|
): ITool {
|
|
466
466
|
return tool({
|
|
467
467
|
name: 'set_cell_content',
|
|
@@ -581,16 +581,15 @@ export function createSetCellContentTool(
|
|
|
581
581
|
|
|
582
582
|
sharedModel.setSource(content);
|
|
583
583
|
|
|
584
|
-
// Show the cell diff using
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
});
|
|
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
|
+
}
|
|
594
593
|
|
|
595
594
|
return JSON.stringify({
|
|
596
595
|
success: true,
|