@jupyterlite/ai 0.13.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 CHANGED
@@ -317,6 +317,18 @@ export declare class AgentManager {
317
317
  * Build an instruction line describing MIME types supported by this session.
318
318
  */
319
319
  private _getSupportedMimeTypesInstruction;
320
+ /**
321
+ * Sanitizes history to ensure it's in a valid state in case of abort or error.
322
+ */
323
+ private _sanitizeHistory;
324
+ /**
325
+ * Extracts tool call IDs from a message
326
+ */
327
+ private _getToolCallIds;
328
+ /**
329
+ * Checks if a tool message contains results for all specified tool call IDs
330
+ */
331
+ private _matchesAllToolCalls;
320
332
  private _settingsModel;
321
333
  private _toolRegistry?;
322
334
  private _providerRegistry?;
package/lib/agent.js CHANGED
@@ -33,6 +33,16 @@ export class AgentManagerFactory {
33
33
  secretsManager: this._secretsManager
34
34
  });
35
35
  this._agentManagers.push(agentManager);
36
+ // New chats can be created before MCP setup finishes.
37
+ // Reinitialize them with connected MCP tools once it does.
38
+ this._initQueue
39
+ .then(() => this.getMCPTools())
40
+ .then(mcpTools => {
41
+ if (Object.keys(mcpTools).length > 0) {
42
+ agentManager.initializeAgent(mcpTools);
43
+ }
44
+ })
45
+ .catch(error => console.warn('Failed to pass MCP tools to new agent:', error));
36
46
  return agentManager;
37
47
  }
38
48
  /**
@@ -406,6 +416,9 @@ export class AgentManager {
406
416
  data: { error: error }
407
417
  });
408
418
  }
419
+ // After an error (including AbortError), sanitize the history
420
+ // to remove any trailing assistant messages without tool results
421
+ this._sanitizeHistory();
409
422
  }
410
423
  finally {
411
424
  this._controller = null;
@@ -838,6 +851,81 @@ WEB RETRIEVAL POLICY:
838
851
  }
839
852
  return `Supported MIME types in this session: ${safeMimeTypes.join(', ')}`;
840
853
  }
854
+ /**
855
+ * Sanitizes history to ensure it's in a valid state in case of abort or error.
856
+ */
857
+ _sanitizeHistory() {
858
+ if (this._history.length === 0) {
859
+ return;
860
+ }
861
+ const newHistory = [];
862
+ for (let i = 0; i < this._history.length; i++) {
863
+ const msg = this._history[i];
864
+ if (msg.role === 'assistant') {
865
+ const toolCallIds = this._getToolCallIds(msg);
866
+ if (toolCallIds.length > 0) {
867
+ // Find if there's a following tool message with results for these calls
868
+ const nextMsg = this._history[i + 1];
869
+ if (nextMsg &&
870
+ nextMsg.role === 'tool' &&
871
+ this._matchesAllToolCalls(nextMsg, toolCallIds)) {
872
+ newHistory.push(msg);
873
+ }
874
+ else {
875
+ // Message has unmatched tool calls drop it and everything after it
876
+ break;
877
+ }
878
+ }
879
+ else {
880
+ newHistory.push(msg);
881
+ }
882
+ }
883
+ else if (msg.role === 'tool') {
884
+ // Tool messages are valid if they were preceded by a valid assistant message
885
+ newHistory.push(msg);
886
+ }
887
+ else {
888
+ newHistory.push(msg);
889
+ }
890
+ }
891
+ this._history = newHistory;
892
+ }
893
+ /**
894
+ * Extracts tool call IDs from a message
895
+ */
896
+ _getToolCallIds(message) {
897
+ const ids = [];
898
+ // Check content array for tool-call parts
899
+ if (Array.isArray(message.content)) {
900
+ for (const part of message.content) {
901
+ if (typeof part === 'object' &&
902
+ part !== null &&
903
+ 'type' in part &&
904
+ part.type === 'tool-call') {
905
+ ids.push(part.toolCallId);
906
+ }
907
+ }
908
+ }
909
+ return ids;
910
+ }
911
+ /**
912
+ * Checks if a tool message contains results for all specified tool call IDs
913
+ */
914
+ _matchesAllToolCalls(message, callIds) {
915
+ if (message.role !== 'tool' || !Array.isArray(message.content)) {
916
+ return false;
917
+ }
918
+ const resultIds = new Set();
919
+ for (const part of message.content) {
920
+ if (typeof part === 'object' &&
921
+ part !== null &&
922
+ 'type' in part &&
923
+ part.type === 'tool-result') {
924
+ resultIds.add(part.toolCallId);
925
+ }
926
+ }
927
+ return callIds.every(id => resultIds.has(id));
928
+ }
841
929
  // Private attributes
