@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.
Files changed (49) hide show
  1. package/lib/agent.d.ts +36 -2
  2. package/lib/agent.js +249 -24
  3. package/lib/{chat-model-registry.d.ts → chat-model-handler.d.ts} +12 -11
  4. package/lib/{chat-model-registry.js → chat-model-handler.js} +6 -40
  5. package/lib/chat-model.d.ts +8 -0
  6. package/lib/chat-model.js +156 -8
  7. package/lib/completion/completion-provider.d.ts +1 -1
  8. package/lib/completion/completion-provider.js +14 -2
  9. package/lib/components/model-select.js +4 -4
  10. package/lib/components/tool-select.d.ts +11 -2
  11. package/lib/components/tool-select.js +77 -18
  12. package/lib/index.d.ts +3 -3
  13. package/lib/index.js +249 -117
  14. package/lib/models/settings-model.d.ts +2 -0
  15. package/lib/models/settings-model.js +2 -0
  16. package/lib/providers/built-in-providers.js +33 -32
  17. package/lib/providers/provider-tools.d.ts +36 -0
  18. package/lib/providers/provider-tools.js +93 -0
  19. package/lib/rendered-message-outputarea.d.ts +24 -0
  20. package/lib/rendered-message-outputarea.js +48 -0
  21. package/lib/tokens.d.ts +65 -7
  22. package/lib/tokens.js +1 -1
  23. package/lib/tools/commands.js +62 -22
  24. package/lib/tools/web.d.ts +8 -0
  25. package/lib/tools/web.js +196 -0
  26. package/lib/widgets/ai-settings.d.ts +4 -9
  27. package/lib/widgets/ai-settings.js +123 -69
  28. package/lib/widgets/main-area-chat.d.ts +6 -0
  29. package/lib/widgets/main-area-chat.js +28 -0
  30. package/lib/widgets/provider-config-dialog.js +211 -11
  31. package/package.json +17 -11
  32. package/schema/settings-model.json +89 -1
  33. package/src/agent.ts +327 -42
  34. package/src/{chat-model-registry.ts → chat-model-handler.ts} +16 -51
  35. package/src/chat-model.ts +223 -14
  36. package/src/completion/completion-provider.ts +26 -12
  37. package/src/components/model-select.tsx +4 -5
  38. package/src/components/tool-select.tsx +110 -7
  39. package/src/index.ts +359 -184
  40. package/src/models/settings-model.ts +6 -0
  41. package/src/providers/built-in-providers.ts +33 -32
  42. package/src/providers/provider-tools.ts +179 -0
  43. package/src/rendered-message-outputarea.ts +62 -0
  44. package/src/tokens.ts +82 -9
  45. package/src/tools/commands.ts +99 -31
  46. package/src/tools/web.ts +238 -0
  47. package/src/widgets/ai-settings.tsx +279 -124
  48. package/src/widgets/main-area-chat.ts +34 -1
  49. 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 = event.data.fullContent;
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 = event.data.content;
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.output);
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
- return `<details class="jp-ai-tool-call ${config.cssClass}"${openAttr}>
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 = {}));
@@ -80,6 +80,6 @@ export declare namespace AICompletionProvider {
80
80
  /**
81
81
  * The token used to request the secrets manager.
82
82
  */
83
- token: symbol;
83
+ token: symbol | null;
84
84
  }
85
85
  }
@@ -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
- apiKey =
119
- (await this._secretsManager.get(Private.getToken(), SECRETS_NAMESPACE, `${provider}:apiKey`))?.value ?? '';
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(--jp-brand-color3, rgba(33, 150, 243, 0.1))'
118
+ ? 'var(--mui-palette-primary-main)'
119
119
  : 'transparent',
120
120
  '&:hover': {
121
121
  backgroundColor: isSelected
122
- ? 'var(--jp-brand-color3, rgba(33, 150, 243, 0.15))'
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-brand-color1, #2196F3)',
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-brand-color1, #2196F3)'
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)', selectedToolNames.length.toString(), tools.length.toString()), buttonProps: {
67
- ...(selectedToolNames.length === 0 && {
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: selectedToolNames.length === 0
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
- } }, tools.map(namedTool => (React.createElement(Tooltip, { key: namedTool.name, title: namedTool.tool.description || namedTool.name, placement: "left" },
95
- React.createElement(MenuItem, { className: SELECT_ITEM_CLASS, onClick: e => {
96
- toggleTool(namedTool.name);
97
- // Prevent sending message on tool selection
98
- e.stopPropagation();
99
- } },
100
- selectedToolNames.includes(namedTool.name) ? (React.createElement(CheckIcon, { sx: {
101
- marginRight: '8px',
102
- color: 'var(--jp-brand-color1, #2196F3)'
103
- } })) : (React.createElement("div", { style: { width: '24px', marginRight: '8px' } })),
104
- React.createElement(Typography, { variant: "body2" }, namedTool.name))))))));
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, IChatModelRegistry, IDiffManager } from './tokens';
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<IChatModelRegistry> | JupyterFrontEndPlugin<AgentManagerFactory> | JupyterFrontEndPlugin<AISettingsModel> | JupyterFrontEndPlugin<IDiffManager> | JupyterFrontEndPlugin<ISkillRegistry> | JupyterFrontEndPlugin<IToolRegistry> | JupyterFrontEndPlugin<IInputToolbarRegistryFactory>)[];
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';