@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ReactWidget } from '@jupyterlab/ui-components';
|
|
2
|
+
import { Debouncer } from '@lumino/polling';
|
|
2
3
|
import Add from '@mui/icons-material/Add';
|
|
3
4
|
import Cable from '@mui/icons-material/Cable';
|
|
4
5
|
import CheckCircle from '@mui/icons-material/CheckCircle';
|
|
@@ -10,7 +11,7 @@ import ErrorOutline from '@mui/icons-material/ErrorOutline';
|
|
|
10
11
|
import MoreVert from '@mui/icons-material/MoreVert';
|
|
11
12
|
import Settings from '@mui/icons-material/Settings';
|
|
12
13
|
import { Alert, Box, Button, Card, CardContent, Chip, Dialog, DialogActions, DialogContent, DialogTitle, Divider, FormControl, FormControlLabel, IconButton, InputLabel, List, ListItem, ListItemSecondaryAction, ListItemText, Menu, MenuItem, Select, Switch, Tab, Tabs, TextField, ThemeProvider, Typography, createTheme } from '@mui/material';
|
|
13
|
-
import React, { useEffect, useState } from 'react';
|
|
14
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
14
15
|
import { SECRETS_NAMESPACE, SECRETS_REPLACEMENT } from '../tokens';
|
|
15
16
|
import { ProviderConfigDialog } from './provider-config-dialog';
|
|
16
17
|
/**
|
|
@@ -83,6 +84,10 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
83
84
|
const [editingMCPServer, setEditingMCPServer] = useState();
|
|
84
85
|
const [mcpMenuAnchor, setMcpMenuAnchor] = useState(null);
|
|
85
86
|
const [mcpMenuServerId, setMcpMenuServerId] = useState('');
|
|
87
|
+
const [systemPromptValue, setSystemPromptValue] = useState(config.systemPrompt);
|
|
88
|
+
const systemPromptValueRef = React.useRef(config.systemPrompt);
|
|
89
|
+
const [completionPromptValue, setCompletionPromptValue] = useState(config.completionSystemPrompt);
|
|
90
|
+
const completionPromptValueRef = React.useRef(config.completionSystemPrompt);
|
|
86
91
|
/**
|
|
87
92
|
* Effect to listen for model state changes and update config
|
|
88
93
|
*/
|
|
@@ -129,10 +134,48 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
129
134
|
agentManagerFactory.mcpConnectionChanged.disconnect(onMCPConnectionChanged);
|
|
130
135
|
};
|
|
131
136
|
}, [agentManagerFactory]);
|
|
137
|
+
// Sync local state when config changes externally
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
setSystemPromptValue(config.systemPrompt);
|
|
140
|
+
systemPromptValueRef.current = config.systemPrompt;
|
|
141
|
+
}, [config.systemPrompt]);
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
setCompletionPromptValue(config.completionSystemPrompt);
|
|
144
|
+
completionPromptValueRef.current = config.completionSystemPrompt;
|
|
145
|
+
}, [config.completionSystemPrompt]);
|
|
146
|
+
const promptDebouncer = useMemo(() => new Debouncer(async () => {
|
|
147
|
+
await handleConfigUpdate({
|
|
148
|
+
systemPrompt: systemPromptValueRef.current,
|
|
149
|
+
completionSystemPrompt: completionPromptValueRef.current
|
|
150
|
+
});
|
|
151
|
+
}, 1000), []);
|
|
152
|
+
// Cleanup debouncer on unmount
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
return () => {
|
|
155
|
+
promptDebouncer.dispose();
|
|
156
|
+
};
|
|
157
|
+
}, [promptDebouncer]);
|
|
158
|
+
const handleSystemPromptChange = (value) => {
|
|
159
|
+
setSystemPromptValue(value);
|
|
160
|
+
systemPromptValueRef.current = value;
|
|
161
|
+
void promptDebouncer.invoke();
|
|
162
|
+
};
|
|
163
|
+
const handleCompletionPromptChange = (value) => {
|
|
164
|
+
setCompletionPromptValue(value);
|
|
165
|
+
completionPromptValueRef.current = value;
|
|
166
|
+
void promptDebouncer.invoke();
|
|
167
|
+
};
|
|
132
168
|
const getSecretFromManager = async (provider, fieldName) => {
|
|
133
169
|
const secret = await secretsManager?.get(Private.getToken(), SECRETS_NAMESPACE, `${provider}:${fieldName}`);
|
|
134
170
|
return secret?.value;
|
|
135
171
|
};
|
|
172
|
+
const setSecretToManager = async (provider, fieldName, value) => {
|
|
173
|
+
await secretsManager?.set(Private.getToken(), SECRETS_NAMESPACE, `${provider}:${fieldName}`, {
|
|
174
|
+
namespace: SECRETS_NAMESPACE,
|
|
175
|
+
id: `${provider}:${fieldName}`,
|
|
176
|
+
value
|
|
177
|
+
});
|
|
178
|
+
};
|
|
136
179
|
/**
|
|
137
180
|
* Attach a secrets field to the secrets manager.
|
|
138
181
|
* @param input - the DOm element to attach.
|
|
@@ -188,9 +231,7 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
188
231
|
// Retrieve the API key from the secrets manager if necessary.
|
|
189
232
|
if (model.config.useSecretsManager && secretsManager) {
|
|
190
233
|
provider.apiKey =
|
|
191
|
-
(await getSecretFromManager(provider.provider, 'apiKey')) ??
|
|
192
|
-
provider.apiKey ??
|
|
193
|
-
'';
|
|
234
|
+
(await getSecretFromManager(provider.provider, 'apiKey')) ?? '';
|
|
194
235
|
}
|
|
195
236
|
setEditingProvider(provider);
|
|
196
237
|
setDialogOpen(true);
|
|
@@ -227,6 +268,11 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
227
268
|
if (updates.useSecretsManager !== undefined) {
|
|
228
269
|
if (updates.useSecretsManager) {
|
|
229
270
|
for (const provider of model.config.providers) {
|
|
271
|
+
// if the secrets manager doesn't have the current API key, copy the current
|
|
272
|
+
// one from settings.
|
|
273
|
+
if (!(await getSecretFromManager(provider.provider, 'apiKey'))) {
|
|
274
|
+
setSecretToManager(provider.provider, 'apiKey', provider.apiKey ?? '');
|
|
275
|
+
}
|
|
230
276
|
provider.apiKey = SECRETS_REPLACEMENT;
|
|
231
277
|
await model.updateProvider(provider.id, provider);
|
|
232
278
|
}
|
|
@@ -307,6 +353,7 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
307
353
|
overflow: 'auto',
|
|
308
354
|
p: 2,
|
|
309
355
|
pb: 4,
|
|
356
|
+
boxSizing: 'border-box',
|
|
310
357
|
fontSize: '0.9rem'
|
|
311
358
|
} },
|
|
312
359
|
React.createElement(Box, { sx: { mb: 2, display: 'flex', alignItems: 'center', gap: 2 } },
|
|
@@ -318,11 +365,6 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
318
365
|
React.createElement(Tab, { label: "Behavior" }),
|
|
319
366
|
React.createElement(Tab, { label: "MCP Servers" }))),
|
|
320
367
|
activeTab === 0 && (React.createElement(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 } },
|
|
321
|
-
secretsManager !== undefined && (React.createElement(FormControlLabel, { control: React.createElement(Switch, { checked: config.useSecretsManager, onChange: e => handleConfigUpdate({
|
|
322
|
-
useSecretsManager: e.target.checked
|
|
323
|
-
}), color: "primary", sx: { alignSelf: 'flex-start' } }), label: React.createElement("div", null,
|
|
324
|
-
React.createElement("span", null, "Use the secrets manager to manage API keys"),
|
|
325
|
-
!config.useSecretsManager && (React.createElement(Alert, { severity: "warning", icon: React.createElement(Error, null), sx: { mb: 2 } }, "The secrets will be stored in plain text in settings"))) })),
|
|
326
368
|
config.providers.length > 0 && (React.createElement(Card, { elevation: 2 },
|
|
327
369
|
React.createElement(CardContent, null,
|
|
328
370
|
React.createElement(Typography, { variant: "h6", component: "h2", gutterBottom: true }, "Default Providers"),
|
|
@@ -335,9 +377,9 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
335
377
|
}), color: "primary" }), label: "Use same provider for chat and completions" }),
|
|
336
378
|
!config.useSameProviderForChatAndCompleter && (React.createElement(FormControl, { fullWidth: true },
|
|
337
379
|
React.createElement(InputLabel, null, "Completion Provider"),
|
|
338
|
-
React.createElement(Select, { value: config.activeCompleterProvider || '', label: "Completion Provider", onChange: e => model.setActiveCompleterProvider(e.target.value || undefined) },
|
|
380
|
+
React.createElement(Select, { value: config.activeCompleterProvider || '', label: "Completion Provider", className: "jp-ai-completion-provider-select", onChange: e => model.setActiveCompleterProvider(e.target.value || undefined) },
|
|
339
381
|
React.createElement(MenuItem, { value: "" },
|
|
340
|
-
React.createElement("em", null, "
|
|
382
|
+
React.createElement("em", null, "No completion")),
|
|
341
383
|
config.providers.map(provider => (React.createElement(MenuItem, { key: provider.id, value: provider.id }, provider.name)))))))))),
|
|
342
384
|
React.createElement(Card, { elevation: 2 },
|
|
343
385
|
React.createElement(CardContent, null,
|
|
@@ -398,7 +440,12 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
398
440
|
params.maxTurns !== undefined && (React.createElement(Chip, { label: `Turns: ${params.maxTurns}`, size: "small", variant: "outlined" }))))),
|
|
399
441
|
React.createElement(IconButton, { onClick: e => handleMenuClick(e, provider.id), size: "small" },
|
|
400
442
|
React.createElement(MoreVert, null)))));
|
|
401
|
-
})))))
|
|
443
|
+
}))))),
|
|
444
|
+
secretsManager !== undefined && (React.createElement(FormControlLabel, { control: React.createElement(Switch, { checked: config.useSecretsManager, onChange: e => handleConfigUpdate({
|
|
445
|
+
useSecretsManager: e.target.checked
|
|
446
|
+
}), color: "primary", sx: { alignSelf: 'flex-start' } }), label: React.createElement("div", null,
|
|
447
|
+
React.createElement("span", null, "Use the secrets manager to manage API keys"),
|
|
448
|
+
!config.useSecretsManager && (React.createElement(Alert, { severity: "warning", icon: React.createElement(Error, null), sx: { mb: 2 } }, "The secrets are stored in plain text in settings"))) })))),
|
|
402
449
|
activeTab === 1 && (React.createElement(Card, { elevation: 2 },
|
|
403
450
|
React.createElement(CardContent, null,
|
|
404
451
|
React.createElement(Typography, { variant: "h6", component: "h2", gutterBottom: true }, "Behavior Settings"),
|
|
@@ -418,8 +465,26 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
418
465
|
}), color: "primary" }), label: React.createElement(Box, null,
|
|
419
466
|
React.createElement(Typography, { variant: "body1" }, "Show Token Usage"),
|
|
420
467
|
React.createElement(Typography, { variant: "caption", color: "text.secondary" }, "Display token usage information in the chat toolbar")) }),
|
|
468
|
+
React.createElement(FormControlLabel, { control: React.createElement(Switch, { checked: config.showCellDiff, onChange: e => handleConfigUpdate({
|
|
469
|
+
showCellDiff: e.target.checked
|
|
470
|
+
}), color: "primary" }), label: React.createElement(Box, null,
|
|
471
|
+
React.createElement(Typography, { variant: "body1" }, "Show Cell Diff"),
|
|
472
|
+
React.createElement(Typography, { variant: "caption", color: "text.secondary" }, "Show diff view when AI modifies cell content")) }),
|
|
473
|
+
config.showCellDiff && (React.createElement(FormControl, { sx: { ml: 4 } },
|
|
474
|
+
React.createElement(InputLabel, null, "Diff Display Mode"),
|
|
475
|
+
React.createElement(Select, { value: config.diffDisplayMode, label: "Diff Display Mode", onChange: e => handleConfigUpdate({
|
|
476
|
+
diffDisplayMode: e.target.value
|
|
477
|
+
}) },
|
|
478
|
+
React.createElement(MenuItem, { value: "split" }, "Split View"),
|
|
479
|
+
React.createElement(MenuItem, { value: "unified" }, "Unified View")))),
|
|
480
|
+
React.createElement(FormControlLabel, { control: React.createElement(Switch, { checked: config.showFileDiff, onChange: e => handleConfigUpdate({
|
|
481
|
+
showFileDiff: e.target.checked
|
|
482
|
+
}), color: "primary" }), label: React.createElement(Box, null,
|
|
483
|
+
React.createElement(Typography, { variant: "body1" }, "Show File Diff"),
|
|
484
|
+
React.createElement(Typography, { variant: "caption", color: "text.secondary" }, "Show diff view when AI modifies file content")) }),
|
|
421
485
|
React.createElement(Divider, { sx: { my: 1 } }),
|
|
422
|
-
React.createElement(TextField, { fullWidth: true, multiline: true, rows: 3, label: "System Prompt", value:
|
|
486
|
+
React.createElement(TextField, { fullWidth: true, multiline: true, rows: 3, label: "System Prompt", value: systemPromptValue, onChange: e => handleSystemPromptChange(e.target.value), placeholder: "Define the AI's behavior and personality...", helperText: "Instructions that define how the AI should behave and respond" }),
|
|
487
|
+
React.createElement(TextField, { fullWidth: true, multiline: true, rows: 3, label: "Completion System Prompt", value: completionPromptValue, onChange: e => handleCompletionPromptChange(e.target.value), placeholder: "Define how the AI should generate code completions...", helperText: "Instructions that define how the AI should generate code completions" }),
|
|
423
488
|
React.createElement(Divider, { sx: { my: 2 } }),
|
|
424
489
|
React.createElement(Box, null,
|
|
425
490
|
React.createElement(Typography, { variant: "body1", gutterBottom: true }, "Commands Requiring Approval"),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ExpandMore from '@mui/icons-material/ExpandMore';
|
|
2
2
|
import Visibility from '@mui/icons-material/Visibility';
|
|
3
3
|
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
|
4
|
-
import { Accordion, AccordionDetails, AccordionSummary, Box, Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, FormControlLabel, IconButton, InputAdornment, InputLabel, MenuItem, Select, Slider, Switch, TextField, Typography } from '@mui/material';
|
|
4
|
+
import { Accordion, AccordionDetails, AccordionSummary, Autocomplete, Box, Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, FormControlLabel, IconButton, InputAdornment, InputLabel, MenuItem, Select, Slider, Switch, TextField, Typography } from '@mui/material';
|
|
5
5
|
import React from 'react';
|
|
6
6
|
/**
|
|
7
7
|
* Default parameter values for provider configuration
|
|
@@ -28,9 +28,10 @@ export const ProviderConfigDialog = ({ open, onClose, onSave, initialConfig, mod
|
|
|
28
28
|
label: info.name,
|
|
29
29
|
models: info.defaultModels,
|
|
30
30
|
apiKeyRequirement: info.apiKeyRequirement,
|
|
31
|
-
allowCustomModel: id === '
|
|
31
|
+
allowCustomModel: id === 'generic', // Generic allows custom models
|
|
32
32
|
supportsBaseURL: info.supportsBaseURL,
|
|
33
|
-
description: info.description
|
|
33
|
+
description: info.description,
|
|
34
|
+
baseUrls: info.baseUrls
|
|
34
35
|
};
|
|
35
36
|
});
|
|
36
37
|
}, [providerRegistry]);
|
|
@@ -124,11 +125,17 @@ export const ProviderConfigDialog = ({ open, onClose, onSave, initialConfig, mod
|
|
|
124
125
|
endAdornment: (React.createElement(InputAdornment, { position: "end" },
|
|
125
126
|
React.createElement(IconButton, { onClick: () => setShowApiKey(!showApiKey), edge: "end" }, showApiKey ? React.createElement(VisibilityOff, null) : React.createElement(Visibility, null))))
|
|
126
127
|
} })),
|
|
127
|
-
selectedProvider?.supportsBaseURL && (React.createElement(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
selectedProvider?.supportsBaseURL && (React.createElement(Autocomplete, { freeSolo: true, fullWidth: true, options: (selectedProvider.baseUrls ?? []).map(option => option.url), value: baseURL || '', onChange: (_, value) => {
|
|
129
|
+
if (value && typeof value === 'string') {
|
|
130
|
+
setBaseURL(value);
|
|
131
|
+
}
|
|
132
|
+
}, inputValue: baseURL || '', renderOption: (props, option) => {
|
|
133
|
+
const urlOption = (selectedProvider.baseUrls ?? []).find(u => u.url === option);
|
|
134
|
+
return (React.createElement(Box, { component: "li", ...props, key: option },
|
|
135
|
+
React.createElement(Box, null,
|
|
136
|
+
React.createElement(Typography, { variant: "body2" }, option),
|
|
137
|
+
urlOption?.description && (React.createElement(Typography, { variant: "caption", color: "text.secondary" }, urlOption.description)))));
|
|
138
|
+
}, renderInput: params => (React.createElement(TextField, { ...params, fullWidth: true, label: "Base URL", placeholder: "https://api.example.com/v1", onChange: e => setBaseURL(e.target.value) })), clearOnBlur: false })),
|
|
132
139
|
React.createElement(Accordion, { expanded: expandedAdvanced, onChange: (_, isExpanded) => setExpandedAdvanced(isExpanded), sx: {
|
|
133
140
|
mt: 2,
|
|
134
141
|
bgcolor: 'transparent',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyterlite/ai",
|
|
3
|
-
"version": "0.9.0
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "AI code completions and chat for JupyterLite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -54,10 +54,11 @@
|
|
|
54
54
|
"watch:labextension": "jupyter labextension watch ."
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@ai-sdk/anthropic": "^2.0.
|
|
57
|
+
"@ai-sdk/anthropic": "^2.0.30",
|
|
58
58
|
"@ai-sdk/google": "^2.0.19",
|
|
59
59
|
"@ai-sdk/mistral": "^2.0.17",
|
|
60
60
|
"@ai-sdk/openai": "^2.0.44",
|
|
61
|
+
"@ai-sdk/openai-compatible": "^1.0.26",
|
|
61
62
|
"@jupyter/chat": "^0.18.2",
|
|
62
63
|
"@jupyterlab/application": "^4.0.0",
|
|
63
64
|
"@jupyterlab/apputils": "^4.5.6",
|
|
@@ -71,11 +72,13 @@
|
|
|
71
72
|
"@jupyterlab/rendermime": "^4.4.6",
|
|
72
73
|
"@jupyterlab/services": "^7.4.6",
|
|
73
74
|
"@jupyterlab/settingregistry": "^4.0.0",
|
|
75
|
+
"@jupyterlab/statusbar": "^4.4.6",
|
|
74
76
|
"@jupyterlab/ui-components": "^4.4.6",
|
|
75
77
|
"@lumino/commands": "^2.3.2",
|
|
76
78
|
"@lumino/coreutils": "^2.2.1",
|
|
77
79
|
"@lumino/disposable": "^2.1.4",
|
|
78
80
|
"@lumino/messaging": "^2.0.3",
|
|
81
|
+
"@lumino/polling": "^2.1.4",
|
|
79
82
|
"@lumino/signaling": "^2.1.4",
|
|
80
83
|
"@lumino/widgets": "^2.7.1",
|
|
81
84
|
"@modelcontextprotocol/sdk": "^1.19.1",
|
|
@@ -85,7 +88,6 @@
|
|
|
85
88
|
"@openai/agents-extensions": "^0.1.5",
|
|
86
89
|
"ai": "^5.0.60",
|
|
87
90
|
"jupyter-secrets-manager": "^0.4.0",
|
|
88
|
-
"ollama-ai-provider-v2": "^1.4.1",
|
|
89
91
|
"zod": "^3.25.76"
|
|
90
92
|
},
|
|
91
93
|
"devDependencies": {
|
|
@@ -163,6 +163,31 @@
|
|
|
163
163
|
"description": "Instructions that define how the AI should behave and respond",
|
|
164
164
|
"type": "string",
|
|
165
165
|
"default": "You are Jupyternaut, an AI coding assistant built specifically for the JupyterLab environment.\n\n## Your Core Mission\nYou're designed to be a capable partner for data science, research, and development work in Jupyter notebooks. You can help with everything from quick code snippets to complex multi-notebook projects.\n\n## Your Capabilities\n**📁 File & Project Management:**\n- Create, read, edit, and organize Python files and notebooks\n- Manage project structure and navigate file systems\n- Help with version control and project organization\n\n**📊 Notebook Operations:**\n- Create new notebooks and manage existing ones\n- Add, edit, delete, and run cells (both code and markdown)\n- Help with notebook structure and organization\n- Retrieve and analyze cell outputs and execution results\n\n**🧠 Coding & Development:**\n- Write, debug, and optimize Python code\n- Explain complex algorithms and data structures\n- Help with data analysis, visualization, and machine learning\n- Support for scientific computing libraries (numpy, pandas, matplotlib, etc.)\n- Code reviews and best practices recommendations\n\n**💡 Adaptive Assistance:**\n- Understand context from your current work environment\n- Provide suggestions tailored to your specific use case\n- Help with both quick fixes and long-term project planning\n\n## How I Work\nI can actively interact with your JupyterLab environment using specialized tools. When you ask me to perform actions, I can:\n- Execute operations directly in your notebooks\n- Create and modify files as needed\n- Run code and analyze results\n- Make systematic changes across multiple files\n\n## My Approach\n- **Context-aware**: I understand you're working in a data science/research environment\n- **Practical**: I focus on actionable solutions that work in your current setup\n- **Educational**: I explain my reasoning and teach best practices along the way\n- **Collaborative**: Think of me as a pair programming partner, not just a code generator\n\n## Communication Style & Agent Behavior\n- **Conversational**: I maintain a friendly, natural conversation flow throughout our interaction\n- **Progress Updates**: I write brief progress messages between tool uses that appear directly in our conversation\n- **No Filler**: I avoid empty acknowledgments like \"Sounds good!\" or \"Okay, I will...\" - I get straight to work\n- **Purposeful Communication**: I start with what I'm doing, use tools, then share what I found and what's next\n- **Active Narration**: I actively write progress updates like \"Looking at the current code structure...\" or \"Found the issue in the notebook...\" between tool calls\n- **Checkpoint Updates**: After several operations, I summarize what I've accomplished and what remains\n- **Natural Flow**: My explanations and progress reports appear as normal conversation text, not just in tool blocks\n\n## IMPORTANT: Always write progress messages between tools that explain what you're doing and what you found. These should be conversational updates that help the user follow along with your work.\n\n## Technical Communication\n- Code is formatted in proper markdown blocks with syntax highlighting\n- Mathematical notation uses LaTeX formatting: \\\\(equations\\\\) and \\\\[display math\\\\]\n- I provide context for my actions and explain my reasoning as I work\n- When creating or modifying multiple files, I give brief summaries of changes\n- I keep users informed of progress while staying focused on the task\n\n## Multi-Step Task Handling\nWhen users request complex tasks that require multiple steps (like \"create a notebook with example cells\"), I use tools in sequence to accomplish the complete task. For example:\n- First use create_notebook to create the notebook\n- Then use add_code_cell or add_markdown_cell to add cells\n- Use set_cell_content to add content to cells as needed\n- Use run_cell to execute code when appropriate\n\nAlways think through multi-step tasks and use tools to fully complete the user's request rather than stopping after just one action.\n\nReady to help you build something great! What are you working on?"
|
|
166
|
+
},
|
|
167
|
+
"completionSystemPrompt": {
|
|
168
|
+
"title": "Completion System Prompt",
|
|
169
|
+
"description": "Instructions that define how the AI should generate code completions",
|
|
170
|
+
"type": "string",
|
|
171
|
+
"default": "You are an AI code completion assistant. Complete the given code fragment with appropriate code.\nRules:\n- Return only the completion text, no explanations or comments\n- Do not include code block markers (``` or similar)\n- Make completions contextually relevant to the surrounding code and notebook context\n- Follow the language-specific conventions and style guidelines for the detected programming language\n- Keep completions concise but functional\n- Do not repeat the existing code that comes before the cursor\n- Use variables, imports, functions, and other definitions from previous notebook cells when relevant"
|
|
172
|
+
},
|
|
173
|
+
"showCellDiff": {
|
|
174
|
+
"title": "Show Cell Diff",
|
|
175
|
+
"description": "Show diff view when AI modifies cell content",
|
|
176
|
+
"type": "boolean",
|
|
177
|
+
"default": true
|
|
178
|
+
},
|
|
179
|
+
"showFileDiff": {
|
|
180
|
+
"title": "Show File Diff",
|
|
181
|
+
"description": "Show diff view when AI modifies file content",
|
|
182
|
+
"type": "boolean",
|
|
183
|
+
"default": true
|
|
184
|
+
},
|
|
185
|
+
"diffDisplayMode": {
|
|
186
|
+
"title": "Diff Display Mode",
|
|
187
|
+
"description": "How to display cell diffs (split or unified view)",
|
|
188
|
+
"type": "string",
|
|
189
|
+
"enum": ["split", "unified"],
|
|
190
|
+
"default": "split"
|
|
166
191
|
}
|
|
167
192
|
},
|
|
168
193
|
"additionalProperties": false
|
package/src/agent.ts
CHANGED
|
@@ -184,18 +184,18 @@ export interface IAgentEventTypeMap {
|
|
|
184
184
|
tool_call_start: {
|
|
185
185
|
callId: string;
|
|
186
186
|
toolName: string;
|
|
187
|
-
input:
|
|
187
|
+
input: string;
|
|
188
188
|
};
|
|
189
189
|
tool_call_complete: {
|
|
190
190
|
callId: string;
|
|
191
191
|
toolName: string;
|
|
192
|
-
output:
|
|
192
|
+
output: string;
|
|
193
193
|
isError: boolean;
|
|
194
194
|
};
|
|
195
195
|
tool_approval_required: {
|
|
196
196
|
interruptionId: string;
|
|
197
197
|
toolName: string;
|
|
198
|
-
toolInput:
|
|
198
|
+
toolInput: string;
|
|
199
199
|
callId?: string;
|
|
200
200
|
};
|
|
201
201
|
grouped_approval_required: {
|
|
@@ -203,7 +203,7 @@ export interface IAgentEventTypeMap {
|
|
|
203
203
|
approvals: Array<{
|
|
204
204
|
interruptionId: string;
|
|
205
205
|
toolName: string;
|
|
206
|
-
toolInput:
|
|
206
|
+
toolInput: string;
|
|
207
207
|
}>;
|
|
208
208
|
};
|
|
209
209
|
error: {
|
|
@@ -718,6 +718,22 @@ export class AgentManager {
|
|
|
718
718
|
}
|
|
719
719
|
}
|
|
720
720
|
|
|
721
|
+
/**
|
|
722
|
+
* Formats tool input for display by pretty-printing JSON strings.
|
|
723
|
+
* @param input The tool input string to format
|
|
724
|
+
* @returns Pretty-printed JSON string
|
|
725
|
+
*/
|
|
726
|
+
private _formatToolInput(input: string): string {
|
|
727
|
+
try {
|
|
728
|
+
// Parse and re-stringify with formatting
|
|
729
|
+
const parsed = JSON.parse(input);
|
|
730
|
+
return JSON.stringify(parsed, null, 2);
|
|
731
|
+
} catch {
|
|
732
|
+
// If parsing fails, return the string as-is
|
|
733
|
+
return input;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
721
737
|
/**
|
|
722
738
|
* Handles the start of a tool call from the model event.
|
|
723
739
|
* @param modelEvent The model event containing tool call information
|
|
@@ -726,19 +742,13 @@ export class AgentManager {
|
|
|
726
742
|
const toolCallId = modelEvent.toolCallId;
|
|
727
743
|
const toolName = modelEvent.toolName;
|
|
728
744
|
const toolInput = modelEvent.input;
|
|
729
|
-
let parsedToolInput;
|
|
730
|
-
try {
|
|
731
|
-
parsedToolInput = JSON.parse(toolInput);
|
|
732
|
-
} catch (error) {
|
|
733
|
-
parsedToolInput = {};
|
|
734
|
-
}
|
|
735
745
|
|
|
736
746
|
this._agentEvent.emit({
|
|
737
747
|
type: 'tool_call_start',
|
|
738
748
|
data: {
|
|
739
749
|
callId: toolCallId,
|
|
740
750
|
toolName,
|
|
741
|
-
input:
|
|
751
|
+
input: this._formatToolInput(toolInput)
|
|
742
752
|
}
|
|
743
753
|
});
|
|
744
754
|
}
|
|
@@ -758,7 +768,7 @@ export class AgentManager {
|
|
|
758
768
|
|
|
759
769
|
const isError =
|
|
760
770
|
toolCallOutput.rawItem.type === 'function_call_result' &&
|
|
761
|
-
|
|
771
|
+
toolCallOutput.rawItem.status === 'incomplete';
|
|
762
772
|
|
|
763
773
|
const toolName =
|
|
764
774
|
toolCallOutput.rawItem.type === 'function_call_result'
|
|
@@ -783,10 +793,13 @@ export class AgentManager {
|
|
|
783
793
|
private async _handleSingleToolApproval(
|
|
784
794
|
interruption: RunToolApprovalItem
|
|
785
795
|
): Promise<void> {
|
|
786
|
-
const toolName =
|
|
787
|
-
const toolInput =
|
|
796
|
+
const toolName = interruption.rawItem.name || 'Unknown Tool';
|
|
797
|
+
const toolInput = interruption.rawItem.arguments || '{}';
|
|
788
798
|
const interruptionId = `int-${Date.now()}-${Math.random()}`;
|
|
789
|
-
const callId =
|
|
799
|
+
const callId =
|
|
800
|
+
interruption.rawItem.type === 'function_call'
|
|
801
|
+
? interruption.rawItem.callId
|
|
802
|
+
: undefined;
|
|
790
803
|
|
|
791
804
|
this._pendingApprovals.set(interruptionId, { interruption });
|
|
792
805
|
|
|
@@ -795,7 +808,7 @@ export class AgentManager {
|
|
|
795
808
|
data: {
|
|
796
809
|
interruptionId,
|
|
797
810
|
toolName,
|
|
798
|
-
toolInput,
|
|
811
|
+
toolInput: this._formatToolInput(toolInput),
|
|
799
812
|
callId
|
|
800
813
|
}
|
|
801
814
|
});
|
|
@@ -810,8 +823,8 @@ export class AgentManager {
|
|
|
810
823
|
): Promise<void> {
|
|
811
824
|
const groupId = `group-${Date.now()}-${Math.random()}`;
|
|
812
825
|
const approvals = interruptions.map(interruption => {
|
|
813
|
-
const toolName =
|
|
814
|
-
const toolInput =
|
|
826
|
+
const toolName = interruption.rawItem.name || 'Unknown Tool';
|
|
827
|
+
const toolInput = interruption.rawItem.arguments || '{}';
|
|
815
828
|
const interruptionId = `int-${Date.now()}-${Math.random()}`;
|
|
816
829
|
|
|
817
830
|
this._pendingApprovals.set(interruptionId, { interruption, groupId });
|
|
@@ -819,7 +832,7 @@ export class AgentManager {
|
|
|
819
832
|
return {
|
|
820
833
|
interruptionId,
|
|
821
834
|
toolName,
|
|
822
|
-
toolInput
|
|
835
|
+
toolInput: this._formatToolInput(toolInput)
|
|
823
836
|
};
|
|
824
837
|
});
|
|
825
838
|
|
|
@@ -921,7 +934,9 @@ Guidelines:
|
|
|
921
934
|
- Use natural, conversational tone throughout
|
|
922
935
|
|
|
923
936
|
COMMAND DISCOVERY:
|
|
924
|
-
- When you want to execute JupyterLab commands, ALWAYS use the 'discover_commands' tool first to find available commands and their metadata.
|
|
937
|
+
- When you want to execute JupyterLab commands, ALWAYS use the 'discover_commands' tool first to find available commands and their metadata, with the optional query parameter.
|
|
938
|
+
- The query should typically be a single word, e.g., 'terminal', 'notebook', 'cell', 'file', 'edit', 'view', 'run', etc, to find relevant commands.
|
|
939
|
+
- If searching with a query does not yield the desired command, try again with a different query or use an empty query to list all commands.
|
|
925
940
|
- This ensures you have complete information about command IDs, descriptions, and required arguments before attempting to execute them. Only after discovering the available commands should you use the 'execute_command' tool with the correct command ID and arguments.
|
|
926
941
|
|
|
927
942
|
TOOL SELECTION GUIDELINES:
|