@notebook-intelligence/notebook-intelligence 3.0.0 → 4.0.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/README.md CHANGED
@@ -4,6 +4,27 @@ Notebook Intelligence (NBI) is an AI coding assistant and extensible AI framewor
4
4
 
5
5
  ## Feature Highlights
6
6
 
7
+ ### Claude Mode
8
+
9
+ Notebook Intelligence provides a dedicated mode for [Claude Code](https://code.claude.com/) integration. In **Claude mode**, NBI uses Claude Code for AI Agent Chat UI and Claude models for inline chat (in editors) and auto-complete suggestions.
10
+
11
+ This integration brings the AI tools and features supported by Claude Code such as built-in tools, skills, MCP servers, custom commands and many more to JupyterLab.
12
+
13
+ <img src="media/claude-chat.png" alt="Claude mode" width=500 />
14
+
15
+ #### Claude Configuration
16
+
17
+ You can configure the Claude settings in the NBI Settings dialog. You can access this dialog by using the gear icon in the NBI Chat UI or from JupyterLab Settings menu -> Notebook Intelligence Settings. Toggle the `Enable Claude mode` option to enable Claude mode. The other options are as follows:
18
+
19
+ - **Chat model**: Select the Claude model to use for Agent Chat UI and inline chat.
20
+ - **Auto-complete model**: Select the Claude model to use for auto-complete suggestions.
21
+ - **Chat Agent setting sources**: Select the setting sources to use for Claude Code. You can choose to use user settings, project settings or both. These settings are the standard Claude Code settings such as tools, skills, MCP servers, custom commands and many more that are saved in the user's home directory and project directory. See [Claude Code documentation](https://code.claude.com/docs/en/settings) for more details.
22
+ - **Chat Agent tools**: Select the tools to activate in the Agent Chat UI. `Claude Code tools` are always activated. `Jupyter UI tools` are the tools that are provided by NBI to interact with JupyterLab UI (authoring notebooks, running cells, etc.).
23
+ - **API key**: Enter your Claude API key.
24
+ - **Base URL**: Enter your Claude base URL.
25
+
26
+ <img src="media/claude-settings.png" alt="Claude settings" width=700 />
27
+
7
28
  ### Agent Mode
8
29
 
9
30
  In Agent Mode, built-in AI agent creates, edits and executes notebooks for you interactively. It can detect issues in the cells and fix for you.
package/lib/api.d.ts CHANGED
@@ -10,6 +10,15 @@ export interface IDeviceVerificationInfo {
10
10
  verificationURI: string;
11
11
  userCode: string;
12
12
  }
13
+ export declare enum ClaudeModelType {
14
+ Default = "",
15
+ ClaudeOpus45 = "claude-opus-4-5",
16
+ ClaudeHaiku45 = "claude-haiku-4-5"
17
+ }
18
+ export declare enum ClaudeToolType {
19
+ ClaudeCodeTools = "claude-code:built-in-tools",
20
+ JupyterUITools = "nbi:built-in-jupyter-ui-tools"
21
+ }
13
22
  export declare class NBIConfig {
14
23
  get userHomeDir(): string;
15
24
  get userConfigDir(): string;
@@ -26,6 +35,8 @@ export declare class NBIConfig {
26
35
  getMCPServer(serverId: string): any;
27
36
  getMCPServerPrompt(serverId: string, promptName: string): any;
28
37
  get mcpServerSettings(): any;
38
+ get claudeSettings(): any;
39
+ get isInClaudeCodeMode(): boolean;
29
40
  capabilities: any;
30
41
  chatParticipants: IChatParticipant[];
31
42
  changed: Signal<this, void>;
package/lib/api.js CHANGED
@@ -13,6 +13,17 @@ export var GitHubCopilotLoginStatus;
13
13
  GitHubCopilotLoginStatus["LoggingIn"] = "LOGGING_IN";
14
14
  GitHubCopilotLoginStatus["LoggedIn"] = "LOGGED_IN";
15
15
  })(GitHubCopilotLoginStatus || (GitHubCopilotLoginStatus = {}));
16
+ export var ClaudeModelType;
17
+ (function (ClaudeModelType) {
18
+ ClaudeModelType["Default"] = "";
19
+ ClaudeModelType["ClaudeOpus45"] = "claude-opus-4-5";
20
+ ClaudeModelType["ClaudeHaiku45"] = "claude-haiku-4-5";
21
+ })(ClaudeModelType || (ClaudeModelType = {}));
22
+ export var ClaudeToolType;
23
+ (function (ClaudeToolType) {
24
+ ClaudeToolType["ClaudeCodeTools"] = "claude-code:built-in-tools";
25
+ ClaudeToolType["JupyterUITools"] = "nbi:built-in-jupyter-ui-tools";
26
+ })(ClaudeToolType || (ClaudeToolType = {}));
16
27
  export class NBIConfig {
17
28
  constructor() {
18
29
  this.capabilities = {};
@@ -69,6 +80,12 @@ export class NBIConfig {
69
80
  get mcpServerSettings() {
70
81
  return this.capabilities.mcp_server_settings;
71
82
  }
83
+ get claudeSettings() {
84
+ return this.capabilities.claude_settings;
85
+ }
86
+ get isInClaudeCodeMode() {
87
+ return this.claudeSettings.enabled === true;
88
+ }
72
89
  }
73
90
  class NBIAPI {
74
91
  static async initialize() {
@@ -77,7 +94,8 @@ class NBIAPI {
77
94
  NBIAPI.initializeWebsocket();
78
95
  this._messageReceived.connect((_, msg) => {
79
96
  msg = JSON.parse(msg);
80
- if (msg.type === BackendMessageType.MCPServerStatusChange) {
97
+ if (msg.type === BackendMessageType.MCPServerStatusChange ||
98
+ msg.type === BackendMessageType.ClaudeCodeStatusChange) {
81
99
  this.fetchCapabilities();
82
100
  }
83
101
  else if (msg.type === BackendMessageType.GitHubCopilotLoginStatusChange) {
@@ -116,15 +134,16 @@ class NBIAPI {
116
134
  NBIAPI.getLoginStatus() === GitHubCopilotLoginStatus.NotLoggedIn);
117
135
  }
118
136
  static getChatEnabled() {
119
- return this.config.chatModel.provider === GITHUB_COPILOT_PROVIDER_ID
120
- ? !this.getGHLoginRequired()
121
- : this.config.llmProviders.find(provider => provider.id === this.config.chatModel.provider);
137
+ return (this.config.isInClaudeCodeMode ||
138
+ (this.config.chatModel.provider === GITHUB_COPILOT_PROVIDER_ID
139
+ ? !this.getGHLoginRequired()
140
+ : this.config.llmProviders.find(provider => provider.id === this.config.chatModel.provider)));
122
141
  }
123
142
  static getInlineCompletionEnabled() {
124
- return this.config.inlineCompletionModel.provider ===
125
- GITHUB_COPILOT_PROVIDER_ID
126
- ? !this.getGHLoginRequired()
127
- : this.config.llmProviders.find(provider => provider.id === this.config.inlineCompletionModel.provider);
143
+ return (this.config.isInClaudeCodeMode ||
144
+ (this.config.inlineCompletionModel.provider === GITHUB_COPILOT_PROVIDER_ID
145
+ ? !this.getGHLoginRequired()
146
+ : this.config.llmProviders.find(provider => provider.id === this.config.inlineCompletionModel.provider)));
128
147
  }
129
148
  static async loginToGitHub() {
130
149
  this._loginStatus = GitHubCopilotLoginStatus.ActivatingDevice;
@@ -4,7 +4,7 @@ import { ReactWidget } from '@jupyterlab/apputils';
4
4
  import { UUID } from '@lumino/coreutils';
5
5
  import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js';
6
6
  import { NBIAPI, GitHubCopilotLoginStatus } from './api';
7
- import { BackendMessageType, BuiltinToolsetType, ContextType, RequestDataType, ResponseStreamDataType, TelemetryEventType } from './tokens';
7
+ import { BackendMessageType, BuiltinToolsetType, CLAUDE_CODE_CHAT_PARTICIPANT_ID, ContextType, RequestDataType, ResponseStreamDataType, TelemetryEventType } from './tokens';
8
8
  import { MarkdownRenderer as OriginalMarkdownRenderer } from './markdown-renderer';
9
9
  const MarkdownRenderer = memo(OriginalMarkdownRenderer);
10
10
  import copySvgstr from '../style/icons/copy.svg';
@@ -14,6 +14,8 @@ import { VscSend, VscStopCircle, VscEye, VscEyeClosed, VscTriangleRight, VscTria
14
14
  import { extractLLMGeneratedCode, isDarkTheme } from './utils';
15
15
  import { CheckBoxItem } from './components/checkbox';
16
16
  import { mcpServerSettingsToEnabledState } from './components/mcp-util';
17
+ import claudeSvg from '../style/claude.svg';
18
+ import { AskUserQuestion } from './components/ask-user-question';
17
19
  export var RunChatCompletionType;
18
20
  (function (RunChatCompletionType) {
19
21
  RunChatCompletionType[RunChatCompletionType["Chat"] = 0] = "Chat";
@@ -261,10 +263,11 @@ function ChatResponse(props) {
261
263
  ? 'Output'
262
264
  : `Output (${Math.floor(item.reasoningTime)} s)`;
263
265
  };
266
+ const chatParticipantId = ((_a = msg.participant) === null || _a === void 0 ? void 0 : _a.id) || 'default';
264
267
  return (React.createElement("div", { className: `chat-message chat-message-${msg.from}`, "data-render-count": renderCount },
265
268
  React.createElement("div", { className: "chat-message-header" },
266
269
  React.createElement("div", { className: "chat-message-from" },
267
- ((_a = msg.participant) === null || _a === void 0 ? void 0 : _a.iconPath) && (React.createElement("div", { className: `chat-message-from-icon ${((_b = msg.participant) === null || _b === void 0 ? void 0 : _b.id) === 'default' ? 'chat-message-from-icon-default' : ''} ${isDarkTheme() ? 'dark' : ''}` },
270
+ ((_b = msg.participant) === null || _b === void 0 ? void 0 : _b.iconPath) && (React.createElement("div", { className: `chat-message-from-icon chat-message-from-icon-${chatParticipantId} ${isDarkTheme() ? 'dark' : ''}` },
268
271
  React.createElement("img", { src: msg.participant.iconPath }))),
269
272
  React.createElement("div", { className: "chat-message-from-title" }, msg.from === 'user'
270
273
  ? 'User'
@@ -329,6 +332,32 @@ function ChatResponse(props) {
329
332
  runCommand('notebook-intelligence:chat-user-input', item.content.cancelArgs);
330
333
  } },
331
334
  React.createElement("div", { className: "jp-Dialog-buttonLabel" }, item.content.cancelLabel))));
335
+ case ResponseStreamDataType.AskUserQuestion:
336
+ return answeredForms.get(item.id) ===
337
+ 'confirmed' ? null : answeredForms.get(item.id) ===
338
+ 'canceled' ? (React.createElement("div", null, "\u2716 Canceled")) : (React.createElement("div", { className: "chat-confirmation-form ask-user-question", key: `key-${index}` },
339
+ React.createElement(AskUserQuestion, { userQuestions: item, onSubmit: (selectedAnswers) => {
340
+ markFormConfirmed(item.id);
341
+ runCommand('notebook-intelligence:chat-user-input', {
342
+ id: item.content.identifier.id,
343
+ data: {
344
+ callback_id: item.content.identifier.callback_id,
345
+ data: {
346
+ confirmed: true,
347
+ selectedAnswers
348
+ }
349
+ }
350
+ });
351
+ }, onCancel: () => {
352
+ markFormCanceled(item.id);
353
+ runCommand('notebook-intelligence:chat-user-input', {
354
+ id: item.content.identifier.id,
355
+ data: {
356
+ callback_id: item.content.identifier.callback_id,
357
+ data: { confirmed: false }
358
+ }
359
+ });
360
+ } })));
332
361
  }
333
362
  return null;
334
363
  }),
@@ -338,15 +367,15 @@ const MemoizedChatResponse = memo(ChatResponse);
338
367
  async function submitCompletionRequest(request, responseEmitter) {
339
368
  switch (request.type) {
340
369
  case RunChatCompletionType.Chat:
341
- return NBIAPI.chatRequest(request.messageId, request.chatId, request.content, request.language || 'python', request.currentDirectory || '', request.filename || 'Untitled.ipynb', request.additionalContext || [], request.chatMode, request.toolSelections || {}, responseEmitter);
370
+ return NBIAPI.chatRequest(request.messageId, request.chatId, request.content, request.language || 'python', request.currentDirectory || '', request.filename || '', request.additionalContext || [], request.chatMode, request.toolSelections || {}, responseEmitter);
342
371
  case RunChatCompletionType.ExplainThis:
343
372
  case RunChatCompletionType.FixThis:
344
373
  case RunChatCompletionType.ExplainThisOutput:
345
374
  case RunChatCompletionType.TroubleshootThisOutput: {
346
- return NBIAPI.chatRequest(request.messageId, request.chatId, request.content, request.language || 'python', request.currentDirectory || '', request.filename || 'Untitled.ipynb', [], 'ask', {}, responseEmitter);
375
+ return NBIAPI.chatRequest(request.messageId, request.chatId, request.content, request.language || 'python', request.currentDirectory || '', request.filename || '', [], 'ask', {}, responseEmitter);
347
376
  }
348
377
  case RunChatCompletionType.GenerateCode:
349
- return NBIAPI.generateCode(request.chatId, request.content, request.prefix || '', request.suffix || '', request.existingCode || '', request.language || 'python', request.filename || 'Untitled.ipynb', responseEmitter);
378
+ return NBIAPI.generateCode(request.chatId, request.content, request.prefix || '', request.suffix || '', request.existingCode || '', request.language || 'python', request.filename || '', responseEmitter);
350
379
  }
351
380
  }
352
381
  function SidebarComponent(props) {
@@ -689,22 +718,35 @@ function SidebarComponent(props) {
689
718
  useEffect(() => {
690
719
  var _a;
691
720
  const prefixes = [];
692
- prefixes.push('/clear');
693
- if (chatMode === 'ask') {
694
- const chatParticipants = NBIAPI.config.chatParticipants;
695
- for (const participant of chatParticipants) {
696
- const id = participant.id;
697
- const commands = participant.commands;
698
- const participantPrefix = id === 'default' ? '' : `@${id}`;
699
- if (participantPrefix !== '') {
700
- prefixes.push(participantPrefix);
701
- }
702
- const commandPrefix = participantPrefix === '' ? '' : `${participantPrefix} `;
721
+ if (NBIAPI.config.isInClaudeCodeMode) {
722
+ const claudeChatParticipant = NBIAPI.config.chatParticipants.find(participant => participant.id === CLAUDE_CODE_CHAT_PARTICIPANT_ID);
723
+ if (claudeChatParticipant) {
724
+ const commands = claudeChatParticipant.commands;
703
725
  for (const command of commands) {
704
- prefixes.push(`${commandPrefix}/${command}`);
726
+ prefixes.push(`/${command}`);
705
727
  }
706
728
  }
707
729
  }
730
+ else {
731
+ if (chatMode === 'ask') {
732
+ const chatParticipants = NBIAPI.config.chatParticipants;
733
+ for (const participant of chatParticipants) {
734
+ const id = participant.id;
735
+ const commands = participant.commands;
736
+ const participantPrefix = id === 'default' ? '' : `@${id}`;
737
+ if (participantPrefix !== '') {
738
+ prefixes.push(participantPrefix);
739
+ }
740
+ const commandPrefix = participantPrefix === '' ? '' : `${participantPrefix} `;
741
+ for (const command of commands) {
742
+ prefixes.push(`${commandPrefix}/${command}`);
743
+ }
744
+ }
745
+ }
746
+ else {
747
+ prefixes.push('/clear');
748
+ }
749
+ }
708
750
  const mcpServers = NBIAPI.config.toolConfig.mcpServers;
709
751
  const mcpServerSettings = NBIAPI.config.mcpServerSettings;
710
752
  for (const mcpServer of mcpServers) {
@@ -1287,7 +1329,7 @@ function SidebarComponent(props) {
1287
1329
  }, title: "Select chat participant" }, "@"))),
1288
1330
  React.createElement("div", { style: { flexGrow: 1 } }),
1289
1331
  React.createElement("div", { className: "chat-mode-widgets-container" },
1290
- React.createElement("div", null,
1332
+ !NBIAPI.config.isInClaudeCodeMode && (React.createElement("div", null,
1291
1333
  React.createElement("select", { className: "chat-mode-select", title: "Chat mode", value: chatMode, onChange: event => {
1292
1334
  if (event.target.value === 'ask') {
1293
1335
  setToolSelections(toolSelectionsEmpty);
@@ -1296,12 +1338,13 @@ function SidebarComponent(props) {
1296
1338
  setChatMode(event.target.value);
1297
1339
  } },
1298
1340
  React.createElement("option", { value: "ask" }, "Ask"),
1299
- React.createElement("option", { value: "agent" }, "Agent"))),
1300
- chatMode !== 'ask' && (React.createElement("div", { className: `user-input-footer-button tools-button ${unsafeToolSelected ? 'tools-button-warning' : selectedToolCount > 0 ? 'tools-button-active' : ''}`, onClick: () => handleChatToolsButtonClick(), title: unsafeToolSelected
1341
+ React.createElement("option", { value: "agent" }, "Agent")))),
1342
+ chatMode !== 'ask' && !NBIAPI.config.isInClaudeCodeMode && (React.createElement("div", { className: `user-input-footer-button tools-button ${unsafeToolSelected ? 'tools-button-warning' : selectedToolCount > 0 ? 'tools-button-active' : ''}`, onClick: () => handleChatToolsButtonClick(), title: unsafeToolSelected
1301
1343
  ? `Tool selection can cause irreversible changes! Review each tool execution carefully.\n${toolSelectionTitle}`
1302
1344
  : toolSelectionTitle },
1303
1345
  React.createElement(VscTools, null),
1304
- selectedToolCount > 0 && React.createElement(React.Fragment, null, selectedToolCount)))),
1346
+ selectedToolCount > 0 && React.createElement(React.Fragment, null, selectedToolCount))),
1347
+ NBIAPI.config.isInClaudeCodeMode && (React.createElement("span", { title: "Claude mode", className: "claude-icon", dangerouslySetInnerHTML: { __html: claudeSvg } }))),
1305
1348
  React.createElement("div", null,
1306
1349
  React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-styled send-button", onClick: () => handleSubmitStopChatButtonClick(), disabled: prompt.length === 0 && !copilotRequestInProgress }, copilotRequestInProgress ? React.createElement(VscStopCircle, null) : React.createElement(VscSend, null)))),
1307
1350
  showPopover && prefixSuggestions.length > 0 && (React.createElement("div", { className: "user-input-autocomplete" }, prefixSuggestions.map((prefix, index) => (React.createElement("div", { key: `key-${index}`, className: `user-input-autocomplete-item ${index === selectedPrefixSuggestionIndex ? 'selected' : ''}`, "data-value": prefix, onClick: event => prefixSuggestionSelected(event) }, prefix))))),
@@ -1443,7 +1486,7 @@ function InlinePromptComponent(props) {
1443
1486
  type: RunChatCompletionType.GenerateCode,
1444
1487
  content: prompt,
1445
1488
  language: props.language || 'python',
1446
- filename: props.filename || 'Untitled.ipynb',
1489
+ filename: props.filename || '',
1447
1490
  prefix: props.prefix,
1448
1491
  suffix: props.suffix,
1449
1492
  existingCode: props.existingCode,
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare function AskUserQuestion(props: any): React.JSX.Element;
@@ -0,0 +1,57 @@
1
+ // Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
2
+ import React, { useState } from 'react';
3
+ export function AskUserQuestion(props) {
4
+ const userQuestions = props.userQuestions.content;
5
+ const [selectedAnswers, setSelectedAnswers] = useState({});
6
+ const onOptionSelected = (question, option) => {
7
+ var _a, _b, _c;
8
+ if (question.multiSelect) {
9
+ if ((_a = selectedAnswers[question.question]) === null || _a === void 0 ? void 0 : _a.includes(option.label)) {
10
+ setSelectedAnswers({
11
+ ...selectedAnswers,
12
+ [question.question]: ((_b = selectedAnswers[question.question]) !== null && _b !== void 0 ? _b : []).filter((o) => o !== option.label)
13
+ });
14
+ }
15
+ else {
16
+ setSelectedAnswers({
17
+ ...selectedAnswers,
18
+ [question.question]: [
19
+ ...((_c = selectedAnswers[question.question]) !== null && _c !== void 0 ? _c : []),
20
+ option.label
21
+ ]
22
+ });
23
+ }
24
+ }
25
+ else {
26
+ setSelectedAnswers({
27
+ ...selectedAnswers,
28
+ [question.question]: [option.label]
29
+ });
30
+ }
31
+ };
32
+ return (React.createElement(React.Fragment, null,
33
+ userQuestions.title ? (React.createElement("div", null,
34
+ React.createElement("b", null, userQuestions.title))) : null,
35
+ userQuestions.message ? React.createElement("div", null, userQuestions.message) : null,
36
+ userQuestions.questions.map((question) => (React.createElement("div", { className: "ask-user-question-container", key: question.question },
37
+ React.createElement("form", { className: "ask-user-question-form" },
38
+ React.createElement("div", { className: "ask-user-question-question" }, question.question),
39
+ React.createElement("div", { className: "ask-user-question-header" }, question.header),
40
+ React.createElement("div", { className: "ask-user-question-options" }, question.options.map((option) => {
41
+ var _a, _b;
42
+ return (React.createElement("div", { className: "ask-user-question-option", key: option.label },
43
+ React.createElement("div", null,
44
+ React.createElement("input", { id: option.label, type: "checkbox", checked: (_b = (_a = selectedAnswers[question.question]) === null || _a === void 0 ? void 0 : _a.includes(option.label)) !== null && _b !== void 0 ? _b : false, onChange: () => onOptionSelected(question, option) }),
45
+ React.createElement("label", { htmlFor: option.label }, option.label)),
46
+ React.createElement("div", { className: "ask-user-question-option-description" }, option.description)));
47
+ })))))),
48
+ React.createElement("div", { className: "ask-user-question-footer" },
49
+ React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", onClick: () => {
50
+ props.onSubmit(selectedAnswers);
51
+ } },
52
+ React.createElement("div", { className: "jp-Dialog-buttonLabel" }, userQuestions.submitLabel)),
53
+ React.createElement("button", { className: "jp-Dialog-button jp-mod-reject jp-mod-styled", onClick: () => {
54
+ props.onCancel();
55
+ } },
56
+ React.createElement("div", { className: "jp-Dialog-buttonLabel" }, userQuestions.cancelLabel)))));
57
+ }
@@ -3,9 +3,14 @@ import React from 'react';
3
3
  import { MdOutlineCheckBoxOutlineBlank, MdCheckBox } from 'react-icons/md';
4
4
  export function CheckBoxItem(props) {
5
5
  const indent = props.indent || 0;
6
- return (React.createElement("div", { className: `checkbox-item checkbox-item-indent-${indent} ${props.header ? 'checkbox-item-header' : ''}`, title: props.tooltip || props.title || '', onClick: event => props.onClick(event) },
6
+ const disabled = props.disabled || false;
7
+ return (React.createElement("div", { className: `checkbox-item checkbox-item-indent-${indent} ${props.header ? 'checkbox-item-header' : ''}`, title: props.tooltip || props.title || '', onClick: event => {
8
+ if (!disabled) {
9
+ props.onClick(event);
10
+ }
11
+ } },
7
12
  React.createElement("div", { className: "checkbox-item-toggle" },
8
- props.checked ? (React.createElement(MdCheckBox, { className: "checkbox-icon" })) : (React.createElement(MdOutlineCheckBoxOutlineBlank, { className: "checkbox-icon" })),
9
- props.label),
13
+ props.checked ? (React.createElement(MdCheckBox, { className: "checkbox-icon", style: { opacity: disabled ? 0.5 : 1 } })) : (React.createElement(MdOutlineCheckBoxOutlineBlank, { className: "checkbox-icon", style: { opacity: disabled ? 0.5 : 1 } })),
14
+ React.createElement("span", { style: { opacity: disabled ? 0.5 : 1 } }, props.label)),
10
15
  props.title && (React.createElement("div", { className: "checkbox-item-description" }, props.title))));
11
16
  }
@@ -4,10 +4,11 @@ import { ReactWidget } from '@jupyterlab/apputils';
4
4
  import { VscWarning } from 'react-icons/vsc';
5
5
  import * as path from 'path';
6
6
  import copySvgstr from '../../style/icons/copy.svg';
7
- import { NBIAPI } from '../api';
7
+ import { ClaudeModelType, ClaudeToolType, NBIAPI } from '../api';
8
8
  import { CheckBoxItem } from './checkbox';
9
9
  import { PillItem } from './pill';
10
10
  import { mcpServerSettingsToEnabledState } from './mcp-util';
11
+ import claudeSvg from '../../style/claude.svg';
11
12
  const OPENAI_COMPATIBLE_CHAT_MODEL_ID = 'openai-compatible-chat-model';
12
13
  const LITELLM_COMPATIBLE_CHAT_MODEL_ID = 'litellm-compatible-chat-model';
13
14
  const OPENAI_COMPATIBLE_INLINE_COMPLETION_MODEL_ID = 'openai-compatible-inline-completion-model';
@@ -32,7 +33,8 @@ function SettingsPanelComponent(props) {
32
33
  React.createElement(SettingsPanelTabsComponent, { onTabSelected: onTabSelected, activeTab: activeTab })),
33
34
  React.createElement("div", { className: "nbi-settings-panel-tab-content" },
34
35
  activeTab === 'general' && (React.createElement(SettingsPanelComponentGeneral, { onSave: props.onSave, onEditMCPConfigClicked: props.onEditMCPConfigClicked })),
35
- activeTab === 'mcp-servers' && (React.createElement(SettingsPanelComponentMCPServers, { onEditMCPConfigClicked: props.onEditMCPConfigClicked })))));
36
+ activeTab === 'mcp-servers' && (React.createElement(SettingsPanelComponentMCPServers, { onEditMCPConfigClicked: props.onEditMCPConfigClicked })),
37
+ activeTab === 'claude' && (React.createElement(SettingsPanelComponentClaude, { onEditMCPConfigClicked: props.onEditMCPConfigClicked })))));
36
38
  }
37
39
  function SettingsPanelTabsComponent(props) {
38
40
  const [activeTab, setActiveTab] = useState(props.activeTab);
@@ -41,6 +43,12 @@ function SettingsPanelTabsComponent(props) {
41
43
  setActiveTab('general');
42
44
  props.onTabSelected('general');
43
45
  } }, "General"),
46
+ React.createElement("div", { className: `nbi-settings-panel-tab ${activeTab === 'claude' ? 'active' : ''}`, onClick: () => {
47
+ setActiveTab('claude');
48
+ props.onTabSelected('claude');
49
+ } },
50
+ React.createElement("span", { className: "claude-icon", dangerouslySetInnerHTML: { __html: claudeSvg } }),
51
+ "Claude"),
44
52
  React.createElement("div", { className: `nbi-settings-panel-tab ${activeTab === 'mcp-servers' ? 'active' : ''}`, onClick: () => {
45
53
  setActiveTab('mcp-servers');
46
54
  props.onTabSelected('mcp-servers');
@@ -378,3 +386,136 @@ function SettingsPanelComponentMCPServers(props) {
378
386
  React.createElement("button", { className: "jp-Dialog-button jp-mod-accept jp-mod-styled", style: { width: 'max-content' }, onClick: props.onEditMCPConfigClicked },
379
387
  React.createElement("div", { className: "jp-Dialog-buttonLabel" }, "Add / Edit")))))))));
380
388
  }
389
+ function SettingsPanelComponentClaude(props) {
390
+ var _a, _b, _c, _d, _e, _f;
391
+ const nbiConfig = NBIAPI.config;
392
+ const claudeSettingsRef = useRef(nbiConfig.claudeSettings);
393
+ const [_renderCount, setRenderCount] = useState(1);
394
+ const [claudeEnabled, setClaudeEnabled] = useState(nbiConfig.isInClaudeCodeMode);
395
+ const [chatModel, setChatModel] = useState((_a = nbiConfig.claudeSettings.chat_model) !== null && _a !== void 0 ? _a : ClaudeModelType.Default);
396
+ const [inlineCompletionModel, setInlineCompletionModel] = useState((_b = nbiConfig.claudeSettings.inline_completion_model) !== null && _b !== void 0 ? _b : ClaudeModelType.Default);
397
+ const [apiKey, setApiKey] = useState((_c = nbiConfig.claudeSettings.api_key) !== null && _c !== void 0 ? _c : '');
398
+ const [baseUrl, setBaseUrl] = useState((_d = nbiConfig.claudeSettings.base_url) !== null && _d !== void 0 ? _d : '');
399
+ const [settingSources, setSettingSources] = useState((_e = nbiConfig.claudeSettings.setting_sources) !== null && _e !== void 0 ? _e : []);
400
+ const [tools, setTools] = useState((_f = nbiConfig.claudeSettings.tools) !== null && _f !== void 0 ? _f : [
401
+ ClaudeToolType.ClaudeCodeTools,
402
+ ClaudeToolType.JupyterUITools
403
+ ]);
404
+ useEffect(() => {
405
+ NBIAPI.configChanged.connect(() => {
406
+ claudeSettingsRef.current = nbiConfig.claudeSettings;
407
+ setRenderCount(renderCount => renderCount + 1);
408
+ });
409
+ }, []);
410
+ const syncSettingsToServerState = () => {
411
+ NBIAPI.setConfig({
412
+ claude_settings: {
413
+ enabled: claudeEnabled,
414
+ chat_model: chatModel,
415
+ inline_completion_model: inlineCompletionModel,
416
+ api_key: apiKey,
417
+ base_url: baseUrl,
418
+ setting_sources: settingSources,
419
+ tools: tools
420
+ }
421
+ });
422
+ };
423
+ useEffect(() => {
424
+ syncSettingsToServerState();
425
+ }, [
426
+ claudeEnabled,
427
+ chatModel,
428
+ inlineCompletionModel,
429
+ apiKey,
430
+ baseUrl,
431
+ settingSources,
432
+ tools
433
+ ]);
434
+ return (React.createElement("div", { className: "config-dialog" },
435
+ React.createElement("div", { className: "config-dialog-body" },
436
+ React.createElement("div", { className: "model-config-section" },
437
+ React.createElement("div", { className: "model-config-section-header" }, "Enable Claude mode"),
438
+ React.createElement("div", { className: "model-config-section-body" },
439
+ React.createElement("div", { className: "model-config-section-row" },
440
+ React.createElement("span", null,
441
+ "This requires a",
442
+ ' ',
443
+ React.createElement("a", { href: "https://claude.ai", target: "_blank" }, "Claude"),
444
+ ' ',
445
+ "account and",
446
+ ' ',
447
+ React.createElement("a", { href: "https://code.claude.com/", target: "_blank" }, "Claude Code"),
448
+ ' ',
449
+ "installed in your system.")),
450
+ React.createElement("div", { className: "model-config-section-row" },
451
+ React.createElement("div", { className: "model-config-section-column" },
452
+ React.createElement("div", null,
453
+ React.createElement(CheckBoxItem, { header: true, label: "Enable Claude mode", checked: claudeEnabled, onClick: () => {
454
+ setClaudeEnabled(!claudeEnabled);
455
+ } })))))),
456
+ React.createElement("div", { className: "model-config-section" },
457
+ React.createElement("div", { className: "model-config-section-header" }, "Models"),
458
+ React.createElement("div", { className: "model-config-section-body" },
459
+ React.createElement("div", { className: "model-config-section-row" },
460
+ React.createElement("div", { className: "model-config-section-column" },
461
+ React.createElement("div", null, "Chat model"),
462
+ React.createElement("div", null,
463
+ React.createElement("select", { className: "jp-mod-styled", onChange: event => setChatModel(event.target.value) },
464
+ React.createElement("option", { value: ClaudeModelType.Default, selected: chatModel === ClaudeModelType.Default }, "Default (recommended)"),
465
+ React.createElement("option", { value: ClaudeModelType.ClaudeOpus45, selected: chatModel === ClaudeModelType.ClaudeOpus45 }, "Claude Opus 4.5"),
466
+ React.createElement("option", { value: ClaudeModelType.ClaudeHaiku45, selected: chatModel === ClaudeModelType.ClaudeHaiku45 }, "Claude Haiku 4.5")))),
467
+ React.createElement("div", { className: "model-config-section-column" },
468
+ React.createElement("div", null, "Auto-complete model"),
469
+ React.createElement("div", null,
470
+ React.createElement("select", { className: "jp-mod-styled", onChange: event => setInlineCompletionModel(event.target.value) },
471
+ React.createElement("option", { value: ClaudeModelType.Default, selected: inlineCompletionModel === ClaudeModelType.Default }, "Default (recommended)"),
472
+ React.createElement("option", { value: ClaudeModelType.ClaudeOpus45, selected: inlineCompletionModel === ClaudeModelType.ClaudeOpus45 }, "Claude Opus 4.5"),
473
+ React.createElement("option", { value: ClaudeModelType.ClaudeHaiku45, selected: inlineCompletionModel === ClaudeModelType.ClaudeHaiku45 }, "Claude Haiku 4.5"))))))),
474
+ React.createElement("div", { className: "model-config-section" },
475
+ React.createElement("div", { className: "model-config-section-header" }, "Chat Agent setting sources"),
476
+ React.createElement("div", { className: "model-config-section-body" },
477
+ React.createElement("div", { className: "model-config-section-row" },
478
+ React.createElement("div", { className: "model-config-section-column" },
479
+ React.createElement("div", null,
480
+ React.createElement(CheckBoxItem, { header: true, label: "User", checked: settingSources.includes('user'), onClick: () => {
481
+ setSettingSources(settingSources.includes('user')
482
+ ? settingSources.filter((source) => source !== 'user')
483
+ : [...settingSources, 'user']);
484
+ } }))),
485
+ React.createElement("div", { className: "model-config-section-column" },
486
+ React.createElement("div", null,
487
+ React.createElement(CheckBoxItem, { header: true, label: "Project (Jupyter root directory)", checked: settingSources.includes('project'), onClick: () => {
488
+ setSettingSources(settingSources.includes('project')
489
+ ? settingSources.filter((source) => source !== 'project')
490
+ : [...settingSources, 'project']);
491
+ } })))))),
492
+ React.createElement("div", { className: "model-config-section" },
493
+ React.createElement("div", { className: "model-config-section-header" }, "Chat Agent tools"),
494
+ React.createElement("div", { className: "model-config-section-body" },
495
+ React.createElement("div", { className: "model-config-section-row" },
496
+ React.createElement("div", { className: "model-config-section-column" },
497
+ React.createElement("div", null,
498
+ React.createElement(CheckBoxItem, { header: true, label: "Claude Code tools", checked: tools.includes(ClaudeToolType.ClaudeCodeTools), disabled: true, onClick: () => {
499
+ setTools(tools.includes(ClaudeToolType.ClaudeCodeTools)
500
+ ? tools.filter((tool) => tool !== ClaudeToolType.ClaudeCodeTools)
501
+ : [...tools, ClaudeToolType.ClaudeCodeTools]);
502
+ } }))),
503
+ React.createElement("div", { className: "model-config-section-column" },
504
+ React.createElement("div", null,
505
+ React.createElement(CheckBoxItem, { header: true, label: "Jupyter UI tools", checked: tools.includes(ClaudeToolType.JupyterUITools), onClick: () => {
506
+ setTools(tools.includes(ClaudeToolType.JupyterUITools)
507
+ ? tools.filter((tool) => tool !== ClaudeToolType.JupyterUITools)
508
+ : [...tools, ClaudeToolType.JupyterUITools]);
509
+ } })))))),
510
+ React.createElement("div", { className: "model-config-section" },
511
+ React.createElement("div", { className: "model-config-section-header" }, "Claude account"),
512
+ React.createElement("div", { className: "model-config-section-body" },
513
+ React.createElement("div", { className: "model-config-section-row" },
514
+ React.createElement("div", { className: "model-config-section-column" },
515
+ React.createElement("div", { className: "form-field-row" },
516
+ React.createElement("div", { className: "form-field-description" }, "API Key (optional)"),
517
+ React.createElement("input", { name: "chat-model-id-input", placeholder: "API Key", className: "jp-mod-styled", spellCheck: false, value: apiKey, onChange: event => setApiKey(event.target.value) })),
518
+ React.createElement("div", { className: "form-field-row" },
519
+ React.createElement("div", { className: "form-field-description" }, "Base URL (optional)"),
520
+ React.createElement("input", { name: "chat-model-id-input", placeholder: "https://api.anthropic.com", className: "jp-mod-styled", spellCheck: false, value: baseUrl, onChange: event => setBaseUrl(event.target.value) })))))))));
521
+ }
package/lib/index.js CHANGED
@@ -276,9 +276,10 @@ class NBIInlineCompletionProvider {
276
276
  }
277
277
  }
278
278
  const nbiConfig = NBIAPI.config;
279
- const inlineCompletionsEnabled = nbiConfig.inlineCompletionModel.provider === GITHUB_COPILOT_PROVIDER_ID
280
- ? NBIAPI.getLoginStatus() === GitHubCopilotLoginStatus.LoggedIn
281
- : nbiConfig.inlineCompletionModel.provider !== 'none';
279
+ const inlineCompletionsEnabled = nbiConfig.isInClaudeCodeMode ||
280
+ (nbiConfig.inlineCompletionModel.provider === GITHUB_COPILOT_PROVIDER_ID
281
+ ? NBIAPI.getLoginStatus() === GitHubCopilotLoginStatus.LoggedIn
282
+ : nbiConfig.inlineCompletionModel.provider !== 'none');
282
283
  this._telemetryEmitter.emitTelemetryEvent({
283
284
  type: TelemetryEventType.InlineCompletionRequest,
284
285
  data: {
@@ -767,9 +768,10 @@ const plugin = {
767
768
  NBIAPI.getLoginStatus() === GitHubCopilotLoginStatus.NotLoggedIn);
768
769
  };
769
770
  const isChatEnabled = () => {
770
- return NBIAPI.config.chatModel.provider === GITHUB_COPILOT_PROVIDER_ID
771
- ? !githubLoginRequired()
772
- : NBIAPI.config.chatModel.provider !== 'none';
771
+ return (NBIAPI.config.isInClaudeCodeMode ||
772
+ (NBIAPI.config.chatModel.provider === GITHUB_COPILOT_PROVIDER_ID
773
+ ? !githubLoginRequired()
774
+ : NBIAPI.config.chatModel.provider !== 'none'));
773
775
  };
774
776
  const isActiveCellCodeCell = () => {
775
777
  if (!(app.shell.currentWidget instanceof NotebookPanel)) {
@@ -1463,10 +1465,12 @@ const plugin = {
1463
1465
  item: githubCopilotStatusBarItem,
1464
1466
  align: 'right',
1465
1467
  rank: 100,
1466
- isActive: () => NBIAPI.config.usingGitHubCopilotModel
1468
+ isActive: () => !NBIAPI.config.isInClaudeCodeMode &&
1469
+ NBIAPI.config.usingGitHubCopilotModel
1467
1470
  });
1468
1471
  NBIAPI.configChanged.connect(() => {
1469
- if (NBIAPI.config.usingGitHubCopilotModel) {
1472
+ if (!NBIAPI.config.isInClaudeCodeMode &&
1473
+ NBIAPI.config.usingGitHubCopilotModel) {
1470
1474
  githubCopilotStatusBarItem.show();
1471
1475
  }
1472
1476
  else {
package/lib/tokens.d.ts CHANGED
@@ -27,7 +27,8 @@ export declare enum BackendMessageType {
27
27
  StreamEnd = "stream-end",
28
28
  RunUICommand = "run-ui-command",
29
29
  GitHubCopilotLoginStatusChange = "github-copilot-login-status-change",
30
- MCPServerStatusChange = "mcp-server-status-change"
30
+ MCPServerStatusChange = "mcp-server-status-change",
31
+ ClaudeCodeStatusChange = "claude-code-status-change"
31
32
  }
32
33
  export declare enum ResponseStreamDataType {
33
34
  LLMRaw = "llm-raw",
@@ -38,7 +39,8 @@ export declare enum ResponseStreamDataType {
38
39
  Button = "button",
39
40
  Anchor = "anchor",
40
41
  Progress = "progress",
41
- Confirmation = "confirmation"
42
+ Confirmation = "confirmation",
43
+ AskUserQuestion = "ask-user-question"
42
44
  }
43
45
  export declare enum ContextType {
44
46
  Custom = "custom",
@@ -93,6 +95,7 @@ export declare enum BuiltinToolsetType {
93
95
  CommandExecute = "nbi-command-execute"
94
96
  }
95
97
  export declare const GITHUB_COPILOT_PROVIDER_ID = "github-copilot";
98
+ export declare const CLAUDE_CODE_CHAT_PARTICIPANT_ID = "claude-code";
96
99
  export declare enum TelemetryEventType {
97
100
  InlineCompletionRequest = "inline-completion-request",
98
101
  ExplainThisRequest = "explain-this-request",