@jupyterlite/ai 0.9.0-a3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +20 -89
  2. package/lib/agent.d.ts +10 -4
  3. package/lib/agent.js +30 -17
  4. package/lib/chat-model.d.ts +6 -0
  5. package/lib/chat-model.js +144 -17
  6. package/lib/completion/completion-provider.js +1 -13
  7. package/lib/components/completion-status.d.ts +20 -0
  8. package/lib/components/completion-status.js +51 -0
  9. package/lib/components/index.d.ts +1 -0
  10. package/lib/components/index.js +1 -0
  11. package/lib/components/model-select.js +1 -2
  12. package/lib/diff-manager.d.ts +25 -0
  13. package/lib/diff-manager.js +60 -0
  14. package/lib/icons.d.ts +0 -1
  15. package/lib/icons.js +2 -6
  16. package/lib/index.d.ts +2 -2
  17. package/lib/index.js +54 -23
  18. package/lib/models/settings-model.d.ts +4 -0
  19. package/lib/models/settings-model.js +24 -2
  20. package/lib/providers/built-in-providers.d.ts +0 -4
  21. package/lib/providers/built-in-providers.js +17 -23
  22. package/lib/tokens.d.ts +74 -0
  23. package/lib/tokens.js +4 -0
  24. package/lib/tools/commands.js +36 -35
  25. package/lib/tools/file.d.ts +10 -1
  26. package/lib/tools/file.js +235 -146
  27. package/lib/tools/notebook.d.ts +2 -3
  28. package/lib/tools/notebook.js +11 -11
  29. package/lib/widgets/ai-settings.js +78 -13
  30. package/lib/widgets/provider-config-dialog.js +15 -8
  31. package/package.json +5 -3
  32. package/schema/settings-model.json +25 -0
  33. package/src/agent.ts +35 -20
  34. package/src/chat-model.ts +182 -19
  35. package/src/completion/completion-provider.ts +1 -14
  36. package/src/components/completion-status.tsx +79 -0
  37. package/src/components/index.ts +1 -0
  38. package/src/components/model-select.tsx +0 -3
  39. package/src/diff-manager.ts +81 -0
  40. package/src/icons.ts +2 -7
  41. package/src/index.ts +74 -24
  42. package/src/models/settings-model.ts +28 -2
  43. package/src/providers/built-in-providers.ts +17 -24
  44. package/src/tokens.ts +78 -0
  45. package/src/tools/commands.ts +45 -40
  46. package/src/tools/file.ts +295 -164
  47. package/src/tools/notebook.ts +13 -14
  48. package/src/widgets/ai-settings.tsx +184 -35
  49. package/src/widgets/provider-config-dialog.tsx +43 -16
  50. package/style/base.css +14 -0
@@ -2,7 +2,6 @@ import { TooltippedButton } from '@jupyter/chat';
2
2
  import CheckIcon from '@mui/icons-material/Check';
3
3
  import { Menu, MenuItem, Typography } from '@mui/material';
4
4
  import React, { useCallback, useEffect, useState } from 'react';
5
- const SELECT_ITEM_CLASS = 'labai-model-select-item';
6
5
  /**
7
6
  * The model select component for choosing AI models.
8
7
  */
@@ -114,7 +113,7 @@ export function ModelSelect(props) {
114
113
  paddingRight: '2em',
115
114
  minWidth: '200px'
116
115
  }