842
930
  _settingsModel;
843
931
  _toolRegistry;
package/lib/index.js CHANGED
@@ -33,6 +33,45 @@ import { createDiscoverSkillsTool, createLoadSkillTool } from './tools/skills';
33
33
  import { createBrowserFetchTool } from './tools/web';
34
34
  import { AISettingsWidget } from './widgets/ai-settings';
35
35
  import { MainAreaChat } from './widgets/main-area-chat';
36
+ var Private;
37
+ (function (Private) {
38
+ let aiSecretsToken = null;
39
+ function setAISecretsToken(token) {
40
+ aiSecretsToken = token;
41
+ }
42
+ Private.setAISecretsToken = setAISecretsToken;
43
+ function createAISecretsAccess(secretsManager) {
44
+ return {
45
+ get isAvailable() {
46
+ return !!(aiSecretsToken && secretsManager);
47
+ },
48
+ async get(id) {
49
+ if (!aiSecretsToken || !secretsManager) {
50
+ return;
51
+ }
52
+ const secret = await secretsManager.get(aiSecretsToken, SECRETS_NAMESPACE, id);
53
+ return secret?.value;
54
+ },
55
+ async set(id, value) {
56
+ if (!aiSecretsToken || !secretsManager) {
57
+ return;
58
+ }
59
+ await secretsManager.set(aiSecretsToken, SECRETS_NAMESPACE, id, {
60
+ namespace: SECRETS_NAMESPACE,
61
+ id,
62
+ value
63
+ });
64
+ },
65
+ async attach(id, input, callback) {
66
+ if (!aiSecretsToken || !secretsManager) {
67
+ return;
68
+ }
69
+ await secretsManager.attach(aiSecretsToken, SECRETS_NAMESPACE, id, input, callback);
70
+ }
71
+ };
72
+ }
73
+ Private.createAISecretsAccess = createAISecretsAccess;
74
+ })(Private || (Private = {}));
36
75
  /**
37
76
  * Provider registry plugin
38
77
  */
