@jupyterlite/ai 0.13.0 → 0.15.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/lib/agent.d.ts +28 -102
- package/lib/agent.js +150 -22
- package/lib/chat-model-handler.d.ts +9 -11
- package/lib/chat-model-handler.js +9 -4
- package/lib/chat-model.d.ts +84 -13
- package/lib/chat-model.js +208 -136
- package/lib/completion/completion-provider.d.ts +2 -3
- package/lib/components/completion-status.d.ts +2 -2
- package/lib/components/model-select.d.ts +3 -3
- package/lib/components/save-button.d.ts +31 -0
- package/lib/components/save-button.js +41 -0
- package/lib/components/token-usage-display.d.ts +2 -3
- package/lib/components/tool-select.d.ts +3 -4
- package/lib/diff-manager.d.ts +2 -3
- package/lib/index.d.ts +2 -4
- package/lib/index.js +306 -78
- package/lib/models/settings-model.d.ts +11 -53
- package/lib/models/settings-model.js +37 -22
- package/lib/providers/built-in-providers.js +29 -54
- package/lib/tokens.d.ts +351 -26
- package/lib/tokens.js +11 -6
- package/lib/tools/commands.d.ts +2 -3
- package/lib/tools/commands.js +58 -20
- package/lib/widgets/ai-settings.d.ts +6 -13
- package/lib/widgets/ai-settings.js +18 -48
- package/lib/widgets/main-area-chat.d.ts +2 -3
- package/lib/widgets/main-area-chat.js +9 -9
- package/lib/widgets/provider-config-dialog.d.ts +1 -2
- package/lib/widgets/provider-config-dialog.js +21 -37
- package/package.json +15 -9
- package/schema/settings-model.json +7 -1
- package/src/agent.ts +211 -149
- package/src/chat-model-handler.ts +25 -21
- package/src/chat-model.ts +304 -196
- package/src/completion/completion-provider.ts +7 -4
- package/src/components/completion-status.tsx +3 -3
- package/src/components/model-select.tsx +4 -3
- package/src/components/save-button.tsx +84 -0
- package/src/components/token-usage-display.tsx +2 -3
- package/src/components/tool-select.tsx +10 -4
- package/src/diff-manager.ts +4 -4
- package/src/index.ts +451 -151
- package/src/models/settings-model.ts +45 -88
- package/src/providers/built-in-providers.ts +29 -54
- package/src/tokens.ts +418 -35
- package/src/tools/commands.ts +97 -32
- package/src/widgets/ai-settings.tsx +45 -83
- package/src/widgets/main-area-chat.ts +15 -12
- package/src/widgets/provider-config-dialog.tsx +59 -64
- package/style/base.css +17 -195
- package/lib/approval-buttons.d.ts +0 -49
- package/lib/approval-buttons.js +0 -79
- package/src/approval-buttons.ts +0 -115
|
@@ -13,7 +13,7 @@ import MoreVert from '@mui/icons-material/MoreVert';
|
|
|
13
13
|
import Settings from '@mui/icons-material/Settings';
|
|
14
14
|
import { Alert, Box, Button, Card, CardContent, Chip, Dialog, DialogActions, DialogContent, DialogTitle, Divider, FormControl, FormControlLabel, IconButton, InputLabel, List, ListItem, ListItemText, Menu, MenuItem, Select, Switch, Tab, Tabs, TextField, ThemeProvider, Tooltip, Typography, createTheme } from '@mui/material';
|
|
15
15
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
16
|
-
import {
|
|
16
|
+
import { SECRETS_REPLACEMENT } from '../tokens';
|
|
17
17
|
import { ProviderConfigDialog } from './provider-config-dialog';
|
|
18
18
|
/**
|
|
19
19
|
* Create a theme that uses IThemeManager to detect theme
|
|
@@ -41,21 +41,20 @@ export class AISettingsWidget extends ReactWidget {
|
|
|
41
41
|
*/
|
|
42
42
|
constructor(options) {
|
|
43
43
|
super();
|
|
44
|
-
Private.setToken(options.token);
|
|
45
44
|
this._settingsModel = options.settingsModel;
|
|
46
45
|
this._agentManagerFactory = options.agentManagerFactory;
|
|
47
46
|
this._themeManager = options.themeManager;
|
|
48
47
|
this._providerRegistry = options.providerRegistry;
|
|
49
|
-
this.
|
|
48
|
+
this._secretsAccess = options.secretsAccess;
|
|
50
49
|
this._trans = options.trans;
|
|
51
50
|
this.id = 'jupyterlite-ai-settings';
|
|
52
51
|
this.title.label = this._trans.__('AI Settings');
|
|
53
52
|
this.title.caption = this._trans.__('Configure AI providers and behavior');
|
|
54
53
|
this.title.closable = true;
|
|
55
54
|
// Disable the secrets manager if the token is empty.
|
|
56
|
-
if (!options.
|
|
55
|
+
if (!options.secretsAccess.isAvailable) {
|
|
57
56
|
this._settingsModel.updateConfig({ useSecretsManager: false });
|
|
58
|
-
this.
|
|
57
|
+
this._secretsAccess = undefined;
|
|
59
58
|
}
|
|
60
59
|
}
|
|
61
60
|
/**
|
|
@@ -63,13 +62,13 @@ export class AISettingsWidget extends ReactWidget {
|
|
|
63
62
|
* @returns A React element containing the AI settings interface
|
|
64
63
|
*/
|
|
65
64
|
render() {
|
|
66
|
-
return (React.createElement(AISettingsComponent, { model: this._settingsModel, agentManagerFactory: this._agentManagerFactory, themeManager: this._themeManager, providerRegistry: this._providerRegistry,
|
|
65
|
+
return (React.createElement(AISettingsComponent, { model: this._settingsModel, agentManagerFactory: this._agentManagerFactory, themeManager: this._themeManager, providerRegistry: this._providerRegistry, secretsAccess: this._secretsAccess, trans: this._trans }));
|
|
67
66
|
}
|
|
68
67
|
_settingsModel;
|
|
69
68
|
_agentManagerFactory;
|
|
70
69
|
_themeManager;
|
|
71
70
|
_providerRegistry;
|
|
72
|
-
|
|
71
|
+
_secretsAccess;
|
|
73
72
|
_trans;
|
|
74
73
|
}
|
|
75
74
|
/**
|
|
@@ -77,7 +76,7 @@ export class AISettingsWidget extends ReactWidget {
|
|
|
77
76
|
* @param props - Component props containing models and theme manager
|
|
78
77
|
* @returns A React component for AI settings configuration
|
|
79
78
|
*/
|
|
80
|
-
const AISettingsComponent = ({ model, agentManagerFactory, themeManager, providerRegistry,
|
|
79
|
+
const AISettingsComponent = ({ model, agentManagerFactory, themeManager, providerRegistry, secretsAccess, trans }) => {
|
|
81
80
|
if (!model) {
|
|
82
81
|
return React.createElement("div", null, trans.__('Settings model not available'));
|
|
83
82
|
}
|
|
@@ -174,23 +173,10 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
174
173
|
void promptDebouncer.invoke();
|
|
175
174
|
};
|
|
176
175
|
const getSecretFromManager = async (provider, fieldName) => {
|
|
177
|
-
|
|
178
|
-
if (!token) {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
const secret = await secretsManager?.get(token, SECRETS_NAMESPACE, `${provider}:${fieldName}`);
|
|
182
|
-
return secret?.value;
|
|
176
|
+
return secretsAccess?.get(`${provider}:${fieldName}`);
|
|
183
177
|
};
|
|
184
178
|
const setSecretToManager = async (provider, fieldName, value) => {
|
|
185
|
-
|
|
186
|
-
if (!token) {
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
await secretsManager?.set(token, SECRETS_NAMESPACE, `${provider}:${fieldName}`, {
|
|
190
|
-
namespace: SECRETS_NAMESPACE,
|
|
191
|
-
id: `${provider}:${fieldName}`,
|
|
192
|
-
value
|
|
193
|
-
});
|
|
179
|
+
await secretsAccess?.set(`${provider}:${fieldName}`, value);
|
|
194
180
|
};
|
|
195
181
|
/**
|
|
196
182
|
* Attach a secrets field to the secrets manager.
|
|
@@ -199,14 +185,10 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
199
185
|
* @param fieldName - the name of the field.
|
|
200
186
|
*/
|
|
201
187
|
const handleSecretField = async (input, provider, fieldName) => {
|
|
202
|
-
if (!(model.config.useSecretsManager &&
|
|
188
|
+
if (!(model.config.useSecretsManager && secretsAccess?.isAvailable)) {
|
|
203
189
|
return;
|
|
204
190
|
}
|
|
205
|
-
|
|
206
|
-
if (!token) {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
await secretsManager?.attach(token, SECRETS_NAMESPACE, `${provider}:${fieldName}`, input);
|
|
191
|
+
await secretsAccess.attach(`${provider}:${fieldName}`, input);
|
|
210
192
|
};
|
|
211
193
|
/**
|
|
212
194
|
* Handle adding a new AI provider
|
|
@@ -214,7 +196,7 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
214
196
|
*/
|
|
215
197
|
const handleAddProvider = async (providerConfig) => {
|
|
216
198
|
if (model.config.useSecretsManager &&
|
|
217
|
-
|
|
199
|
+
secretsAccess?.isAvailable &&
|
|
218
200
|
providerConfig.apiKey) {
|
|
219
201
|
providerConfig.apiKey = SECRETS_REPLACEMENT;
|
|
220
202
|
}
|
|
@@ -227,7 +209,7 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
227
209
|
const handleEditProvider = async (providerConfig) => {
|
|
228
210
|
if (editingProvider) {
|
|
229
211
|
if (model.config.useSecretsManager &&
|
|
230
|
-
|
|
212
|
+
secretsAccess?.isAvailable &&
|
|
231
213
|
providerConfig.apiKey) {
|
|
232
214
|
providerConfig.apiKey = SECRETS_REPLACEMENT;
|
|
233
215
|
}
|
|
@@ -249,7 +231,7 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
249
231
|
*/
|
|
250
232
|
const openEditDialog = async (provider) => {
|
|
251
233
|
// Retrieve the API key from the secrets manager if necessary.
|
|
252
|
-
if (model.config.useSecretsManager &&
|
|
234
|
+
if (model.config.useSecretsManager && secretsAccess?.isAvailable) {
|
|
253
235
|
provider.apiKey =
|
|
254
236
|
(await getSecretFromManager(provider.provider, 'apiKey')) ?? '';
|
|
255
237
|
}
|
|
@@ -477,7 +459,7 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
477
459
|
React.createElement(IconButton, { onClick: e => handleMenuClick(e, provider.id), size: "small" },
|
|
478
460
|
React.createElement(MoreVert, null)))));
|
|
479
461
|
}))))),
|
|
480
|
-
|
|
462
|
+
secretsAccess?.isAvailable && (React.createElement(FormControlLabel, { control: React.createElement(Switch, { checked: config.useSecretsManager, onChange: e => handleConfigUpdate({
|
|
481
463
|
useSecretsManager: e.target.checked
|
|
482
464
|
}), color: "primary", sx: { alignSelf: 'flex-start' } }), label: React.createElement("div", null,
|
|
483
465
|
React.createElement("span", null, trans.__('Use the secrets manager to manage API keys')),
|
|
@@ -518,6 +500,9 @@ const AISettingsComponent = ({ model, agentManagerFactory, themeManager, provide
|
|
|
518
500
|
}), color: "primary" }), label: React.createElement(Box, null,
|
|
519
501
|
React.createElement(Typography, { variant: "body1" }, trans.__('Show File Diff')),
|
|
520
502
|
React.createElement(Typography, { variant: "caption", color: "text.secondary" }, trans.__('Show diff view when AI modifies file content'))) }),
|
|
503
|
+
React.createElement(TextField, { fullWidth: true, label: trans.__('Chat Backup Directory'), value: config.chatBackupDirectory, onChange: e => handleConfigUpdate({
|
|
504
|
+
chatBackupDirectory: e.target.value
|
|
505
|
+
}), helperText: trans.__('Directory where chat history backups are saved (relative to the server root)') }),
|
|
521
506
|
React.createElement(Divider, { sx: { my: 1 } }),
|
|
522
507
|
React.createElement(TextField, { fullWidth: true, multiline: true, rows: 3, label: trans.__('System Prompt'), value: systemPromptValue, onChange: e => handleSystemPromptChange(e.target.value), placeholder: trans.__("Define the AI's behavior and personality..."), helperText: trans.__('Instructions that define how the AI should behave and respond') }),
|
|
523
508
|
React.createElement(TextField, { fullWidth: true, multiline: true, rows: 3, label: trans.__('Completion System Prompt'), value: completionPromptValue, onChange: e => handleCompletionPromptChange(e.target.value), placeholder: trans.__('Define how the AI should generate code completions...'), helperText: trans.__('Instructions that define how the AI should generate code completions') }),
|
|
@@ -761,18 +746,3 @@ const MCPServerDialog = ({ open, onClose, onSave, initialConfig, mode, trans })
|
|
|
761
746
|
React.createElement(Button, { onClick: onClose }, trans.__('Cancel')),
|
|
762
747
|
React.createElement(Button, { onClick: handleSave, variant: "contained", disabled: !canSave }, mode === 'add' ? trans.__('Add') : trans.__('Save')))));
|
|
763
748
|
};
|
|
764
|
-
var Private;
|
|
765
|
-
(function (Private) {
|
|
766
|
-
/**
|
|
767
|
-
* The token to use with the secrets manager, setter and getter.
|
|
768
|
-
*/
|
|
769
|
-
let secretsToken;
|
|
770
|
-
function setToken(value) {
|
|
771
|
-
secretsToken = value;
|
|
772
|
-
}
|
|
773
|
-
Private.setToken = setToken;
|
|
774
|
-
function getToken() {
|
|
775
|
-
return secretsToken;
|
|
776
|
-
}
|
|
777
|
-
Private.getToken = getToken;
|
|
778
|
-
})(Private || (Private = {}));
|
|
@@ -3,11 +3,11 @@ import { MainAreaWidget } from '@jupyterlab/apputils';
|
|
|
3
3
|
import type { TranslationBundle } from '@jupyterlab/translation';
|
|
4
4
|
import { CommandRegistry } from '@lumino/commands';
|
|
5
5
|
import { AIChatModel } from '../chat-model';
|
|
6
|
-
import {
|
|
6
|
+
import { type IAISettingsModel } from '../tokens';
|
|
7
7
|
export declare namespace MainAreaChat {
|
|
8
8
|
interface IOptions extends MainAreaWidget.IOptions<ChatWidget> {
|
|
9
9
|
commands: CommandRegistry;
|
|
10
|
-
settingsModel:
|
|
10
|
+
settingsModel: IAISettingsModel;
|
|
11
11
|
trans: TranslationBundle;
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -26,6 +26,5 @@ export declare class MainAreaChat extends MainAreaWidget<ChatWidget> {
|
|
|
26
26
|
*/
|
|
27
27
|
get area(): string | undefined;
|
|
28
28
|
private _writersChanged;
|
|
29
|
-
private _approvalButtons;
|
|
30
29
|
private _outputAreaCompat;
|
|
31
30
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CommandToolbarButton, MainAreaWidget } from '@jupyterlab/apputils';
|
|
2
2
|
import { launchIcon } from '@jupyterlab/ui-components';
|
|
3
|
-
import {
|
|
3
|
+
import { SaveComponentWidget } from '../components/save-button';
|
|
4
4
|
import { TokenUsageWidget } from '../components/token-usage-display';
|
|
5
5
|
import { RenderedMessageOutputAreaCompat } from '../rendered-message-outputarea';
|
|
6
6
|
import { CommandIds } from '../tokens';
|
|
@@ -12,7 +12,7 @@ export class MainAreaChat extends MainAreaWidget {
|
|
|
12
12
|
super(options);
|
|
13
13
|
this.title.label = this.content.model.name;
|
|
14
14
|
const { trans } = options;
|
|
15
|
-
//
|
|
15
|
+
// Move to side button.
|
|
16
16
|
this.toolbar.addItem('moveToSide', new CommandToolbarButton({
|
|
17
17
|
commands: options.commands,
|
|
18
18
|
id: CommandIds.moveChat,
|
|
@@ -22,6 +22,13 @@ export class MainAreaChat extends MainAreaWidget {
|
|
|
22
22
|
},
|
|
23
23
|
icon: launchIcon
|
|
24
24
|
}));
|
|
25
|
+
if (this.model.saveAvailable) {
|
|
26
|
+
// Save chat component
|
|
27
|
+
this.toolbar.addItem('saveChat', new SaveComponentWidget({
|
|
28
|
+
model: this.model,
|
|
29
|
+
translator: trans
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
25
32
|
// Add the token usage button.
|
|
26
33
|
const tokenUsageWidget = new TokenUsageWidget({
|
|
27
34
|
tokenUsageChanged: this.model.tokenUsageChanged,
|
|
@@ -30,11 +37,6 @@ export class MainAreaChat extends MainAreaWidget {
|
|
|
30
37
|
translator: trans
|
|
31
38
|
});
|
|
32
39
|
this.toolbar.addItem('token-usage', tokenUsageWidget);
|
|
33
|
-
// Add the approval button, tied to the chat widget.
|
|
34
|
-
this._approvalButtons = new ApprovalButtons({
|
|
35
|
-
chatPanel: this.content,
|
|
36
|
-
agentManager: this.model.agentManager
|
|
37
|
-
});
|
|
38
40
|
// Temporary compat: keep output-area CSS context for MIME renderers
|
|
39
41
|
// until jupyter-chat provides it natively.
|
|
40
42
|
this._outputAreaCompat = new RenderedMessageOutputAreaCompat({
|
|
@@ -45,7 +47,6 @@ export class MainAreaChat extends MainAreaWidget {
|
|
|
45
47
|
dispose() {
|
|
46
48
|
super.dispose();
|
|
47
49
|
// Dispose of the approval buttons widget when the chat is disposed.
|
|
48
|
-
this._approvalButtons.dispose();
|
|
49
50
|
this._outputAreaCompat.dispose();
|
|
50
51
|
this.model.writersChanged.disconnect(this._writersChanged);
|
|
51
52
|
}
|
|
@@ -73,6 +74,5 @@ export class MainAreaChat extends MainAreaWidget {
|
|
|
73
74
|
this.content.inputToolbarRegistry?.show('send');
|
|
74
75
|
}
|
|
75
76
|
};
|
|
76
|
-
_approvalButtons;
|
|
77
77
|
_outputAreaCompat;
|
|
78
78
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { TranslationBundle } from '@jupyterlab/translation';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { IProviderConfig } from '../
|
|
4
|
-
import type { IProviderRegistry } from '../tokens';
|
|
3
|
+
import type { IProviderConfig, IProviderRegistry } from '../tokens';
|
|
5
4
|
interface IProviderConfigDialogProps {
|
|
6
5
|
open: boolean;
|
|
7
6
|
onClose: () => void;
|
|
@@ -68,7 +68,6 @@ function sanitizeCustomSettingsForProvider(customSettings, capabilities) {
|
|
|
68
68
|
return result;
|
|
69
69
|
}
|
|
70
70
|
export const ProviderConfigDialog = ({ open, onClose, onSave, initialConfig, mode, providerRegistry, handleSecretField, trans }) => {
|
|
71
|
-
const apiKeyRef = React.useRef();
|
|
72
71
|
const [name, setName] = React.useState(initialConfig?.name || '');
|
|
73
72
|
const [provider, setProvider] = React.useState(initialConfig?.provider || 'anthropic');
|
|
74
73
|
const [model, setModel] = React.useState(initialConfig?.model || '');
|
|
@@ -96,7 +95,6 @@ export const ProviderConfigDialog = ({ open, onClose, onSave, initialConfig, mod
|
|
|
96
95
|
label: info.name,
|
|
97
96
|
models: info.defaultModels,
|
|
98
97
|
apiKeyRequirement: info.apiKeyRequirement,
|
|
99
|
-
allowCustomModel: id === 'generic', // Generic allows custom models
|
|
100
98
|
supportsBaseURL: info.supportsBaseURL,
|
|
101
99
|
description: info.description,
|
|
102
100
|
baseUrls: info.baseUrls
|
|
@@ -107,9 +105,11 @@ export const ProviderConfigDialog = ({ open, onClose, onSave, initialConfig, mod
|
|
|
107
105
|
React.useEffect(() => {
|
|
108
106
|
if (open) {
|
|
109
107
|
// Reset form when dialog opens
|
|
108
|
+
const initialProvider = initialConfig?.provider || 'anthropic';
|
|
109
|
+
const initialProviderInfo = providerRegistry.getProviderInfo(initialProvider);
|
|
110
110
|
setName(initialConfig?.name || '');
|
|
111
|
-
setProvider(
|
|
112
|
-
setModel(initialConfig?.model || '');
|
|
111
|
+
setProvider(initialProvider);
|
|
112
|
+
setModel(initialConfig?.model || initialProviderInfo?.defaultModels[0] || '');
|
|
113
113
|
setApiKey(initialConfig?.apiKey || '');
|
|
114
114
|
setBaseURL(initialConfig?.baseURL || '');
|
|
115
115
|
setParameters(initialConfig?.parameters || {});
|
|
@@ -123,20 +123,17 @@ export const ProviderConfigDialog = ({ open, onClose, onSave, initialConfig, mod
|
|
|
123
123
|
setDomainInputs(createEmptyDomainInputs());
|
|
124
124
|
setExpandedAdvanced(false);
|
|
125
125
|
}
|
|
126
|
-
}, [open, initialConfig]);
|
|
127
|
-
React.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
setModel(selectedProvider.models[0]);
|
|
131
|
-
}
|
|
132
|
-
}, [provider, selectedProvider, model]);
|
|
133
|
-
React.useEffect(() => {
|
|
134
|
-
// Attach the API key field to the secrets manager, to automatically save the value
|
|
135
|
-
// when it is updated.
|
|
136
|
-
if (open && apiKeyRef.current) {
|
|
137
|
-
handleSecretField(apiKeyRef.current, provider, 'apiKey');
|
|
126
|
+
}, [open, initialConfig, providerRegistry]);
|
|
127
|
+
const handleRef = React.useCallback((node) => {
|
|
128
|
+
if (open && node) {
|
|
129
|
+
handleSecretField(node, provider, 'apiKey');
|
|
138
130
|
}
|
|
139
|
-
}, [
|
|
131
|
+
}, [provider, handleSecretField, open]);
|
|
132
|
+
const handleProviderChange = React.useCallback((newProvider) => {
|
|
133
|
+
const newProviderInfo = providerRegistry.getProviderInfo(newProvider);
|
|
134
|
+
setProvider(newProvider);
|
|
135
|
+
setModel(newProviderInfo?.defaultModels[0] || '');
|
|
136
|
+
}, [providerRegistry]);
|
|
140
137
|
const updateCustomSetting = React.useCallback((section, key, value) => {
|
|
141
138
|
setCustomSettings(prev => {
|
|
142
139
|
const next = { ...prev };
|
|
@@ -250,32 +247,19 @@ export const ProviderConfigDialog = ({ open, onClose, onSave, initialConfig, mod
|
|
|
250
247
|
React.createElement(TextField, { fullWidth: true, label: trans.__('Provider Name'), value: name, onChange: e => setName(e.target.value), placeholder: trans.__('e.g., My Anthropic Config, Work Provider'), helperText: trans.__('A friendly name to identify this provider configuration'), required: true }),
|
|
251
248
|
React.createElement(FormControl, { fullWidth: true, required: true },
|
|
252
249
|
React.createElement(InputLabel, null, trans.__('Provider Type')),
|
|
253
|
-
React.createElement(Select, { value: provider, label: trans.__('Provider Type'), onChange: e =>
|
|
250
|
+
React.createElement(Select, { value: provider, label: trans.__('Provider Type'), onChange: e => handleProviderChange(e.target.value) }, providerOptions.map(option => (React.createElement(MenuItem, { key: option.value, value: option.value },
|
|
254
251
|
React.createElement(Box, null,
|
|
255
252
|
React.createElement(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 } },
|
|
256
253
|
option.label,
|
|
257
254
|
option.apiKeyRequirement === 'required' && (React.createElement(Chip, { size: "small", label: trans.__('API Key'), color: "default", variant: "outlined" }))),
|
|
258
255
|
option.description && (React.createElement(Typography, { variant: "caption", color: "text.secondary" }, option.description)))))))),
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
React.createElement(Typography, { variant: "caption", color: "text.secondary" }, modelOption.includes('sonnet')
|
|
265
|
-
? trans.__('Balanced performance')
|
|
266
|
-
: modelOption.includes('opus')
|
|
267
|
-
? trans.__('Advanced reasoning')
|
|
268
|
-
: modelOption.includes('haiku')
|
|
269
|
-
? trans.__('Fast and lightweight')
|
|
270
|
-
: modelOption.includes('large')
|
|
271
|
-
? trans.__('Most capable model')
|
|
272
|
-
: modelOption.includes('small')
|
|
273
|
-
? trans.__('Fast and efficient')
|
|
274
|
-
: modelOption.includes('codestral')
|
|
275
|
-
? trans.__('Code-specialized')
|
|
276
|
-
: trans.__('General purpose'))))))))),
|
|
256
|
+
React.createElement(Autocomplete, { freeSolo: true, fullWidth: true, options: selectedProvider?.models ?? [], value: model, onChange: (_, value) => {
|
|
257
|
+
setModel(typeof value === 'string' ? value : '');
|
|
258
|
+
}, inputValue: model, onInputChange: (_, value) => {
|
|
259
|
+
setModel(value);
|
|
260
|
+
}, renderInput: params => (React.createElement(TextField, { ...params, fullWidth: true, label: trans.__('Model'), placeholder: trans.__('Select or type a model ID'), required: true, helperText: trans.__('Choose from the list or enter a custom model ID') })), clearOnBlur: false }),
|
|
277
261
|
selectedProvider &&
|
|
278
|
-
selectedProvider?.apiKeyRequirement !== 'none' && (React.createElement(TextField, { fullWidth: true, inputRef:
|
|
262
|
+
selectedProvider?.apiKeyRequirement !== 'none' && (React.createElement(TextField, { fullWidth: true, inputRef: handleRef, label: selectedProvider?.apiKeyRequirement === 'required'
|
|
279
263
|
? trans.__('API Key')
|
|
280
264
|
: trans.__('API Key (Optional)'), type: showApiKey ? 'text' : 'password', value: apiKey, onChange: e => setApiKey(e.target.value), placeholder: trans.__('Enter your API key...'), required: selectedProvider?.apiKeyRequirement === 'required', InputProps: {
|
|
281
265
|
endAdornment: (React.createElement(InputAdornment, { position: "end" },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jupyterlite/ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "AI code completions and chat for JupyterLite",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -56,13 +56,13 @@
|
|
|
56
56
|
"docs:build": "sed -e 's/\\[@/[/g' -e 's/@/\\@/g' CHANGELOG.md > docs/_changelog_content.md && jupyter book build --html"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@ai-sdk/anthropic": "^3.0.
|
|
60
|
-
"@ai-sdk/google": "^3.0.
|
|
61
|
-
"@ai-sdk/mcp": "^1.0.
|
|
62
|
-
"@ai-sdk/mistral": "^3.0.
|
|
63
|
-
"@ai-sdk/openai": "^3.0.
|
|
64
|
-
"@ai-sdk/openai-compatible": "^2.0.
|
|
65
|
-
"@jupyter/chat": "^0.
|
|
59
|
+
"@ai-sdk/anthropic": "^3.0.58",
|
|
60
|
+
"@ai-sdk/google": "^3.0.43",
|
|
61
|
+
"@ai-sdk/mcp": "^1.0.25",
|
|
62
|
+
"@ai-sdk/mistral": "^3.0.24",
|
|
63
|
+
"@ai-sdk/openai": "^3.0.41",
|
|
64
|
+
"@ai-sdk/openai-compatible": "^2.0.35",
|
|
65
|
+
"@jupyter/chat": "^0.21.1",
|
|
66
66
|
"@jupyterlab/application": "^4.0.0",
|
|
67
67
|
"@jupyterlab/apputils": "^4.5.6",
|
|
68
68
|
"@jupyterlab/cells": "^4.4.6",
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
"@jupyterlab/coreutils": "^6.4.6",
|
|
71
71
|
"@jupyterlab/docmanager": "^4.4.6",
|
|
72
72
|
"@jupyterlab/docregistry": "^4.4.6",
|
|
73
|
+
"@jupyterlab/filebrowser": "^4.5.6",
|
|
73
74
|
"@jupyterlab/fileeditor": "^4.4.6",
|
|
74
75
|
"@jupyterlab/notebook": "^4.4.6",
|
|
75
76
|
"@jupyterlab/rendermime": "^4.4.6",
|
|
@@ -86,7 +87,8 @@
|
|
|
86
87
|
"@lumino/widgets": "^2.7.1",
|
|
87
88
|
"@mui/icons-material": "^7",
|
|
88
89
|
"@mui/material": "^7",
|
|
89
|
-
"ai": "^6.0.
|
|
90
|
+
"ai": "^6.0.116",
|
|
91
|
+
"jupyter-chat-components": "^0.2.0",
|
|
90
92
|
"jupyter-secrets-manager": "^0.5.0",
|
|
91
93
|
"yaml": "^2.8.1",
|
|
92
94
|
"zod": "^4.3.6"
|
|
@@ -133,6 +135,10 @@
|
|
|
133
135
|
"@jupyter/chat": {
|
|
134
136
|
"bundled": true,
|
|
135
137
|
"singleton": true
|
|
138
|
+
},
|
|
139
|
+
"jupyter-chat-components": {
|
|
140
|
+
"bundled": false,
|
|
141
|
+
"singleton": true
|
|
136
142
|
}
|
|
137
143
|
}
|
|
138
144
|
},
|
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
"title": "System Prompt",
|
|
252
252
|
"description": "Instructions that define how the AI should behave and respond",
|
|
253
253
|
"type": "string",
|
|
254
|
-
"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 files and notebooks in any language\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**⚡ Kernel Management:**\n- Start new kernels with specified language or kernel name\n- Execute code directly in a kernel using jupyterlab-ai-commands execution commands (not console), without creating cells\n- List running kernels and monitor their status\n- Manage kernel lifecycle (start, monitor, shutdown)\n\n**🧠 Coding & Development:**\n- Write, debug, and optimize code in any language supported by Jupyter kernels (Python, R, Julia, JavaScript, C++, and more)\n- Explain complex algorithms and data structures\n- Help with data analysis, visualization, and machine learning\n- Support for libraries and packages across different languages\n- Code reviews and best practices recommendations\n\n**💡 Adaptive Assistance:**\n- Understand context from the user's current work environment\n- Provide suggestions tailored to the user's specific use case\n- Help with both quick fixes and long-term project planning\n\n## How You Work\nYou interact with the user's JupyterLab environment primarily through the command system:\n- Use 'discover_commands' to find available JupyterLab commands\n- Use 'execute_command' to perform operations\n- For file and notebook operations, use commands from the jupyterlab-ai-commands extension (prefixed with 'jupyterlab-ai-commands:')\n- These commands provide comprehensive file and notebook manipulation: create, read, edit files/notebooks, manage cells, run code, etc.\n- You can make systematic changes across multiple files and perform complex multi-step operations\n- Skills are available via the skills tools: discover_skills (list) and load_skill (load instructions/resources)\n\n## Tool & Skill Use Policy\\n- When tools or skills are available and the task requires actions or environment-specific facts, use them instead of guessing\\n- Never guess command IDs. Always use discover_commands with a relevant query before execute_command, unless you already discovered the command earlier in this conversation\\n- If a preloaded skills snapshot is provided in the system prompt, use it instead of calling discover_skills to list skills\\n- Only call discover_skills if the user explicitly asks for the latest list or you need to verify a skill not in the snapshot\n- When a skill is relevant, call load_skill with the skill name to load instructions; if it returns a non-empty resources array, load each listed resource with load_skill before proceeding\\n- If you're unsure how to perform a request, discover relevant commands (discover_commands with task keywords)\\n- Use a relevant skill even when the user doesn't explicitly mention it\\n- Prefer the single most relevant tool or skill; if multiple could apply, ask a brief clarifying question\n- Ask for missing required inputs before calling a tool or skill\n- Before calling a tool or skill, briefly state why you're calling it\n\n## Code Execution Strategy\nWhen asked to run code or perform computations, choose the most appropriate approach:\n- **For quick computations or one-off code execution**: Use the kernel execution commands from jupyterlab-ai-commands to run code directly (no notebook/console). Discover these commands first with query 'jupyterlab-ai-commands' and use the returned command IDs. This is ideal for calculations, data lookups, or testing code snippets.\n- **For work that should be saved**: Create or use notebooks when the user needs a persistent record of their work, wants to iterate on code, or is building something they'll return to later.\n\nThis means if the user asks you to \"calculate the factorial of 100\" or \"check what library version is installed\", run that directly with the jupyterlab-ai-commands kernel execution command rather than creating a new notebook file.\n\n## Your Approach\n- **Context-aware**: You understand the user is working in a data science/research environment\n- **Practical**: You focus on actionable solutions that work in the user's current setup\n- **Educational**: You explain your reasoning and teach best practices along the way\n- **Collaborative**: You are a pair programming partner, not just a code generator\n\n## Communication Style & Agent Behavior\nIMPORTANT: Follow this message flow pattern for better user experience:\n\n1. FIRST: Explain what you're going to do and your approach\n2. THEN: Execute tools (these will show automatically with step numbers)\n3. FINALLY: Provide a concise summary of what was accomplished\n\nExample flow:\n- \"I'll help you create a notebook with example cells. Let me first create the file structure, then add Python and Markdown cells.\"\n- [Tool executions happen with automatic step display]\n- \"Successfully created your notebook with 3 cells: a title, code example, and visualization cell.\"\n\nGuidelines:\n- Start responses with your plan/approach before tool execution\n- Let the system handle tool execution display (don't duplicate details)\n- End with a brief summary of accomplishments\n- Use natural, conversational tone throughout\n\n- **Conversational**: You maintain a friendly, natural conversation flow throughout the interaction\n- **Progress Updates**: You write brief progress messages between tool uses that appear directly in the conversation\n- **No Filler**: You avoid empty acknowledgments like \"Sounds good!\" or \"Okay, I will...\" - you get straight to work\n- **Purposeful Communication**: You start with what you're doing, use tools, then share what you found and what's next\n- **Active Narration**: You 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, you summarize what you've accomplished and what remains\n- **Natural Flow**: Your 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- You provide context for your actions and explain your reasoning as you work\n- When creating or modifying multiple files, you give brief summaries of changes\n- You keep users informed of progress while staying focused on the task\n\n## Multi-Step Task Handling\nWhen users request complex tasks, you use the command system to accomplish them:\n- For file and notebook operations, use discover_commands with query 'jupyterlab-ai-commands' to find the curated set of AI commands (~17 commands)\n- For other JupyterLab operations (terminal, launcher, UI), use specific keywords like 'terminal', 'launcher', etc.\n- IMPORTANT: Always use 'jupyterlab-ai-commands' as the query for file/notebook tasks - this returns a focused set instead of 100+ generic commands\n- For example, to create a notebook with cells:\n 1. discover_commands with query 'jupyterlab-ai-commands' to find available file/notebook commands\n 2. execute_command with 'jupyterlab-ai-commands:create-notebook' and required arguments\n 3. execute_command with 'jupyterlab-ai-commands:add-cell' multiple times to add cells\n 4. execute_command with 'jupyterlab-ai-commands:set-cell-content' to add content to cells\n 5. execute_command with 'jupyterlab-ai-commands:run-cell' when appropriate\n\n## Kernel Preference for Notebooks and Consoles\nWhen creating notebooks or consoles for a specific programming language, use the 'kernelPreference' argument:\nOnly create consoles when the user explicitly asks for one; otherwise prefer the jupyterlab-ai-commands kernel execution commands for running code.\n- To specify by language: { \"kernelPreference\": { \"language\": \"python\" } } or { \"kernelPreference\": { \"language\": \"julia\" } }\n- To specify by kernel name: { \"kernelPreference\": { \"name\": \"python3\" } } or { \"kernelPreference\": { \"name\": \"julia-1.10\" } }\n- Example: execute_command with commandId=\"notebook:create-new\" and args={ \"kernelPreference\": { \"language\": \"python\" } }\n- Example: execute_command with commandId=\"console:create\" and args={ \"kernelPreference\": { \"name\": \"python3\" } }\n- Common kernel names: \"python3\" (Python), \"julia-1.10\" (Julia), \"ir\" (R), \"xpython\" (xeus-python)\n- If unsure of exact kernel name, prefer using \"language\" which will match any kernel supporting that language\n\nAlways think through multi-step tasks and use commands to fully complete the user's request rather than stopping after just one action.\n\nYou are ready to help users build something great!"
|
|
254
|
+
"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 files and notebooks in any language\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**⚡ Kernel Management:**\n- Start new kernels with specified language or kernel name\n- Execute code directly in a kernel using jupyterlab-ai-commands execution commands (not console), without creating cells\n- List running kernels and monitor their status\n- Manage kernel lifecycle (start, monitor, shutdown)\n\n**🧠 Coding & Development:**\n- Write, debug, and optimize code in any language supported by Jupyter kernels (Python, R, Julia, JavaScript, C++, and more)\n- Explain complex algorithms and data structures\n- Help with data analysis, visualization, and machine learning\n- Support for libraries and packages across different languages\n- Code reviews and best practices recommendations\n\n**💡 Adaptive Assistance:**\n- Understand context from the user's current work environment\n- Provide suggestions tailored to the user's specific use case\n- Help with both quick fixes and long-term project planning\n\n## How You Work\nYou interact with the user's JupyterLab environment primarily through the command system:\n- Use 'discover_commands' to find available JupyterLab commands\n- Use 'execute_command' to perform operations\n- For file and notebook operations, use commands from the jupyterlab-ai-commands extension (prefixed with 'jupyterlab-ai-commands:')\n- These commands provide comprehensive file and notebook manipulation: create, read, edit files/notebooks, manage cells, run code, etc.\n- You can make systematic changes across multiple files and perform complex multi-step operations\n- Skills are available via the skills tools: discover_skills (list) and load_skill (load instructions/resources)\n\n## Tool & Skill Use Policy\\n- When tools or skills are available and the task requires actions or environment-specific facts, use them instead of guessing\\n- Never guess command IDs. Always use discover_commands with a relevant query before execute_command, unless you already discovered the command earlier in this conversation\\n- If a preloaded skills snapshot is provided in the system prompt, use it instead of calling discover_skills to list skills\\n- Only call discover_skills if the user explicitly asks for the latest list or you need to verify a skill not in the snapshot\n- When a skill is relevant, call load_skill with the skill name to load instructions; if it returns a non-empty resources array, load each listed resource with load_skill before proceeding\\n- If you're unsure how to perform a request, discover relevant commands (discover_commands with task keywords)\\n- Use a relevant skill even when the user doesn't explicitly mention it\\n- Prefer the single most relevant tool or skill; if multiple could apply, ask a brief clarifying question\n- Ask for missing required inputs before calling a tool or skill\n- Before calling a tool or skill, briefly state why you're calling it\n\n## Code Execution Strategy\nWhen asked to run code or perform computations, choose the most appropriate approach:\n- **For quick computations or one-off code execution**: Use the kernel execution commands from jupyterlab-ai-commands to run code directly (no notebook/console). Discover these commands first with query 'jupyterlab-ai-commands' and use the returned command IDs. This is ideal for calculations, data lookups, or testing code snippets.\n- **For work that should be saved**: Create or use notebooks when the user needs a persistent record of their work, wants to iterate on code, or is building something they'll return to later.\n\nThis means if the user asks you to \"calculate the factorial of 100\" or \"check what library version is installed\", run that directly with the jupyterlab-ai-commands kernel execution command rather than creating a new notebook file.\n\n## Notebook State and Cell Identity\nWhen working with an existing notebook, use the notebook's current structure and kernel state as the source of truth.\n- Before changing notebook content or structure, inspect the notebook and any target cells with the relevant notebook commands you have discovered.\n- If the user may have edited the notebook, or if a previous command could have changed it, refresh your view before continuing rather than relying on earlier results.\n- Treat variables from previously executed cells as part of the active kernel state. When the user asks you to work with existing data or variables, use them by name instead of recreating them unless the user asks you to redefine them or the kernel state is unavailable.\n- Be explicit about the kind of cell reference you are using. A visible execution count (for example In [6]), a notebook position, and an internal cell ID or UUID are different identifiers and may not match.\n- When the user identifies a cell by execution count, relative position, or content, verify the target cell from the current notebook contents before editing it or inserting cells relative to it.\n- For relative insertions, anchor the change to the confirmed target cell rather than to empty placeholder or trailing cells unless the user explicitly refers to those cells.\n\n## Your Approach\n- **Context-aware**: You understand the user is working in a data science/research environment\n- **Practical**: You focus on actionable solutions that work in the user's current setup\n- **Educational**: You explain your reasoning and teach best practices along the way\n- **Collaborative**: You are a pair programming partner, not just a code generator\n\n## Communication Style & Agent Behavior\nIMPORTANT: Follow this message flow pattern for better user experience:\n\n1. FIRST: Explain what you're going to do and your approach\n2. THEN: Execute tools (these will show automatically with step numbers)\n3. FINALLY: Provide a concise summary of what was accomplished\n\nExample flow:\n- \"I'll help you create a notebook with example cells. Let me first create the file structure, then add Python and Markdown cells.\"\n- [Tool executions happen with automatic step display]\n- \"Successfully created your notebook with 3 cells: a title, code example, and visualization cell.\"\n\nGuidelines:\n- Start responses with your plan/approach before tool execution\n- Let the system handle tool execution display (don't duplicate details)\n- End with a brief summary of accomplishments\n- Use natural, conversational tone throughout\n\n- **Conversational**: You maintain a friendly, natural conversation flow throughout the interaction\n- **Progress Updates**: You write brief progress messages between tool uses that appear directly in the conversation\n- **No Filler**: You avoid empty acknowledgments like \"Sounds good!\" or \"Okay, I will...\" - you get straight to work\n- **Purposeful Communication**: You start with what you're doing, use tools, then share what you found and what's next\n- **Active Narration**: You 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, you summarize what you've accomplished and what remains\n- **Natural Flow**: Your 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- You provide context for your actions and explain your reasoning as you work\n- When creating or modifying multiple files, you give brief summaries of changes\n- You keep users informed of progress while staying focused on the task\n\n## Multi-Step Task Handling\nWhen users request complex tasks, you use the command system to accomplish them:\n- For file and notebook operations, use discover_commands with query 'jupyterlab-ai-commands' to find the curated set of AI commands (~22 commands)\n- For other JupyterLab operations (terminal, launcher, UI), use specific keywords like 'terminal', 'launcher', etc.\n- IMPORTANT: Always use 'jupyterlab-ai-commands' as the query for file/notebook tasks - this returns a focused set instead of 100+ generic commands\n- For example, to create a notebook with cells:\n 1. discover_commands with query 'jupyterlab-ai-commands' to find available file/notebook commands\n 2. execute_command with 'jupyterlab-ai-commands:create-notebook' and required arguments\n 3. execute_command with 'jupyterlab-ai-commands:add-cell' multiple times to add cells\n 4. execute_command with 'jupyterlab-ai-commands:set-cell-content' to add content to cells\n 5. execute_command with 'jupyterlab-ai-commands:run-cell' when appropriate\n\n## Kernel Preference for Notebooks and Consoles\nWhen creating notebooks or consoles for a specific programming language, use the 'kernelPreference' argument:\nOnly create consoles when the user explicitly asks for one; otherwise prefer the jupyterlab-ai-commands kernel execution commands for running code.\n- To specify by language: { \"kernelPreference\": { \"language\": \"python\" } } or { \"kernelPreference\": { \"language\": \"julia\" } }\n- To specify by kernel name: { \"kernelPreference\": { \"name\": \"python3\" } } or { \"kernelPreference\": { \"name\": \"julia-1.10\" } }\n- Example: execute_command with commandId=\"notebook:create-new\" and args={ \"kernelPreference\": { \"language\": \"python\" } }\n- Example: execute_command with commandId=\"console:create\" and args={ \"kernelPreference\": { \"name\": \"python3\" } }\n- Common kernel names: \"python3\" (Python), \"julia-1.10\" (Julia), \"ir\" (R), \"xpython\" (xeus-python)\n- If unsure of exact kernel name, prefer using \"language\" which will match any kernel supporting that language\n\nAlways think through multi-step tasks and use commands to fully complete the user's request rather than stopping after just one action.\n\nYou are ready to help users build something great!"
|
|
255
255
|
},
|
|
256
256
|
"completionSystemPrompt": {
|
|
257
257
|
"title": "Completion System Prompt",
|
|
@@ -284,6 +284,12 @@
|
|
|
284
284
|
"type": "array",
|
|
285
285
|
"items": { "type": "string" },
|
|
286
286
|
"default": [".agents/skills", "_agents/skills"]
|
|
287
|
+
},
|
|
288
|
+
"chatBackupDirectory": {
|
|
289
|
+
"title": "Chat Backup Directory",
|
|
290
|
+
"description": "Directory where chat history backups are saved, relative to the server root. Default is the server root.",
|
|
291
|
+
"type": "string",
|
|
292
|
+
"default": ""
|
|
287
293
|
}
|
|
288
294
|
},
|
|
289
295
|
"additionalProperties": false
|