117
- } }, availableModels.map(({ provider, providerLabel, isSelected }) => (React.createElement(MenuItem, { key: provider, className: SELECT_ITEM_CLASS, onClick: async (e) => {
116
+ } }, availableModels.map(({ provider, providerLabel, isSelected }) => (React.createElement(MenuItem, { key: provider, onClick: async (e) => {
118
117
  await selectModel(provider);
119
118
  // Prevent sending message on model selection
120
119
  e.stopPropagation();
@@ -0,0 +1,25 @@
1
+ import { CommandRegistry } from '@lumino/commands';
2
+ import { AISettingsModel } from './models/settings-model';
3
+ import { IDiffManager, IShowCellDiffParams, IShowFileDiffParams } from './tokens';
4
+ /**
5
+ * Implementation of the diff manager
6
+ */
7
+ export declare class DiffManager implements IDiffManager {
8
+ /**
9
+ * Construct a new DiffManager
10
+ */
11
+ constructor(options: {
12
+ commands: CommandRegistry;
13
+ settingsModel: AISettingsModel;
14
+ });
15
+ /**
16
+ * Show diff between original and modified cell content
17
+ */
18
+ showCellDiff(params: IShowCellDiffParams): Promise<void>;
19
+ /**
20
+ * Show diff between original and modified file content
21
+ */
22
+ showFileDiff(params: IShowFileDiffParams): Promise<void>;
23
+ private _commands;
24
+ private _settingsModel;
25
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Command IDs for unified cell diffs
3
+ */
4
+ const UNIFIED_DIFF_COMMAND_ID = 'jupyterlab-diff:unified-cell-diff';
5
+ /**
6
+ * Command IDs for split cell diffs
7
+ */
8
+ const SPLIT_DIFF_COMMAND_ID = 'jupyterlab-diff:split-cell-diff';
9
+ /**
10
+ * Command ID for unified file diffs
11
+ */
12
+ const UNIFIED_FILE_DIFF_COMMAND_ID = 'jupyterlab-diff:unified-file-diff';
13
+ /**
14
+ * Implementation of the diff manager
15
+ */
16
+ export class DiffManager {
17
+ /**
18
+ * Construct a new DiffManager
19
+ */
20
+ constructor(options) {
21
+ this._commands = options.commands;
22
+ this._settingsModel = options.settingsModel;
23
+ }
24
+ /**
25
+ * Show diff between original and modified cell content
26
+ */
27
+ async showCellDiff(params) {
28
+ if (!this._settingsModel.config.showCellDiff) {
29
+ return;
30
+ }
31
+ const showDiffCommandId = this._settingsModel.config.diffDisplayMode === 'unified'
32
+ ? UNIFIED_DIFF_COMMAND_ID
33
+ : SPLIT_DIFF_COMMAND_ID;
34
+ await this._commands.execute(showDiffCommandId, {
35
+ originalSource: params.original,
36
+ newSource: params.modified,
37
+ cellId: params.cellId,
38
+ showActionButtons: params.showActionButtons ?? true,
39
+ openDiff: params.openDiff ?? true,
40
+ notebookPath: params.notebookPath
41
+ });
42
+ }
43
+ /**
44
+ * Show diff between original and modified file content
45
+ */
46
+ async showFileDiff(params) {
47
+ if (!this._settingsModel.config.showFileDiff) {
48
+ return;
49
+ }
50
+ // File diffs only support unified view
51
+ await this._commands.execute(UNIFIED_FILE_DIFF_COMMAND_ID, {
52
+ originalSource: params.original,
53
+ newSource: params.modified,
54
+ filePath: params.filePath,
55
+ showActionButtons: params.showActionButtons ?? true
56
+ });
57
+ }
58
+ _commands;
59
+ _settingsModel;
60
+ }
package/lib/icons.d.ts CHANGED
@@ -1,4 +1,3 @@
1
1
  import { LabIcon } from '@jupyterlab/ui-components';
2
- export declare const labaiIcon: LabIcon;
3
2
  export declare const jupyternautIcon: LabIcon;
4
3
  export declare const AI_AVATAR: string;
package/lib/icons.js CHANGED
@@ -1,12 +1,8 @@
1
1
  import { LabIcon } from '@jupyterlab/ui-components';
2
- import labaiIconSvg from '../style/icons/jupyternaut-lite.svg';
3
- export const labaiIcon = new LabIcon({
4
- name: '@jupyterlite/ai:icon',
5
- svgstr: labaiIconSvg
6
- });
2
+ import jupyternautSvg from '../style/icons/jupyternaut-lite.svg';
7
3
  export const jupyternautIcon = new LabIcon({
8
4
  name: '@jupyterlite/ai:jupyternaut',
9
- svgstr: labaiIconSvg
5
+ svgstr: jupyternautSvg
10
6
  });
11
7
  const AI_AVATAR_BASE64 = btoa(jupyternautIcon.svgstr);
12
8
  export const AI_AVATAR = `data:image/svg+xml;base64,${AI_AVATAR_BASE64}`;
package/lib/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
2
  import { IInputToolbarRegistryFactory } from '@jupyter/chat';
3
3
  import { AgentManagerFactory } from './agent';
4
- import { IProviderRegistry, IToolRegistry, IChatModelRegistry } from './tokens';
4
+ import { IProviderRegistry, IToolRegistry, IChatModelRegistry, IDiffManager } from './tokens';
5
5
  import { AISettingsModel } from './models/settings-model';
6
- declare const _default: (JupyterFrontEndPlugin<IProviderRegistry> | JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<IChatModelRegistry> | JupyterFrontEndPlugin<AgentManagerFactory> | JupyterFrontEndPlugin<AISettingsModel> | JupyterFrontEndPlugin<IToolRegistry> | JupyterFrontEndPlugin<IInputToolbarRegistryFactory>)[];
6
+ declare const _default: (JupyterFrontEndPlugin<IProviderRegistry> | JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<IChatModelRegistry> | JupyterFrontEndPlugin<AgentManagerFactory> | JupyterFrontEndPlugin<AISettingsModel> | JupyterFrontEndPlugin<IDiffManager> | JupyterFrontEndPlugin<IToolRegistry> | JupyterFrontEndPlugin<IInputToolbarRegistryFactory>)[];
7
7
  export default _default;
8
8
  export * from './tokens';
package/lib/index.js CHANGED
@@ -3,10 +3,12 @@ import { ActiveCellManager, AttachmentOpenerRegistry, chatIcon, ChatWidget, IInp
3
3
  import { ICommandPalette, IThemeManager, WidgetTracker } from '@jupyterlab/apputils';
4
4
  import { ICompletionProviderManager } from '@jupyterlab/completer';
5
5
  import { IDocumentManager } from '@jupyterlab/docmanager';
6
+ import { IEditorTracker } from '@jupyterlab/fileeditor';
6
7
  import { INotebookTracker } from '@jupyterlab/notebook';
7
8
  import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
8
9
  import { IKernelSpecManager } from '@jupyterlab/services';
9
10
  import { ISettingRegistry } from '@jupyterlab/settingregistry';
11
+ import { IStatusBar } from '@jupyterlab/statusbar';
10
12
  import { settingsIcon, Toolbar, ToolbarButton } from '@jupyterlab/ui-components';
11
13
  import { ISecretsManager, SecretsManager } from 'jupyter-secrets-manager';
12
14
  import { PromiseDelegate, UUID } from '@lumino/coreutils';
@@ -14,14 +16,15 @@ import { AgentManagerFactory } from './agent';
14
16
  import { ProviderRegistry } from './providers/provider-registry';
15
17
  import { ApprovalButtons } from './approval-buttons';
16
18
  import { ChatModelRegistry } from './chat-model-registry';
17
- import { CommandIds, IAgentManagerFactory, IProviderRegistry, IToolRegistry, SECRETS_NAMESPACE, IAISettingsModel, IChatModelRegistry } from './tokens';
18
- import { anthropicProvider, googleProvider, mistralProvider, openaiProvider, ollamaProvider, genericProvider } from './providers/built-in-providers';
19
+ import { CommandIds, IAgentManagerFactory, IProviderRegistry, IToolRegistry, SECRETS_NAMESPACE, IAISettingsModel, IChatModelRegistry, IDiffManager } from './tokens';
20
+ import { anthropicProvider, googleProvider, mistralProvider, openaiProvider, genericProvider } from './providers/built-in-providers';
19
21
  import { AICompletionProvider } from './completion';
20
- import { clearItem, createModelSelectItem, createToolSelectItem, stopItem, TokenUsageWidget } from './components';
22
+ import { clearItem, createModelSelectItem, createToolSelectItem, stopItem, CompletionStatusWidget, TokenUsageWidget } from './components';
21
23
  import { AISettingsModel } from './models/settings-model';
24
+ import { DiffManager } from './diff-manager';
22
25
  import { ToolRegistry } from './tools/tool-registry';
23
26
  import { createAddCellTool, createDeleteCellTool, createExecuteActiveCellTool, createGetCellInfoTool, createGetNotebookInfoTool, createNotebookCreationTool, createRunCellTool, createSaveNotebookTool, createSetCellContentTool } from './tools/notebook';
24
- import { createCopyFileTool, createDeleteFileTool, createNavigateToDirectoryTool, createNewFileTool, createOpenFileTool, createRenameFileTool } from './tools/file';
27
+ import { createCopyFileTool, createDeleteFileTool, createGetFileInfoTool, createNavigateToDirectoryTool, createNewFileTool, createOpenFileTool, createRenameFileTool, createSetFileContentTool } from './tools/file';
25
28
  import { createDiscoverCommandsTool, createExecuteCommandTool } from './tools/commands';
26
29
  import { AISettingsWidget } from './widgets/ai-settings';
27
30
  import { MainAreaChat } from './widgets/main-area-chat';
@@ -85,18 +88,6 @@ const openaiProviderPlugin = {
85
88
  providerRegistry.registerProvider(openaiProvider);
86
89
  }
87
90
  };
88
- /**
89
- * Ollama provider plugin
90
- */
91
- const ollamaProviderPlugin = {
92
- id: '@jupyterlite/ai:ollama-provider',
93
- description: 'Register Ollama provider',
94
- autoStart: true,
95
- requires: [IProviderRegistry],
96
- activate: (app, providerRegistry) => {
97
- providerRegistry.registerProvider(ollamaProvider);
98
- }
99
- };
100
91
  /**
101
92
  * Generic provider plugin
102
93
  */
@@ -478,6 +469,7 @@ const agentManagerFactory = SecretsManager.sign(SECRETS_NAMESPACE, token => ({
478
469
  });
479
470
  settingsWidget.id = 'jupyterlite-ai-settings';
480
471
  settingsWidget.title.icon = settingsIcon;
472
+ settingsWidget.title.iconClass = 'jp-ai-settings-icon';
481
473
  // Build the completion provider
482
474
  if (completionManager) {
483
475
  const completionProvider = new AICompletionProvider({
@@ -498,6 +490,7 @@ const agentManagerFactory = SecretsManager.sign(SECRETS_NAMESPACE, token => ({
498
490
  label: 'AI Settings',
499
491
  caption: 'Configure AI providers and behavior',
500
492
  icon: settingsIcon,
493
+ iconClass: 'jp-ai-settings-icon',
501
494
  execute: () => {
502
495
  // Check if the widget already exists in shell
503
496
  let widget = Array.from(app.shell.widgets('main')).find(w => w.id === 'jupyterlite-ai-settings');
@@ -537,15 +530,30 @@ const settingsModel = {
537
530
  return new AISettingsModel({ settingRegistry });
538
531
  }
539
532
  };
533
+ /**
534
+ * Diff manager plugin
535
+ */
536
+ const diffManager = {
537
+ id: '@jupyterlite/ai:diff-manager',
538
+ description: 'Provide the diff manager for notebook cell diffs',
539
+ autoStart: true,
540
+ provides: IDiffManager,
541
+ requires: [IAISettingsModel],
542
+ activate: (app, settingsModel) => {
543
+ return new DiffManager({
544
+ commands: app.commands,
545
+ settingsModel
546
+ });
547
+ }
548
+ };
540
549
  const toolRegistry = {
541
550
  id: '@jupyterlite/ai:tool-registry',
542
551
  description: 'Provide the AI tool registry',
543
552
  autoStart: true,
544
553
  requires: [IAISettingsModel, IDocumentManager, IKernelSpecManager],
545
- optional: [INotebookTracker],
554
+ optional: [INotebookTracker, IDiffManager, IEditorTracker],
546
555
  provides: IToolRegistry,
547
- activate: (app, settingsModel, docManager, kernelSpecManager, notebookTracker) => {
548
- const { commands } = app;
556
+ activate: (app, settingsModel, docManager, kernelSpecManager, notebookTracker, diffManager, editorTracker) => {
549
557
  const toolRegistry = new ToolRegistry();
550
558
  const notebookCreationTool = createNotebookCreationTool(docManager, kernelSpecManager);
551
559
  toolRegistry.add('create_notebook', notebookCreationTool);
@@ -553,7 +561,7 @@ const toolRegistry = {
553
561
  const addCellTool = createAddCellTool(docManager, notebookTracker);
554
562
  const getNotebookInfoTool = createGetNotebookInfoTool(docManager, notebookTracker);
555
563
  const getCellInfoTool = createGetCellInfoTool(docManager, notebookTracker);
556
- const setCellContentTool = createSetCellContentTool(docManager, commands, notebookTracker);
564
+ const setCellContentTool = createSetCellContentTool(docManager, notebookTracker, diffManager);
557
565
  const runCellTool = createRunCellTool(docManager, notebookTracker);
558
566
  const deleteCellTool = createDeleteCellTool(docManager, notebookTracker);
559
567
  const saveNotebookTool = createSaveNotebookTool(docManager, notebookTracker);
@@ -573,12 +581,16 @@ const toolRegistry = {
573
581
  const renameFileTool = createRenameFileTool(docManager);
574
582
  const copyFileTool = createCopyFileTool(docManager);
575
583
  const navigateToDirectoryTool = createNavigateToDirectoryTool(app.commands);
584
+ const getFileInfoTool = createGetFileInfoTool(docManager, editorTracker);
585
+ const setFileContentTool = createSetFileContentTool(docManager, diffManager);
576
586
  toolRegistry.add('create_file', newFileTool);
577
587
  toolRegistry.add('open_file', openFileTool);
578
588
  toolRegistry.add('delete_file', deleteFileTool);
579
589
  toolRegistry.add('rename_file', renameFileTool);
580
590
  toolRegistry.add('copy_file', copyFileTool);
581
591
  toolRegistry.add('navigate_to_directory', navigateToDirectoryTool);
592
+ toolRegistry.add('get_file_info', getFileInfoTool);
593
+ toolRegistry.add('set_file_content', setFileContentTool);
582
594
  // Add command operation tools
583
595
  const discoverCommandsTool = createDiscoverCommandsTool(app.commands);
584
596
  const executeCommandTool = createExecuteCommandTool(app.commands, settingsModel);
@@ -591,7 +603,7 @@ const toolRegistry = {
591
603
  * Extension providing the input toolbar registry.
592
604
  */
593
605
  const inputToolbarFactory = {
594
- id: 'labai:input-toolbar-factory',
606
+ id: '@jupyterlite/ai:input-toolbar-factory',
595
607
  description: 'The input toolbar registry plugin.',
596
608
  autoStart: true,
597
609
  provides: IInputToolbarRegistryFactory,
@@ -623,20 +635,39 @@ const inputToolbarFactory = {
623
635
  };
624
636
  }
625
637
  };
638
+ const completionStatus = {
639
+ id: '@jupyterlite/ai:completion-status',
640
+ description: 'The completion status displayed in the status bar',
641
+ autoStart: true,
642
+ requires: [IAISettingsModel],
643
+ optional: [IStatusBar],
644
+ activate: (app, settingsModel, statusBar) => {
645
+ if (!statusBar) {
646
+ return;
647
+ }
648
+ const item = new CompletionStatusWidget({ settingsModel });
649
+ statusBar?.registerStatusItem('completionState', {
650
+ item,
651
+ align: 'right',
652
+ rank: 10
653
+ });
654
+ }
655
+ };
626
656
  export default [
627
657
  providerRegistryPlugin,
628
658
  anthropicProviderPlugin,
629
659
  googleProviderPlugin,
630
660
  mistralProviderPlugin,
631
661
  openaiProviderPlugin,
632
- ollamaProviderPlugin,
633
662
  genericProviderPlugin,
634
663
  settingsModel,
664
+ diffManager,
635
665
  chatModelRegistry,
636
666
  plugin,
637
667
  toolRegistry,
638
668
  agentManagerFactory,
639
- inputToolbarFactory
669
+ inputToolbarFactory,
670
+ completionStatus
640
671
  ];
641
672
  // Export extension points for other extensions to use
642
673
  export * from './tokens';
@@ -36,10 +36,14 @@ export interface IAIConfig {
36
36
  contextAwareness: boolean;
37
37
  codeExecution: boolean;
38
38
  systemPrompt: string;
39
+ completionSystemPrompt: string;
39
40
  toolsEnabled: boolean;
40
41
  sendWithShiftEnter: boolean;
41
42
  showTokenUsage: boolean;
42
43
  commandsRequiringApproval: string[];
44
+ showCellDiff: boolean;
45
+ showFileDiff: boolean;
46
+ diffDisplayMode: 'split' | 'unified';
43
47
  }
44
48
  export declare class AISettingsModel extends VDomModel {
45
49
  private _config;
@@ -13,6 +13,9 @@ export class AISettingsModel extends VDomModel {
13
13
  toolsEnabled: true,
14
14
  sendWithShiftEnter: false,
15
15
  showTokenUsage: false,
16
+ showCellDiff: true,
17
+ showFileDiff: true,
18
+ diffDisplayMode: 'split',
16
19
  commandsRequiringApproval: [
17
20
  'notebook:restart-run-all',
18
21
  'notebook:run-cell',
@@ -97,7 +100,18 @@ When users request complex tasks that require multiple steps (like "create a not
97
100
 
98
101
  Always think through multi-step tasks and use tools to fully complete the user's request rather than stopping after just one action.
99
102
 
100
- Ready to help you build something great! What are you working on?`
103
+ Ready to help you build something great! What are you working on?`,
104
+ // Completion system prompt - also defined in schema/settings-model.json
105
+ // This serves as a fallback if settings fail to load or are not available
106
+ completionSystemPrompt: `You are an AI code completion assistant. Complete the given code fragment with appropriate code.
107
+ Rules:
108
+ - Return only the completion text, no explanations or comments
109
+ - Do not include code block markers (\`\`\` or similar)
110
+ - Make completions contextually relevant to the surrounding code and notebook context
111
+ - Follow the language-specific conventions and style guidelines for the detected programming language
112
+ - Keep completions concise but functional
113
+ - Do not repeat the existing code that comes before the cursor
114
+ - Use variables, imports, functions, and other definitions from previous notebook cells when relevant`
101
115
  };
102
116
  _settingRegistry;
103
117
  _settings = null;
@@ -152,7 +166,7 @@ Ready to help you build something great! What are you working on?`
152
166
  }
153
167
  return this._config.activeCompleterProvider
154
168
  ? this.getProvider(this._config.activeCompleterProvider)
155
- : this.getDefaultProvider();
169
+ : undefined;
156
170
  }
157
171
  async addProvider(providerConfig) {
158
172
  const id = `${providerConfig.provider}-${Date.now()}`;
@@ -205,6 +219,11 @@ Ready to help you build something great! What are you working on?`
205
219
  return;
206
220
  }
207
221
  Object.assign(provider, updates);
222
+ Object.keys(provider).forEach(key => {
223
+ if (key !== 'id' && updates[key] === undefined) {
224
+ delete provider[key];
225
+ }
226
+ });
208
227
  await this.saveSetting('providers', this._config.providers);
209
228
  }
210
229
  async setActiveProvider(id) {
@@ -284,6 +303,9 @@ Ready to help you build something great! What are you working on?`
284
303
  if (value !== undefined) {
285
304
  await this._settings.set(key, value);
286
305
  }
306
+ else {
307
+ await this._settings.remove(key);
308
+ }
287
309
  }
288
310
  }
289
311
  catch (error) {
@@ -15,10 +15,6 @@ export declare const mistralProvider: IProviderInfo;
15
15
  * OpenAI provider
16
16
  */
17
17
  export declare const openaiProvider: IProviderInfo;
18
- /**
19
- * Ollama provider
20
- */
21
- export declare const ollamaProvider: IProviderInfo;
22
18
  /**
23
19
  * Generic OpenAI-compatible provider
24
20
  */
@@ -2,7 +2,7 @@ import { createAnthropic } from '@ai-sdk/anthropic';
2
2
  import { createGoogleGenerativeAI } from '@ai-sdk/google';
3
3
  import { createMistral } from '@ai-sdk/mistral';
4
4
  import { createOpenAI } from '@ai-sdk/openai';
5
- import { createOllama } from 'ollama-ai-provider-v2';
5
+ import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
6
6
  /**
7
7
  * Anthropic provider
8
8
  */
@@ -13,6 +13,8 @@ export const anthropicProvider = {
13
13
  defaultModels: [
14
14
  'claude-sonnet-4-5',
15
15
  'claude-sonnet-4-5-20250929',
16
+ 'claude-haiku-4-5',
17
+ 'claude-haiku-4-5-20251001',
16
18
  'claude-opus-4-1',
17
19
  'claude-opus-4-0',
18
20
  'claude-sonnet-4-0',
@@ -192,25 +194,6 @@ export const openaiProvider = {
192
194
  return openai(modelName);
193
195
  }
194
196
  };
195
- /**
196
- * Ollama provider
197
- */
198
- export const ollamaProvider = {
199
- id: 'ollama',
200
- name: 'Ollama',
201
- apiKeyRequirement: 'none',
202
- defaultModels: [],
203
- supportsBaseURL: true,
204
- supportsHeaders: true,
205
- factory: (options) => {
206
- const ollama = createOllama({
207
- baseURL: options.baseURL || 'http://localhost:11434/api',
208
- ...(options.headers && { headers: options.headers })
209
- });
210
- const modelName = options.model || 'phi3';
211
- return ollama(modelName);
212
- }
213
- };
214
197
  /**
215
198
  * Generic OpenAI-compatible provider
216
199
  */
@@ -223,13 +206,24 @@ export const genericProvider = {
223
206
  supportsHeaders: true,
224
207
  supportsToolCalling: true,
225
208
  description: 'Uses /chat/completions endpoint',
209
+ baseUrls: [
210
+ {
211
+ url: 'http://localhost:4000',
212
+ description: 'Default for local LiteLLM server'
213
+ },
214
+ {
215
+ url: 'http://localhost:11434/v1',
216
+ description: 'Default for local Ollama server'
217
+ }
218
+ ],
226
219
  factory: (options) => {
227
- const openai = createOpenAI({
220
+ const openaiCompatible = createOpenAICompatible({
221
+ name: options.provider,
228
222
  apiKey: options.apiKey || 'dummy',
229
- ...(options.baseURL && { baseURL: options.baseURL }),
223
+ baseURL: options.baseURL ?? '',
230
224
  ...(options.headers && { headers: options.headers })
231
225
  });
232
226
  const modelName = options.model || 'gpt-4o';
233
- return openai(modelName);
227
+ return openaiCompatible(modelName);
234
228
  }
235
229
  };
package/lib/tokens.d.ts CHANGED
@@ -130,6 +130,13 @@ export interface IProviderInfo {
130
130
  * Optional description shown in the UI
131
131
  */
132
132
  description?: string;
133
+ /**
134
+ * Optional URL suggestions
135
+ */
136
+ baseUrls?: {
137
+ url: string;
138
+ description?: string;
139
+ }[];
133
140
  /**
134
141
  * Factory function for creating language models
135
142
  */
@@ -190,3 +197,70 @@ export interface IChatModelRegistry {
190
197
  createModel(name?: string, activeProvider?: string, tokenUsage?: ITokenUsage): AIChatModel;
191
198
  }
192
199
  export declare const IChatModelRegistry: Token<IChatModelRegistry>;
200
+ /**
201
+ * Parameters for showing cell diff
202
+ */
203
+ export interface IShowCellDiffParams {
204
+ /**
205
+ * Original cell content
206
+ */
207
+ original: string;
208
+ /**
209
+ * Modified cell content
210
+ */
211
+ modified: string;
212
+ /**
213
+ * Optional cell ID
214
+ */
215
+ cellId?: string;
216
+ /**
217
+ * Whether to show action buttons in the diff view
218
+ */
219
+ showActionButtons?: boolean;
220
+ /**
221
+ * Whether to open the diff view
222
+ */
223
+ openDiff?: boolean;
224
+ /**
225
+ * Optional path to the notebook
226
+ */
227
+ notebookPath?: string;
228
+ }
229
+ /**
230
+ * Parameters for showing file diff
231
+ */
232
+ export interface IShowFileDiffParams {
233
+ /**
234
+ * Original file content
235
+ */
236
+ original: string;
237
+ /**
238
+ * Modified file content
239
+ */
240
+ modified: string;
241
+ /**
242
+ * Optional file path
243
+ */
244
+ filePath?: string;
245
+ /**
246
+ * Whether to show action buttons in the diff view
247
+ */
248
+ showActionButtons?: boolean;
249
+ }
250
+ /**
251
+ * Interface for managing diff operations
252
+ */
253
+ export interface IDiffManager {
254
+ /**
255
+ * Show diff between original and modified cell content
256
+ */
257
+ showCellDiff(params: IShowCellDiffParams): Promise<void>;
258
+ /**
259
+ * Show diff between original and modified file content
260
+ */
261
+ showFileDiff(params: IShowFileDiffParams): Promise<void>;
262
+ }
263
+ /**
264
+ * Token for the diff manager.
265
+ */
266
+ export declare const IDiffManager: Token<IDiffManager>;
package/lib/tokens.js CHANGED
@@ -35,3 +35,7 @@ export const SECRETS_REPLACEMENT = '***';
35
35
  */
36
36
  export const IAgentManagerFactory = new Token('@jupyterlite/ai:agent-manager-factory');
37
37
  export const IChatModelRegistry = new Token('@jupyterlite/ai:chat-model-registry');
38
+ /**
39
+ * Token for the diff manager.
40
+ */
41
+ export const IDiffManager = new Token('@jupyterlite/ai:diff-manager');
@@ -15,43 +15,44 @@ export function createDiscoverCommandsTool(commands) {
15
15
  .nullable()
16
16
  .describe('Optional search query to filter commands')
17
17
  }),
18
- execute: async () => {
19
- try {
20
- const commandList = [];
21
- // Get all command IDs
22
- const commandIds = commands.listCommands();
23
- for (const id of commandIds) {
24
- try {
25
- // Get command metadata using various CommandRegistry methods
26
- const description = await commands.describedBy(id);
27
- const label = commands.label(id);
28
- const caption = commands.caption(id);
29
- const usage = commands.usage(id);
30
- commandList.push({
31
- id,
32
- label: label || undefined,
33
- caption: caption || undefined,
34
- description: usage || undefined,
35
- args: description?.args || undefined
36
- });
37
- }
38
- catch (error) {
39
- // Some commands might not have descriptions, skip them
40
- commandList.push({ id });
18
+ execute: async (input) => {
19
+ const { query } = input;
20
+ const commandList = [];
21
+ // Get all command IDs
22
+ const commandIds = commands.listCommands();
23
+ for (const id of commandIds) {
24
+ // Get command metadata using various CommandRegistry methods
25
+ const description = await commands.describedBy(id);
26
+ const label = commands.label(id);
27
+ const caption = commands.caption(id);
28
+ const usage = commands.usage(id);
29
+ const command = {
30
+ id,
31
+ label: label || undefined,
32
+ caption: caption || undefined,
33
+ description: usage || undefined,
34
+ 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);
41
45
  }
42
46
  }
43
- return {
44
- success: true,
45
- commandCount: commandList.length,
46
- commands: commandList
47
- };
48
- }
49
- catch (error) {
50
- return {
51
- success: false,
52
- error: `Failed to discover commands: ${error instanceof Error ? error.message : String(error)}`
53
- };
47
+ else {
48
+ commandList.push(command);
49
+ }
54
50
  }
51
+ return {
52
+ success: true,
53
+ commandCount: commandList.length,
54
+ commands: commandList
55
+ };
55
56
  }
56
57
  });
57
58
  }
@@ -69,7 +70,7 @@ export function createExecuteCommandTool(commands, settingsModel) {
69
70
  .optional()
70
71
  .describe('Optional arguments to pass to the command')
71
72
  }),
72
- needsApproval: async (_context, { commandId }) => {
73
+ needsApproval: async (context, { commandId }) => {
73
74
  // Use configurable list of commands requiring approval
74
75
  const commandsRequiringApproval = settingsModel.config.commandsRequiringApproval;
75
76
  return commandsRequiringApproval.some(cmd => commandId.includes(cmd) || cmd.includes(commandId));