@jupyterlite/ai 0.12.0 → 0.14.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 +36 -2
- package/lib/agent.js +249 -24
- package/lib/{chat-model-registry.d.ts → chat-model-handler.d.ts} +12 -11
- package/lib/{chat-model-registry.js → chat-model-handler.js} +6 -40
- package/lib/chat-model.d.ts +8 -0
- package/lib/chat-model.js +156 -8
- package/lib/completion/completion-provider.d.ts +1 -1
- package/lib/completion/completion-provider.js +14 -2
- package/lib/components/model-select.js +4 -4
- package/lib/components/tool-select.d.ts +11 -2
- package/lib/components/tool-select.js +77 -18
- package/lib/index.d.ts +3 -3
- package/lib/index.js +249 -117
- package/lib/models/settings-model.d.ts +2 -0
- package/lib/models/settings-model.js +2 -0
- package/lib/providers/built-in-providers.js +33 -32
- package/lib/providers/provider-tools.d.ts +36 -0
- package/lib/providers/provider-tools.js +93 -0
- package/lib/rendered-message-outputarea.d.ts +24 -0
- package/lib/rendered-message-outputarea.js +48 -0
- package/lib/tokens.d.ts +65 -7
- package/lib/tokens.js +1 -1
- package/lib/tools/commands.js +62 -22
- package/lib/tools/web.d.ts +8 -0
- package/lib/tools/web.js +196 -0
- package/lib/widgets/ai-settings.d.ts +4 -9
- package/lib/widgets/ai-settings.js +123 -69
- package/lib/widgets/main-area-chat.d.ts +6 -0
- package/lib/widgets/main-area-chat.js +28 -0
- package/lib/widgets/provider-config-dialog.js +211 -11
- package/package.json +17 -11
- package/schema/settings-model.json +89 -1
- package/src/agent.ts +327 -42
- package/src/{chat-model-registry.ts → chat-model-handler.ts} +16 -51
- package/src/chat-model.ts +223 -14
- package/src/completion/completion-provider.ts +26 -12
- package/src/components/model-select.tsx +4 -5
- package/src/components/tool-select.tsx +110 -7
- package/src/index.ts +359 -184
- package/src/models/settings-model.ts +6 -0
- package/src/providers/built-in-providers.ts +33 -32
- package/src/providers/provider-tools.ts +179 -0
- package/src/rendered-message-outputarea.ts +62 -0
- package/src/tokens.ts +82 -9
- package/src/tools/commands.ts +99 -31
- package/src/tools/web.ts +238 -0
- package/src/widgets/ai-settings.tsx +279 -124
- package/src/widgets/main-area-chat.ts +34 -1
- package/src/widgets/provider-config-dialog.tsx +504 -11
package/lib/chat-model.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AbstractChatModel } from '@jupyter/chat';
|
|
2
2
|
import { PathExt } from '@jupyterlab/coreutils';
|
|
3
|
+
import * as nbformat from '@jupyterlab/nbformat';
|
|
3
4
|
import { UUID } from '@lumino/coreutils';
|
|
4
5
|
import { Signal } from '@lumino/signaling';
|
|
5
6
|
import { AI_AVATAR } from './icons';
|
|
@@ -236,8 +237,9 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
236
237
|
type: 'msg',
|
|
237
238
|
raw_time: false
|
|
238
239
|
};
|
|
239
|
-
this._currentStreamingMessage = aiMessage;
|
|
240
240
|
this.messageAdded(aiMessage);
|
|
241
|
+
this._currentStreamingMessage =
|
|
242
|
+
this.messages.find(message => message.id === aiMessage.id) ?? null;
|
|
241
243
|
}
|
|
242
244
|
/**
|
|
243
245
|
* Handles streaming message chunks from the AI agent.
|
|
@@ -246,8 +248,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
246
248
|
_handleMessageChunk(event) {
|
|
247
249
|
if (this._currentStreamingMessage &&
|
|
248
250
|
this._currentStreamingMessage.id === event.data.messageId) {
|
|
249
|
-
this._currentStreamingMessage.body
|
|
250
|
-
this.messageAdded(this._currentStreamingMessage);
|
|
251
|
+
this._currentStreamingMessage.update({ body: event.data.fullContent });
|
|
251
252
|
}
|
|
252
253
|
}
|
|
253
254
|
/**
|
|
@@ -257,8 +258,7 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
257
258
|
_handleMessageComplete(event) {
|
|
258
259
|
if (this._currentStreamingMessage &&
|
|
259
260
|
this._currentStreamingMessage.id === event.data.messageId) {
|
|
260
|
-
this._currentStreamingMessage.body
|
|
261
|
-
this.messageAdded(this._currentStreamingMessage);
|
|
261
|
+
this._currentStreamingMessage.update({ body: event.data.content });
|
|
262
262
|
this._currentStreamingMessage = null;
|
|
263
263
|
}
|
|
264
264
|
}
|
|
@@ -295,6 +295,21 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
295
295
|
return parsedInput.name;
|
|
296
296
|
}
|
|
297
297
|
break;
|
|
298
|
+
case 'browser_fetch':
|
|
299
|
+
if (parsedInput.url) {
|
|
300
|
+
return parsedInput.url;
|
|
301
|
+
}
|
|
302
|
+
break;
|
|
303
|
+
case 'web_fetch':
|
|
304
|
+
if (parsedInput.url) {
|
|
305
|
+
return parsedInput.url;
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
case 'web_search':
|
|
309
|
+
if (parsedInput.query) {
|
|
310
|
+
return `query: "${parsedInput.query}"`;
|
|
311
|
+
}
|
|
312
|
+
break;
|
|
298
313
|
}
|
|
299
314
|
}
|
|
300
315
|
catch {
|
|
@@ -302,6 +317,22 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
302
317
|
}
|
|
303
318
|
return '';
|
|
304
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* Determine whether this tool call should auto-render trusted MIME bundles.
|
|
322
|
+
*/
|
|
323
|
+
_computeShouldAutoRenderMimeBundles(toolName, input) {
|
|
324
|
+
if (toolName !== 'execute_command') {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
const parsedInput = JSON.parse(input);
|
|
329
|
+
return (typeof parsedInput.commandId === 'string' &&
|
|
330
|
+
this._settingsModel.config.commandsAutoRenderMimeBundles.includes(parsedInput.commandId));
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
305
336
|
/**
|
|
306
337
|
* Handles the start of a tool call execution.
|
|
307
338
|
* @param event Event containing the tool call start data
|
|
@@ -309,13 +340,15 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
309
340
|
_handleToolCallStartEvent(event) {
|
|
310
341
|
const messageId = UUID.uuid4();
|
|
311
342
|
const summary = this._extractToolSummary(event.data.toolName, event.data.input);
|
|
343
|
+
const shouldAutoRenderMimeBundles = this._computeShouldAutoRenderMimeBundles(event.data.toolName, event.data.input);
|
|
312
344
|
const context = {
|
|
313
345
|
toolCallId: event.data.callId,
|
|
314
346
|
messageId,
|
|
315
347
|
toolName: event.data.toolName,
|
|
316
348
|
input: event.data.input,
|
|
317
349
|
status: 'pending',
|
|
318
|
-
summary
|
|
350
|
+
summary,
|
|
351
|
+
shouldAutoRenderMimeBundles
|
|
319
352
|
};
|
|
320
353
|
this._toolContexts.set(event.data.callId, context);
|
|
321
354
|
const toolCallMessage = {
|
|
@@ -338,10 +371,38 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
338
371
|
* Handles the completion of a tool call execution.
|
|
339
372
|
*/
|
|
340
373
|
_handleToolCallCompleteEvent(event) {
|
|
374
|
+
const context = this._toolContexts.get(event.data.callId);
|
|
341
375
|
const status = event.data.isError ? 'error' : 'completed';
|
|
342
|
-
this._updateToolCallUI(event.data.callId, status, event.data.
|
|
376
|
+
this._updateToolCallUI(event.data.callId, status, Private.formatToolOutput(event.data.outputData));
|
|
377
|
+
if (!event.data.isError && this._shouldAutoRenderMimeBundles(context)) {
|
|
378
|
+
// Tool results are arbitrary command payloads (often wrapped in
|
|
379
|
+
// { success, result, outputs, ... }), so extract display outputs
|
|
380
|
+
// defensively instead of assuming a raw kernel message shape.
|
|
381
|
+
const mimeBundles = Private.extractMimeBundlesFromUnknown(event.data.outputData, {
|
|
382
|
+
trustedMimeTypes: this._settingsModel.config.trustedMimeTypesForAutoRender
|
|
383
|
+
});
|
|
384
|
+
for (const bundle of mimeBundles) {
|
|
385
|
+
this.messageAdded({
|
|
386
|
+
body: bundle,
|
|
387
|
+
sender: this._getAIUser(),
|
|
388
|
+
id: UUID.uuid4(),
|
|
389
|
+
time: Date.now() / 1000,
|
|
390
|
+
type: 'msg',
|
|
391
|
+
raw_time: false
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
343
395
|
this._toolContexts.delete(event.data.callId);
|
|
344
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* Determine whether a tool call output should auto-render MIME bundles.
|
|
399
|
+
*/
|
|
400
|
+
_shouldAutoRenderMimeBundles(context) {
|
|
401
|
+
if (!context) {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
return !!context.shouldAutoRenderMimeBundles;
|
|
405
|
+
}
|
|
345
406
|
/**
|
|
346
407
|
* Handles error events from the AI agent.
|
|
347
408
|
*/
|
|
@@ -638,6 +699,87 @@ export class AIChatModel extends AbstractChatModel {
|
|
|
638
699
|
}
|
|
639
700
|
var Private;
|
|
640
701
|
(function (Private) {
|
|
702
|
+
const isPlainObject = (value) => {
|
|
703
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
704
|
+
};
|
|
705
|
+
const isDisplayOutput = (value) => {
|
|
706
|
+
if (!isPlainObject(value)) {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
const output = value;
|
|
710
|
+
return (nbformat.isDisplayData(output) ||
|
|
711
|
+
nbformat.isDisplayUpdate(output) ||
|
|
712
|
+
nbformat.isExecuteResult(output));
|
|
713
|
+
};
|
|
714
|
+
const toMimeBundle = (value, trustedMimeTypes) => {
|
|
715
|
+
const data = value.data;
|
|
716
|
+
if (!isPlainObject(data) || Object.keys(data).length === 0) {
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
return {
|
|
720
|
+
data: data,
|
|
721
|
+
...(isPlainObject(value.metadata)
|
|
722
|
+
? { metadata: value.metadata }
|
|
723
|
+
: {}),
|
|
724
|
+
// MIME auto-rendering only runs for explicitly configured command IDs.
|
|
725
|
+
// Trust handling is configurable to keep risky MIME execution opt-in.
|
|
726
|
+
...(Object.keys(data).some(m => trustedMimeTypes.has(m))
|
|
727
|
+
? { trusted: true }
|
|
728
|
+
: {})
|
|
729
|
+
};
|
|
730
|
+
};
|
|
731
|
+
/**
|
|
732
|
+
* Normalize arbitrary tool payloads into canonical display outputs.
|
|
733
|
+
*
|
|
734
|
+
* Tool outputs are not guaranteed to be raw Jupyter IOPub messages; they are
|
|
735
|
+
* often wrapped objects (for example `{ success, result: { outputs: [...] } }`).
|
|
736
|
+
*/
|
|
737
|
+
const toDisplayOutputs = (value) => {
|
|
738
|
+
if (isDisplayOutput(value)) {
|
|
739
|
+
return [value];
|
|
740
|
+
}
|
|
741
|
+
if (Array.isArray(value)) {
|
|
742
|
+
return value.filter(isDisplayOutput);
|
|
743
|
+
}
|
|
744
|
+
if (!isPlainObject(value)) {
|
|
745
|
+
return [];
|
|
746
|
+
}
|
|
747
|
+
if (Array.isArray(value.outputs)) {
|
|
748
|
+
return value.outputs.filter(isDisplayOutput);
|
|
749
|
+
}
|
|
750
|
+
if ('result' in value) {
|
|
751
|
+
return toDisplayOutputs(value.result);
|
|
752
|
+
}
|
|
753
|
+
return [];
|
|
754
|
+
};
|
|
755
|
+
/**
|
|
756
|
+
* Extract rendermime-ready mime bundles from arbitrary tool results.
|
|
757
|
+
*/
|
|
758
|
+
function extractMimeBundlesFromUnknown(content, options = {}) {
|
|
759
|
+
const bundles = [];
|
|
760
|
+
const outputs = toDisplayOutputs(content);
|
|
761
|
+
const trustedMimeTypes = new Set(options.trustedMimeTypes ?? []);
|
|
762
|
+
for (const output of outputs) {
|
|
763
|
+
const bundle = toMimeBundle(output, trustedMimeTypes);
|
|
764
|
+
if (bundle) {
|
|
765
|
+
bundles.push(bundle);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return bundles;
|
|
769
|
+
}
|
|
770
|
+
Private.extractMimeBundlesFromUnknown = extractMimeBundlesFromUnknown;
|
|
771
|
+
function formatToolOutput(outputData) {
|
|
772
|
+
if (typeof outputData === 'string') {
|
|
773
|
+
return outputData;
|
|
774
|
+
}
|
|
775
|
+
try {
|
|
776
|
+
return JSON.stringify(outputData, null, 2);
|
|
777
|
+
}
|
|
778
|
+
catch {
|
|
779
|
+
return '[Complex object - cannot serialize]';
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
Private.formatToolOutput = formatToolOutput;
|
|
641
783
|
function escapeHtml(value) {
|
|
642
784
|
// Prefer the same native escaping approach used in JupyterLab itself
|
|
643
785
|
// (e.g. `@jupyterlab/completer`).
|
|
@@ -737,7 +879,7 @@ var Private;
|
|
|
737
879
|
<pre class="jp-ai-tool-code"><code>${escapedOutput}</code></pre>
|
|
738
880
|
</div>`;
|
|
739
881
|
}
|
|
740
|
-
|
|
882
|
+
const HTMLContent = `<details class="jp-ai-tool-call ${config.cssClass}"${openAttr}>
|
|
741
883
|
<summary class="jp-ai-tool-header">
|
|
742
884
|
<div class="jp-ai-tool-icon">⚡</div>
|
|
743
885
|
<div class="jp-ai-tool-title">${escapedToolName}${summaryHtml}</div>
|
|
@@ -746,6 +888,12 @@ var Private;
|
|
|
746
888
|
<div class="jp-ai-tool-body">${bodyContent}
|
|
747
889
|
</div>
|
|
748
890
|
</details>`;
|
|
891
|
+
return {
|
|
892
|
+
trusted: true,
|
|
893
|
+
data: {
|
|
894
|
+
'text/html': HTMLContent
|
|
895
|
+
}
|
|
896
|
+
};
|
|
749
897
|
}
|
|
750
898
|
Private.buildToolCallHtml = buildToolCallHtml;
|
|
751
899
|
})(Private || (Private = {}));
|
|
@@ -22,6 +22,10 @@ export class AICompletionProvider {
|
|
|
22
22
|
this._updateModel();
|
|
23
23
|
});
|
|
24
24
|
this._updateModel();
|
|
25
|
+
// Disable the secrets manager if the token is empty.
|
|
26
|
+
if (!options.token) {
|
|
27
|
+
this._secretsManager = undefined;
|
|
28
|
+
}
|
|
25
29
|
}
|
|
26
30
|
/**
|
|
27
31
|
* The unique identifier of the provider.
|
|
@@ -115,8 +119,16 @@ export class AICompletionProvider {
|
|
|
115
119
|
const baseURL = activeProvider.baseURL;
|
|
116
120
|
let apiKey;
|
|
117
121
|
if (this._secretsManager && this._settingsModel.config.useSecretsManager) {
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
const token = Private.getToken();
|
|
123
|
+
if (!token) {
|
|
124
|
+
// This should never happen, the secrets manager should be disabled.
|
|
125
|
+
console.error('@jupyterlite/ai::AICompletionProvider error: the settings manager token is not set.\nYou should disable the secrets manager from the AI settings.');
|
|
126
|
+
apiKey = '';
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
apiKey =
|
|
130
|
+
(await this._secretsManager.get(token, SECRETS_NAMESPACE, `${provider}:apiKey`))?.value ?? '';
|
|
131
|
+
}
|
|
120
132
|
}
|
|
121
133
|
else {
|
|
122
134
|
apiKey = this._settingsModel.getApiKey(activeProvider.id);
|
|
@@ -115,11 +115,11 @@ export function ModelSelect(props) {
|
|
|
115
115
|
e.stopPropagation();
|
|
116
116
|
}, sx: {
|
|
117
117
|
backgroundColor: isSelected
|
|
118
|
-
? 'var(--
|
|
118
|
+
? 'var(--mui-palette-primary-main)'
|
|
119
119
|
: 'transparent',
|
|
120
120
|
'&:hover': {
|
|
121
121
|
backgroundColor: isSelected
|
|
122
|
-
? 'var(--
|
|
122
|
+
? 'var(--mui-palette-primary-main)'
|
|
123
123
|
: 'var(--jp-layout-color1)'
|
|
124
124
|
},
|
|
125
125
|
display: 'flex',
|
|
@@ -127,13 +127,13 @@ export function ModelSelect(props) {
|
|
|
127
127
|
gap: '8px'
|
|
128
128
|
} },
|
|
129
129
|
isSelected ? (React.createElement(CheckIcon, { sx: {
|
|
130
|
-
color: 'var(--jp-
|
|
130
|
+
color: 'var(--jp-ui-inverse-font-color1)',
|
|
131
131
|
fontSize: 16
|
|
132
132
|
} })) : (React.createElement("div", { style: { width: '16px' } })),
|
|
133
133
|
React.createElement(Typography, { variant: "body2", component: "div", sx: {
|
|
134
134
|
fontWeight: isSelected ? 600 : 400,
|
|
135
135
|
color: isSelected
|
|
136
|
-
? 'var(--jp-
|
|
136
|
+
? 'var(--jp-ui-inverse-font-color1)'
|
|
137
137
|
: 'inherit'
|
|
138
138
|
} }, providerLabel)))))));
|
|
139
139
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { InputToolbarRegistry } from '@jupyter/chat';
|
|
2
2
|
import type { TranslationBundle } from '@jupyterlab/translation';
|
|
3
|
-
import { IToolRegistry } from '../tokens';
|
|
3
|
+
import { IProviderRegistry, IToolRegistry } from '../tokens';
|
|
4
|
+
import { AISettingsModel } from '../models/settings-model';
|
|
4
5
|
/**
|
|
5
6
|
* Properties for the tool select component.
|
|
6
7
|
*/
|
|
@@ -17,6 +18,14 @@ export interface IToolSelectProps extends InputToolbarRegistry.IToolbarItemProps
|
|
|
17
18
|
* Function to handle tool selection changes.
|
|
18
19
|
*/
|
|
19
20
|
onToolSelectionChange: (selectedToolNames: string[]) => void;
|
|
21
|
+
/**
|
|
22
|
+
* The settings model to compute provider-level web tools.
|
|
23
|
+
*/
|
|
24
|
+
settingsModel: AISettingsModel;
|
|
25
|
+
/**
|
|
26
|
+
* Registry for provider metadata used to resolve provider tool capabilities.
|
|
27
|
+
*/
|
|
28
|
+
providerRegistry: IProviderRegistry;
|
|
20
29
|
/**
|
|
21
30
|
* The application language translator.
|
|
22
31
|
*/
|
|
@@ -29,4 +38,4 @@ export declare function ToolSelect(props: IToolSelectProps): JSX.Element;
|
|
|
29
38
|
/**
|
|
30
39
|
* Factory function returning the toolbar item for tool selection.
|
|
31
40
|
*/
|
|
32
|
-
export declare function createToolSelectItem(toolRegistry: IToolRegistry, toolsEnabled: boolean | undefined, translator: TranslationBundle): InputToolbarRegistry.IToolbarItem;
|
|
41
|
+
export declare function createToolSelectItem(toolRegistry: IToolRegistry, settingsModel: AISettingsModel, providerRegistry: IProviderRegistry, toolsEnabled: boolean | undefined, translator: TranslationBundle): InputToolbarRegistry.IToolbarItem;
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { TooltippedButton } from '@jupyter/chat';
|
|
2
2
|
import BuildIcon from '@mui/icons-material/Build';
|
|
3
3
|
import CheckIcon from '@mui/icons-material/Check';
|
|
4
|
-
import { Menu, MenuItem, Tooltip, Typography } from '@mui/material';
|
|
4
|
+
import { Divider, Menu, MenuItem, Tooltip, Typography } from '@mui/material';
|
|
5
5
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
6
|
+
import { createProviderTools } from '../providers/provider-tools';
|
|
6
7
|
const SELECT_ITEM_CLASS = 'jp-AIToolSelect-item';
|
|
7
8
|
/**
|
|
8
9
|
* The tool select component for choosing AI tools.
|
|
9
10
|
*/
|
|
10
11
|
export function ToolSelect(props) {
|
|
11
|
-
const { toolRegistry, onToolSelectionChange, toolsEnabled, translator: trans } = props;
|
|
12
|
+
const { toolRegistry, onToolSelectionChange, toolsEnabled, settingsModel, providerRegistry, model, translator: trans } = props;
|
|
13
|
+
const chatContext = model.chatContext;
|
|
14
|
+
const agentManager = chatContext.agentManager;
|
|
12
15
|
const [selectedToolNames, setSelectedToolNames] = useState([]);
|
|
13
16
|
const [tools, setTools] = useState(toolRegistry?.namedTools || []);
|
|
17
|
+
const [providerToolNames, setProviderToolNames] = useState([]);
|
|
14
18
|
const [menuAnchorEl, setMenuAnchorEl] = useState(null);
|
|
15
19
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
16
20
|
const openMenu = useCallback((el) => {
|
|
@@ -48,6 +52,41 @@ export function ToolSelect(props) {
|
|
|
48
52
|
};
|
|
49
53
|
}
|
|
50
54
|
}, [toolRegistry]);
|
|
55
|
+
// Track provider-level tools (e.g. web_search/web_fetch).
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!agentManager || !toolsEnabled) {
|
|
58
|
+
setProviderToolNames([]);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const updateProviderTools = () => {
|
|
62
|
+
const activeProviderId = agentManager.activeProvider;
|
|
63
|
+
const providerConfig = settingsModel.getProvider(activeProviderId);
|
|
64
|
+
if (!providerConfig) {
|
|
65
|
+
setProviderToolNames([]);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const providerInfo = providerRegistry.getProviderInfo(providerConfig.provider);
|
|
69
|
+
const providerTools = createProviderTools({
|
|
70
|
+
providerInfo,
|
|
71
|
+
customSettings: providerConfig.customSettings,
|
|
72
|
+
hasFunctionTools: selectedToolNames.length > 0
|
|
73
|
+
});
|
|
74
|
+
setProviderToolNames(Object.keys(providerTools));
|
|
75
|
+
};
|
|
76
|
+
updateProviderTools();
|
|
77
|
+
settingsModel.stateChanged.connect(updateProviderTools);
|
|
78
|
+
agentManager.activeProviderChanged.connect(updateProviderTools);
|
|
79
|
+
return () => {
|
|
80
|
+
settingsModel.stateChanged.disconnect(updateProviderTools);
|
|
81
|
+
agentManager.activeProviderChanged.disconnect(updateProviderTools);
|
|
82
|
+
};
|
|
83
|
+
}, [
|
|
84
|
+
settingsModel,
|
|
85
|
+
providerRegistry,
|
|
86
|
+
agentManager,
|
|
87
|
+
selectedToolNames.length,
|
|
88
|
+
toolsEnabled
|
|
89
|
+
]);
|
|
51
90
|
// Initialize selected tools to all tools by default
|
|
52
91
|
useEffect(() => {
|
|
53
92
|
if (tools.length > 0 && selectedToolNames.length === 0) {
|
|
@@ -57,14 +96,16 @@ export function ToolSelect(props) {
|
|
|
57
96
|
}
|
|
58
97
|
}, [tools, selectedToolNames.length, onToolSelectionChange]);
|
|
59
98
|
// Don't render if tools are disabled or no tools available
|
|
60
|
-
if (!toolsEnabled || tools.length === 0) {
|
|
99
|
+
if (!toolsEnabled || (tools.length === 0 && providerToolNames.length === 0)) {
|
|
61
100
|
return React.createElement(React.Fragment, null);
|
|
62
101
|
}
|
|
102
|
+
const selectedCount = selectedToolNames.length + providerToolNames.length;
|
|
103
|
+
const totalCount = tools.length + providerToolNames.length;
|
|
63
104
|
return (React.createElement(React.Fragment, null,
|
|
64
105
|
React.createElement(TooltippedButton, { onClick: e => {
|
|
65
106
|
openMenu(e.currentTarget);
|
|
66
|
-
}, tooltip: trans.__('Tools (%1/%2 selected)',
|
|
67
|
-
...(
|
|
107
|
+
}, tooltip: trans.__('Tools (%1/%2 selected)', selectedCount.toString(), totalCount.toString()), buttonProps: {
|
|
108
|
+
...(selectedCount === 0 && {
|
|
68
109
|
variant: 'outlined'
|
|
69
110
|
}),
|
|
70
111
|
title: trans.__('Select AI Tools'),
|
|
@@ -76,7 +117,7 @@ export function ToolSelect(props) {
|
|
|
76
117
|
// Stop propagation to prevent sending message
|
|
77
118
|
e.stopPropagation();
|
|
78
119
|
}
|
|
79
|
-
}, sx:
|
|
120
|
+
}, sx: selectedCount === 0
|
|
80
121
|
? { backgroundColor: 'var(--jp-layout-color3)' }
|
|
81
122
|
: {} },
|
|
82
123
|
React.createElement(BuildIcon, { sx: { fontSize: 'small' } })),
|
|
@@ -91,22 +132,38 @@ export function ToolSelect(props) {
|
|
|
91
132
|
padding: '0.5em',
|
|
92
133
|
paddingRight: '2em'
|
|
93
134
|
}
|
|
94
|
-
} },
|
|
95
|
-
React.createElement(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
135
|
+
} },
|
|
136
|
+
tools.map(namedTool => (React.createElement(Tooltip, { key: namedTool.name, title: namedTool.tool.description || namedTool.name, placement: "left" },
|
|
137
|
+
React.createElement(MenuItem, { className: SELECT_ITEM_CLASS, onClick: e => {
|
|
138
|
+
toggleTool(namedTool.name);
|
|
139
|
+
// Prevent sending message on tool selection
|
|
140
|
+
e.stopPropagation();
|
|
141
|
+
} },
|
|
142
|
+
selectedToolNames.includes(namedTool.name) ? (React.createElement(CheckIcon, { sx: {
|
|
143
|
+
marginRight: '8px',
|
|
144
|
+
color: 'var(--jp-brand-color1, #2196F3)'
|
|
145
|
+
} })) : (React.createElement("div", { style: { width: '24px', marginRight: '8px' } })),
|
|
146
|
+
React.createElement(Typography, { variant: "body2" }, namedTool.name))))),
|
|
147
|
+
providerToolNames.length > 0 && tools.length > 0 && React.createElement(Divider, null),
|
|
148
|
+
providerToolNames.length > 0 && (React.createElement(MenuItem, { disabled: true },
|
|
149
|
+
React.createElement(Typography, { variant: "caption" }, trans.__('Provider Tools')))),
|
|
150
|
+
providerToolNames.map(toolName => {
|
|
151
|
+
return (React.createElement(Tooltip, { key: toolName, title: trans.__('Enabled via provider settings.'), placement: "left" },
|
|
152
|
+
React.createElement(MenuItem, { className: SELECT_ITEM_CLASS, onClick: e => {
|
|
153
|
+
// Keep provider-managed tools read-only from this menu.
|
|
154
|
+
e.stopPropagation();
|
|
155
|
+
} },
|
|
156
|
+
React.createElement(CheckIcon, { sx: {
|
|
157
|
+
marginRight: '8px',
|
|
158
|
+
color: 'text.disabled'
|
|
159
|
+
} }),
|
|
160
|
+
React.createElement(Typography, { variant: "body2" }, toolName))));
|
|
161
|
+
}))));
|
|
105
162
|
}
|
|
106
163
|
/**
|
|
107
164
|
* Factory function returning the toolbar item for tool selection.
|
|
108
165
|
*/
|
|
109
|
-
export function createToolSelectItem(toolRegistry, toolsEnabled = true, translator) {
|
|
166
|
+
export function createToolSelectItem(toolRegistry, settingsModel, providerRegistry, toolsEnabled = true, translator) {
|
|
110
167
|
return {
|
|
111
168
|
element: (props) => {
|
|
112
169
|
const onToolSelectionChange = (tools) => {
|
|
@@ -120,6 +177,8 @@ export function createToolSelectItem(toolRegistry, toolsEnabled = true, translat
|
|
|
120
177
|
const toolSelectProps = {
|
|
121
178
|
...props,
|
|
122
179
|
toolRegistry,
|
|
180
|
+
settingsModel,
|
|
181
|
+
providerRegistry,
|
|
123
182
|
onToolSelectionChange,
|
|
124
183
|
toolsEnabled,
|
|
125
184
|
translator
|
package/lib/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
2
|
-
import { IChatCommandRegistry, IInputToolbarRegistryFactory } from '@jupyter/chat';
|
|
2
|
+
import { IChatCommandRegistry, IChatTracker, IInputToolbarRegistryFactory } from '@jupyter/chat';
|
|
3
3
|
import { AgentManagerFactory } from './agent';
|
|
4
|
-
import { IProviderRegistry, IToolRegistry, ISkillRegistry,
|
|
4
|
+
import { IProviderRegistry, IToolRegistry, ISkillRegistry, IChatModelHandler, IDiffManager } from './tokens';
|
|
5
5
|
import { AISettingsModel } from './models/settings-model';
|
|
6
|
-
declare const _default: (JupyterFrontEndPlugin<IProviderRegistry> | JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<IChatCommandRegistry> | JupyterFrontEndPlugin<
|
|
6
|
+
declare const _default: (JupyterFrontEndPlugin<IProviderRegistry> | JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<IChatCommandRegistry> | JupyterFrontEndPlugin<IChatModelHandler> | JupyterFrontEndPlugin<IChatTracker> | JupyterFrontEndPlugin<AgentManagerFactory> | JupyterFrontEndPlugin<AISettingsModel> | JupyterFrontEndPlugin<IDiffManager> | JupyterFrontEndPlugin<ISkillRegistry> | JupyterFrontEndPlugin<IToolRegistry> | JupyterFrontEndPlugin<IInputToolbarRegistryFactory>)[];
|
|
7
7
|
export default _default;
|
|
8
8
|
export * from './tokens';
|
|
9
9
|
export * from './icons';
|