@@ -204,6 +243,11 @@ const plugin = {
204
243
  attachmentOpenerRegistry.set('notebook', attachment => {
205
244
  app.commands.execute('docmanager:open', { path: attachment.value });
206
245
  });
246
+ const openSettings = () => {
247
+ if (app.commands.hasCommand(CommandIds.openSettings)) {
248
+ void app.commands.execute(CommandIds.openSettings);
249
+ }
250
+ };
207
251
  // Create ActiveCellManager if notebook tracker is available, and add it to the
208
252
  // model registry.
209
253
  let activeCellManager;
@@ -229,7 +273,7 @@ const plugin = {
229
273
  provider = settingsModel.getDefaultProvider()?.id;
230
274
  if (!provider) {
231
275
  showErrorMessage('Error creating chat', 'Please set up a provider');
232
- app.commands.execute('@jupyterlite/ai:open-settings');
276
+ openSettings();
233
277
  return {};
234
278
  }
235
279
  }
@@ -262,13 +306,30 @@ const plugin = {
262
306
  chatPanel.title.icon = chatIcon;
263
307
  chatPanel.title.caption = trans.__('Chat with AI assistant');
264
308
  chatPanel.toolbar.addItem('spacer', Toolbar.createSpacerItem());
265
- chatPanel.toolbar.addItem('settings', new ToolbarButton({
266
- icon: settingsIcon,
267
- onClick: () => {
268
- app.commands.execute('@jupyterlite/ai:open-settings');
269
- },
270
- tooltip: trans.__('Open AI Settings')
271
- }));
309
+ const addSettingsButton = () => {
310
+ chatPanel.toolbar.addItem('settings', new ToolbarButton({
311
+ icon: settingsIcon,
312
+ onClick: openSettings,
313
+ tooltip: trans.__('Open AI Settings')
314
+ }));
315
+ };
316
+ if (app.commands.hasCommand(CommandIds.openSettings)) {
317
+ addSettingsButton();
318
+ }
319
+ else {
320
+ const disconnectSettingsButtonListener = () => {
321
+ app.commands.commandChanged.disconnect(onCommandChanged);
322
+ chatPanel.disposed.disconnect(disconnectSettingsButtonListener);
323
+ };
324
+ const onCommandChanged = (_, args) => {
325
+ if (args.id === CommandIds.openSettings && args.type === 'added') {
326
+ disconnectSettingsButtonListener();
327
+ addSettingsButton();
328
+ }
329
+ };
330
+ app.commands.commandChanged.connect(onCommandChanged);
331
+ chatPanel.disposed.connect(disconnectSettingsButtonListener);
332
+ }
272
333
  let tokenUsageWidget = null;
273
334
  chatPanel.chatOpened.connect((_, widget) => {
274
335
  const model = widget.model;
@@ -562,59 +623,78 @@ function registerCommands(app, rmRegistry, chatPanel, attachmentOpenerRegistry,
562
623
  }
563
624
  }
564
625
  /**
565
- * A plugin to provide the agent manager factory, the completion provider and
566
- * the settings model.
567
- * All these objects require the secrets manager token with the same namespace.
626
+ * A plugin to provide the agent manager factory and completion provider.
627
+ * These objects require the secrets manager token with the same namespace.
568
628
  */
569
- const agentManagerFactory = SecretsManager.sign(SECRETS_NAMESPACE, token => ({
570
- id: SECRETS_NAMESPACE,
571
- description: 'Provide the AI agent manager',
629
+ const agentManagerFactory = SecretsManager.sign(SECRETS_NAMESPACE, token => {
630
+ Private.setAISecretsToken(token);
631
+ return {
632
+ id: SECRETS_NAMESPACE,
633
+ description: 'Provide the AI agent manager',
634
+ autoStart: true,
635
+ provides: IAgentManagerFactory,
636
+ requires: [IAISettingsModel, IProviderRegistry],
637
+ optional: [ISkillRegistry, ICompletionProviderManager, ISecretsManager],
638
+ activate: (app, settingsModel, providerRegistry, skillRegistry, completionManager, secretsManager) => {
639
+ const agentManagerFactory = new AgentManagerFactory({
640
+ settingsModel,
641
+ skillRegistry,
642
+ secretsManager,
643
+ token
644
+ });
645
+ // Build the completion provider
646
+ if (completionManager) {
647
+ const completionProvider = new AICompletionProvider({
648
+ settingsModel,
649
+ providerRegistry,
650
+ secretsManager,
651
+ token
652
+ });
653
+ completionManager.registerInlineProvider(completionProvider);
654
+ }
655
+ else {
656
+ console.info('Completion provider manager not available, skipping AI completion setup');
657
+ }
658
+ return agentManagerFactory;
659
+ }
660
+ };
661
+ });
662
+ /**
663
+ * AI settings panel plugin.
664
+ */
665
+ const settingsPanelPlugin = {
666
+ id: '@jupyterlite/ai:settings-panel',
667
+ description: 'Provide the AI settings panel',
572
668
  autoStart: true,
573
- provides: IAgentManagerFactory,
574
- requires: [IAISettingsModel, IProviderRegistry],
669
+ requires: [IAISettingsModel, IAgentManagerFactory, IProviderRegistry],
575
670
  optional: [
576
- ISkillRegistry,
577
671
  ICommandPalette,
578
- ICompletionProviderManager,
579
672
  ILayoutRestorer,
580
673
  ISecretsManager,
581
674
  IThemeManager,
582
675
  ITranslator
583
676
  ],
584
- activate: (app, settingsModel, providerRegistry, skillRegistry, palette, completionManager, restorer, secretsManager, themeManager, translator) => {
677
+ activate: (app, settingsModel, agentManagerFactory, providerRegistry, palette, restorer, secretsManager, themeManager, translator) => {
585
678
  const trans = (translator ?? nullTranslator).load('jupyterlite_ai');
586
- const agentManagerFactory = new AgentManagerFactory({
587
- settingsModel,
588
- skillRegistry,
589
- secretsManager,
590
- token
591
- });
592
- // Build the settings panel
679
+ const secretsAccess = Private.createAISecretsAccess(secretsManager);
593
680
  const settingsWidget = new AISettingsWidget({
594
681
  settingsModel,
595
682
  agentManagerFactory,
596
683
  themeManager,
597
684
  providerRegistry,
598
- secretsManager,
599
- token,
685
+ secretsAccess,
600
686
  trans
601
687
  });
602
- settingsWidget.id = 'jupyterlite-ai-settings';
603
688
  settingsWidget.title.icon = settingsIcon;
604
689
  settingsWidget.title.iconClass = 'jp-ai-settings-icon';
605
- // Build the completion provider
606
- if (completionManager) {
607
- const completionProvider = new AICompletionProvider({
608
- settingsModel,
609
- providerRegistry,
610
- secretsManager,
611
- token
612
- });
613
- completionManager.registerInlineProvider(completionProvider);
614
- }
615
- else {
616
- console.info('Completion provider manager not available, skipping AI completion setup');
617
- }
690
+ const open = () => {
691
+ let widget = Array.from(app.shell.widgets('main')).find(w => w.id === 'jupyterlite-ai-settings');
692
+ if (!widget) {
693
+ widget = settingsWidget;
694
+ app.shell.add(widget, 'main');
695
+ }
696
+ app.shell.activateById(widget.id);
697
+ };
618
698
  if (restorer) {
619
699
  restorer.add(settingsWidget, settingsWidget.id);
620
700
  }
@@ -624,31 +704,20 @@ const agentManagerFactory = SecretsManager.sign(SECRETS_NAMESPACE, token => ({
624
704
  icon: settingsIcon,
625
705
  iconClass: 'jp-ai-settings-icon',
626
706
  execute: () => {
627
- // Check if the widget already exists in shell
628
- let widget = Array.from(app.shell.widgets('main')).find(w => w.id === 'jupyterlite-ai-settings');
629
- if (!widget && settingsWidget) {
630
- // Use the pre-created widget
631
- widget = settingsWidget;
632
- app.shell.add(widget, 'main');
633
- }
634
- if (widget) {
635
- app.shell.activateById(widget.id);
636
- }
707
+ open();
637
708
  },
638
709
  describedBy: {
639
710
  args: {}
640
711
  }
641
712
  });
642
- // Add to command palette if available
643
713
  if (palette) {
644
714
  palette.addItem({
645
715
  command: CommandIds.openSettings,
646
716
  category: trans.__('AI Assistant')
647
717
  });
648
718
  }
649
- return agentManagerFactory;
650
719
  }
651
- }));
720
+ };
652
721
  /**
653
722
  * Built-in completion providers plugin
654
723
  */
@@ -894,6 +963,7 @@ export default [
894
963
  plugin,
895
964
  toolRegistry,
896
965
  agentManagerFactory,
966
+ settingsPanelPlugin,
897
967
  inputToolbarFactory,
898
968
  completionStatus,
899
969
  skillsPlugin
@@ -12,6 +12,7 @@ export const anthropicProvider = {
12
12
  apiKeyRequirement: 'required',
13
13
  defaultModels: [
14
14
  'claude-opus-4-6',
15
+ 'claude-sonnet-4-6',
15
16
  'claude-opus-4-5',
16
17
  'claude-opus-4-5-20251101',
17
18
  'claude-sonnet-4-5',
@@ -24,10 +25,6 @@ export const anthropicProvider = {
24
25
  'claude-opus-4-20250514',
25
26
  'claude-sonnet-4-0',
26
27
  'claude-sonnet-4-20250514',
27
- 'claude-3-7-sonnet-latest',
28
- 'claude-3-7-sonnet-20250219',
29
- 'claude-3-5-haiku-latest',
30
- 'claude-3-5-haiku-20241022',
31
28
  'claude-3-haiku-20240307'
32
29
  ],
33
30
  supportsBaseURL: true,
@@ -60,6 +57,9 @@ export const googleProvider = {
60
57
  name: 'Google Generative AI',
61
58
  apiKeyRequirement: 'required',
62
59
  defaultModels: [
60
+ 'gemini-3.1-pro-preview',
61
+ 'gemini-3.1-pro-preview-customtools',
62
+ 'gemini-3.1-flash-image-preview',
63
63
  'gemini-3-pro-preview',
64
64
  'gemini-3-pro-image-preview',
65
65
  'gemini-3-flash-preview',
@@ -68,33 +68,21 @@ export const googleProvider = {
68
68
  'gemini-2.5-flash-image',
69
69
  'gemini-2.5-flash-lite',
70
70
  'gemini-2.5-flash-lite-preview-09-2025',
71
- 'gemini-2.5-flash-preview-04-17',
72
- 'gemini-2.5-flash-preview-09-2025',
73
- 'gemini-2.5-pro-exp-03-25',
71
+ 'gemini-2.5-computer-use-preview-10-2025',
74
72
  'gemini-2.0-flash',
75
73
  'gemini-2.0-flash-001',
76
- 'gemini-2.0-flash-live-001',
77
74
  'gemini-2.0-flash-lite',
78
- 'gemini-2.0-pro-exp-02-05',
79
- 'gemini-2.0-flash-thinking-exp-01-21',
80
- 'gemini-2.0-flash-exp',
81
- 'gemini-1.5-flash',
82
- 'gemini-1.5-flash-latest',
83
- 'gemini-1.5-flash-001',
84
- 'gemini-1.5-flash-002',
85
- 'gemini-1.5-flash-8b',
86
- 'gemini-1.5-flash-8b-latest',
87
- 'gemini-1.5-flash-8b-001',
88
- 'gemini-1.5-pro',
89
- 'gemini-1.5-pro-latest',
90
- 'gemini-1.5-pro-001',
91
- 'gemini-1.5-pro-002',
75
+ 'gemini-2.0-flash-lite-001',
92
76
  'gemini-pro-latest',
93
77
  'gemini-flash-latest',
94
78
  'gemini-flash-lite-latest',
95
- 'gemini-exp-1206',
79
+ 'deep-research-pro-preview-12-2025',
80
+ 'gemma-3-27b-it',
96
81
  'gemma-3-12b-it',
97
- 'gemma-3-27b-it'
82
+ 'gemma-3-4b-it',
83
+ 'gemma-3-1b-it',
84
+ 'gemma-3n-e4b-it',
85
+ 'gemma-3n-e2b-it'
98
86
  ],
99
87
  supportsBaseURL: true,
100
88
  factory: (options) => {
@@ -157,10 +145,13 @@ export const openaiProvider = {
157
145
  apiKeyRequirement: 'required',
158
146
  defaultModels: [
159
147
  'gpt-5.2',
148
+ 'gpt-5.2-2025-12-11',
160
149
  'gpt-5.2-chat-latest',
161
150
  'gpt-5.2-pro',
151
+ 'gpt-5.2-pro-2025-12-11',
162
152
  'gpt-5.2-codex',
163
153
  'gpt-5.1',
154
+ 'gpt-5.1-2025-11-13',
164
155
  'gpt-5.1-chat-latest',
165
156
  'gpt-5',
166
157
  'gpt-5-2025-08-07',
@@ -177,8 +168,6 @@ export const openaiProvider = {
177
168
  'o3-mini-2025-01-31',
178
169
  'o1',
179
170
  'o1-2024-12-17',
180
- 'gpt-4.5-preview',
181
- 'gpt-4.5-preview-2025-02-27',
182
171
  'gpt-4.1',
183
172
  'gpt-4.1-2025-04-14',
184
173
  'gpt-4.1-mini',
@@ -189,16 +178,21 @@ export const openaiProvider = {
189
178
  'gpt-4o-2024-05-13',
190
179
  'gpt-4o-2024-08-06',
191
180
  'gpt-4o-2024-11-20',
181
+ 'gpt-4o-audio-preview',
182
+ 'gpt-4o-audio-preview-2024-12-17',
183
+ 'gpt-4o-audio-preview-2025-06-03',
192
184
  'gpt-4o-mini',
193
185
  'gpt-4o-mini-2024-07-18',
194
- 'chatgpt-4o-latest',
195
- 'gpt-4-turbo',
196
- 'gpt-4-turbo-2024-04-09',
197
- 'gpt-4',
198
- 'gpt-4-0613',
186
+ 'gpt-4o-mini-audio-preview',
187
+ 'gpt-4o-mini-audio-preview-2024-12-17',
188
+ 'gpt-4o-search-preview',
189
+ 'gpt-4o-search-preview-2025-03-11',
190
+ 'gpt-4o-mini-search-preview',
191
+ 'gpt-4o-mini-search-preview-2025-03-11',
199
192
  'gpt-3.5-turbo',
200
193
  'gpt-3.5-turbo-0125',
201
- 'gpt-3.5-turbo-1106'
194
+ 'gpt-3.5-turbo-1106',
195
+ 'gpt-3.5-turbo-16k'
202
196
  ],
203
197
  supportsBaseURL: true,
204
198
  supportsHeaders: true,
package/lib/tokens.d.ts CHANGED
@@ -253,6 +253,27 @@ export interface IProviderRegistry {
253
253
  * Token for the AI settings model.
254
254
  */
255
255
  export declare const IAISettingsModel: Token<AISettingsModel>;
256
+ /**
257
+ * Internal interface for AI provider secret access within the shared namespace.
258
+ */
259
+ export interface IAISecretsAccess {
260
+ /**
261
+ * Whether secrets access is currently available.
262
+ */
263
+ readonly isAvailable: boolean;
264
+ /**
265
+ * Get a secret value by ID.
266
+ */
267
+ get(id: string): Promise<string | undefined>;
268
+ /**
269
+ * Set a secret value by ID.
270
+ */
271
+ set(id: string, value: string): Promise<void>;
272
+ /**
273
+ * Attach an input field to a secret ID.
274
+ */
275
+ attach(id: string, input: HTMLInputElement, callback?: (value: string) => void): Promise<void>;
276
+ }
256
277
  /**
257
278
  * Token for the agent manager.
258
279
  */
@@ -1,6 +1,56 @@
1
1
  import { Widget } from '@lumino/widgets';
2
2
  import { tool } from 'ai';
3
3
  import { z } from 'zod';
4
+ /**
5
+ * Search commands using case-insensitive term matching across command metadata.
6
+ *
7
+ * Multi-word queries are split into individual terms, and every term must be
8
+ * contained in at least one searchable field. Results are ranked by stronger
9
+ * field matches while keeping a stable fallback order.
10
+ */
11
+ function searchCommands(commands, query) {
12
+ const normalizedQuery = query.trim().toLowerCase();
13
+ if (!normalizedQuery) {
14
+ return commands;
15
+ }
16
+ const terms = normalizedQuery.split(/\s+/).filter(Boolean);
17
+ return commands
18
+ .map((command, index) => {
19
+ const fields = [
20
+ { value: command.label, weight: 4 },
21
+ { value: command.caption, weight: 3 },
22
+ { value: command.id, weight: 2 },
23
+ { value: command.description, weight: 1 }
24
+ ];
25
+ const normalizedFields = fields.map(field => ({
26
+ normalizedValue: field.value?.toLowerCase() ?? '',
27
+ weight: field.weight
28
+ }));
29
+ const matchesAllTerms = terms.every(term => normalizedFields.some(field => field.normalizedValue.includes(term)));
30
+ if (!matchesAllTerms) {
31
+ return null;
32
+ }
33
+ const score = normalizedFields.reduce((total, field) => {
34
+ if (!field.normalizedValue) {
35
+ return total;
36
+ }
37
+ let fieldScore = 0;
38
+ if (field.normalizedValue.includes(normalizedQuery)) {
39
+ fieldScore += field.weight * 4;
40
+ }
41
+ else {
42
+ fieldScore +=
43
+ terms.filter(term => field.normalizedValue.includes(term)).length *
44
+ field.weight;
45
+ }
46
+ return total + fieldScore;
47
+ }, 0);
48
+ return { command, index, score };
49
+ })
50
+ .filter((result) => result !== null)
51
+ .sort((a, b) => b.score - a.score || a.index - b.index)
52
+ .map(result => result.command);
53
+ }
4
54
  /**
5
55
  * Create a tool to discover all available commands and their metadata
6
56
  */
@@ -13,41 +63,29 @@ export function createDiscoverCommandsTool(commands) {
13
63
  .string()
14
64
  .optional()
15
65
  .nullable()
16
- .describe("Optional search query to filter commands. It doesn't need to be provided to list all commands")
66
+ .describe('Optional search query to filter commands. Supports multi-word queries (whitespace-separated) by requiring each word to be contained in the command id, label, caption, or description. Leave empty to list all commands.')
17
67
  }),
18
68
  execute: async (input) => {
19
69
  const { query } = input;
20
- const commandList = [];
21
- // Get all command IDs
70
+ // Build the full command list first.
22
71
  const commandIds = commands.listCommands();
72
+ const allCommands = [];
23
73
  for (const id of commandIds) {
24
- // Get command metadata using various CommandRegistry methods
25
74
  const description = await commands.describedBy(id);
26
75
  const label = commands.label(id);
27
76
  const caption = commands.caption(id);
28
77
  const usage = commands.usage(id);
29
- const command = {
78
+ allCommands.push({
30
79
  id,
31
80
  label: label || undefined,
32
81
  caption: caption || undefined,
33
82
  description: usage || undefined,
34
83
  args: description?.args || undefined
35
- };
36
- // Filter by query if provided
37
- if (query) {
38
- const searchTerm = query.toLowerCase();
39
- const matchesQuery = id.toLowerCase().includes(searchTerm) ||
40
- label?.toLowerCase().includes(searchTerm) ||
41
- caption?.toLowerCase().includes(searchTerm) ||
42
- usage?.toLowerCase().includes(searchTerm);
43
- if (matchesQuery) {
44
- commandList.push(command);
45
- }
46
- }
47
- else {
48
- commandList.push(command);
49
- }
84
+ });
50
85
  }
86
+ const commandList = query
87
+ ? searchCommands(allCommands, query)
88
+ : allCommands;
51
89
  return {
52
90
  success: true,
53
91
  commandCount: commandList.length,
@@ -1,11 +1,10 @@
1
1
  import { IThemeManager } from '@jupyterlab/apputils';
2
2
  import { ReactWidget } from '@jupyterlab/ui-components';
3
3
  import type { TranslationBundle } from '@jupyterlab/translation';
4
- import { ISecretsManager } from 'jupyter-secrets-manager';
5
4
  import React from 'react';
6
5
  import { AgentManagerFactory } from '../agent';
7
6
  import { AISettingsModel } from '../models/settings-model';
8
- import { type IProviderRegistry } from '../tokens';
7
+ import { type IAISecretsAccess, type IProviderRegistry } from '../tokens';
9
8
  /**
10
9
  * A JupyterLab widget for AI settings configuration
11
10
  */
@@ -24,7 +23,7 @@ export declare class AISettingsWidget extends ReactWidget {
24
23
  private _agentManagerFactory?;
25
24
  private _themeManager?;
26
25
  private _providerRegistry;
27
- private _secretsManager?;
26
+ private _secretsAccess?;
28
27
  private _trans;
29
28
  }
30
29
  /**
@@ -40,13 +39,9 @@ export declare namespace AISettingsWidget {
40
39
  themeManager?: IThemeManager;
41
40
  providerRegistry: IProviderRegistry;
42
41
  /**
43
- * The secrets manager.
42
+ * Access to provider secrets in the shared namespace.
44
43
  */
45
- secretsManager?: ISecretsManager;
46
- /**
47
- * The token used to request the secrets manager.
48
- */
49
- token: symbol | null;
44
+ secretsAccess: IAISecretsAccess;
50
45
  /**
51
46
  * The application language translation bundle.
52
47
  */