@plmbr/notebook-intelligence 5.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/LICENSE +674 -0
- package/README.md +412 -0
- package/lib/api.d.ts +288 -0
- package/lib/api.js +927 -0
- package/lib/cell-output-bundle.d.ts +25 -0
- package/lib/cell-output-bundle.js +129 -0
- package/lib/cell-output-toolbar.d.ts +26 -0
- package/lib/cell-output-toolbar.js +188 -0
- package/lib/chat-progress-feedback.d.ts +3 -0
- package/lib/chat-progress-feedback.js +27 -0
- package/lib/chat-sidebar.d.ts +92 -0
- package/lib/chat-sidebar.js +3452 -0
- package/lib/command-ids.d.ts +39 -0
- package/lib/command-ids.js +44 -0
- package/lib/components/ask-user-question.d.ts +2 -0
- package/lib/components/ask-user-question.js +85 -0
- package/lib/components/checkbox.d.ts +2 -0
- package/lib/components/checkbox.js +30 -0
- package/lib/components/claude-mcp-panel.d.ts +2 -0
- package/lib/components/claude-mcp-panel.js +275 -0
- package/lib/components/claude-mcp-paste.d.ts +7 -0
- package/lib/components/claude-mcp-paste.js +104 -0
- package/lib/components/claude-session-picker.d.ts +8 -0
- package/lib/components/claude-session-picker.js +127 -0
- package/lib/components/form-dialog.d.ts +25 -0
- package/lib/components/form-dialog.js +35 -0
- package/lib/components/launcher-picker.d.ts +6 -0
- package/lib/components/launcher-picker.js +135 -0
- package/lib/components/mcp-util.d.ts +2 -0
- package/lib/components/mcp-util.js +37 -0
- package/lib/components/notebook-generation-popover.d.ts +7 -0
- package/lib/components/notebook-generation-popover.js +60 -0
- package/lib/components/pill.d.ts +2 -0
- package/lib/components/pill.js +5 -0
- package/lib/components/plugins-panel.d.ts +3 -0
- package/lib/components/plugins-panel.js +466 -0
- package/lib/components/settings-panel.d.ts +11 -0
- package/lib/components/settings-panel.js +742 -0
- package/lib/components/skills-panel.d.ts +2 -0
- package/lib/components/skills-panel.js +1264 -0
- package/lib/handler.d.ts +8 -0
- package/lib/handler.js +36 -0
- package/lib/icons.d.ts +45 -0
- package/lib/icons.js +54 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.js +2079 -0
- package/lib/markdown-renderer.d.ts +10 -0
- package/lib/markdown-renderer.js +64 -0
- package/lib/notebook-generation-toolbar.d.ts +16 -0
- package/lib/notebook-generation-toolbar.js +197 -0
- package/lib/notebook-generation.d.ts +8 -0
- package/lib/notebook-generation.js +12 -0
- package/lib/open-file-refresh-watcher-env.d.ts +4 -0
- package/lib/open-file-refresh-watcher-env.js +33 -0
- package/lib/open-file-refresh-watcher.d.ts +97 -0
- package/lib/open-file-refresh-watcher.js +190 -0
- package/lib/shell-utils.d.ts +6 -0
- package/lib/shell-utils.js +9 -0
- package/lib/task-target-notebook.d.ts +2 -0
- package/lib/task-target-notebook.js +28 -0
- package/lib/terminal-drag-format.d.ts +9 -0
- package/lib/terminal-drag-format.js +23 -0
- package/lib/terminal-drag.d.ts +12 -0
- package/lib/terminal-drag.js +268 -0
- package/lib/tokens.d.ts +149 -0
- package/lib/tokens.js +88 -0
- package/lib/tour/tour-anchors.d.ts +18 -0
- package/lib/tour/tour-anchors.js +18 -0
- package/lib/tour/tour-config.d.ts +66 -0
- package/lib/tour/tour-config.js +99 -0
- package/lib/tour/tour-defaults.json +58 -0
- package/lib/tour/tour-events.d.ts +19 -0
- package/lib/tour/tour-events.js +30 -0
- package/lib/tour/tour-overlay.d.ts +6 -0
- package/lib/tour/tour-overlay.js +350 -0
- package/lib/tour/tour-state.d.ts +20 -0
- package/lib/tour/tour-state.js +81 -0
- package/lib/tour/tour-steps.d.ts +33 -0
- package/lib/tour/tour-steps.js +216 -0
- package/lib/utils.d.ts +53 -0
- package/lib/utils.js +385 -0
- package/package.json +258 -0
- package/schema/plugin.json +42 -0
- package/src/api.ts +1424 -0
- package/src/cell-output-bundle.ts +176 -0
- package/src/cell-output-toolbar.ts +232 -0
- package/src/chat-progress-feedback.ts +35 -0
- package/src/chat-sidebar.tsx +5147 -0
- package/src/command-ids.ts +67 -0
- package/src/components/ask-user-question.tsx +151 -0
- package/src/components/checkbox.tsx +62 -0
- package/src/components/claude-mcp-panel.tsx +543 -0
- package/src/components/claude-mcp-paste.ts +132 -0
- package/src/components/claude-session-picker.tsx +214 -0
- package/src/components/form-dialog.tsx +75 -0
- package/src/components/launcher-picker.tsx +237 -0
- package/src/components/mcp-util.ts +53 -0
- package/src/components/notebook-generation-popover.tsx +127 -0
- package/src/components/pill.tsx +15 -0
- package/src/components/plugins-panel.tsx +774 -0
- package/src/components/settings-panel.tsx +1631 -0
- package/src/components/skills-panel.tsx +2084 -0
- package/src/handler.ts +51 -0
- package/src/icons.ts +71 -0
- package/src/index.ts +2583 -0
- package/src/markdown-renderer.tsx +153 -0
- package/src/notebook-generation-toolbar.tsx +281 -0
- package/src/notebook-generation.ts +23 -0
- package/src/open-file-refresh-watcher-env.ts +52 -0
- package/src/open-file-refresh-watcher.ts +260 -0
- package/src/shell-utils.ts +10 -0
- package/src/svg.d.ts +4 -0
- package/src/task-target-notebook.ts +37 -0
- package/src/terminal-drag-format.ts +29 -0
- package/src/terminal-drag.ts +382 -0
- package/src/tokens.ts +171 -0
- package/src/tour/tour-anchors.ts +21 -0
- package/src/tour/tour-config.ts +160 -0
- package/src/tour/tour-events.ts +34 -0
- package/src/tour/tour-overlay.tsx +474 -0
- package/src/tour/tour-state.ts +87 -0
- package/src/tour/tour-steps.ts +281 -0
- package/src/utils.ts +455 -0
- package/style/base.css +3238 -0
- package/style/icons/cell-toolbar-bug.svg +5 -0
- package/style/icons/cell-toolbar-chat.svg +5 -0
- package/style/icons/cell-toolbar-sparkle.svg +5 -0
- package/style/icons/claude.svg +1 -0
- package/style/icons/copilot-warning.svg +1 -0
- package/style/icons/copilot.svg +1 -0
- package/style/icons/copy.svg +1 -0
- package/style/icons/openai.svg +1 -0
- package/style/icons/opencode.svg +1 -0
- package/style/icons/sparkles-warning.svg +5 -0
- package/style/icons/sparkles.svg +1 -0
- package/style/index.css +1 -0
- package/style/index.js +1 -0
package/src/api.ts
ADDED
|
@@ -0,0 +1,1424 @@
|
|
|
1
|
+
// Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>
|
|
2
|
+
|
|
3
|
+
import { ServerConnection } from '@jupyterlab/services';
|
|
4
|
+
import { requestAPI } from './handler';
|
|
5
|
+
import { URLExt } from '@jupyterlab/coreutils';
|
|
6
|
+
import { Signal } from '@lumino/signaling';
|
|
7
|
+
import {
|
|
8
|
+
GITHUB_COPILOT_PROVIDER_ID,
|
|
9
|
+
IChatCompletionResponseEmitter,
|
|
10
|
+
IChatParticipant,
|
|
11
|
+
IContextItem,
|
|
12
|
+
ITelemetryEvent,
|
|
13
|
+
IToolSelections,
|
|
14
|
+
RequestDataType,
|
|
15
|
+
BackendMessageType,
|
|
16
|
+
AssistantMode
|
|
17
|
+
} from './tokens';
|
|
18
|
+
|
|
19
|
+
export enum GitHubCopilotLoginStatus {
|
|
20
|
+
NotLoggedIn = 'NOT_LOGGED_IN',
|
|
21
|
+
ActivatingDevice = 'ACTIVATING_DEVICE',
|
|
22
|
+
LoggingIn = 'LOGGING_IN',
|
|
23
|
+
LoggedIn = 'LOGGED_IN'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface IDeviceVerificationInfo {
|
|
27
|
+
verificationURI: string;
|
|
28
|
+
userCode: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export enum ClaudeModelType {
|
|
32
|
+
None = 'none',
|
|
33
|
+
Inherit = 'inherit',
|
|
34
|
+
Default = ''
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface IClaudeModelInfo {
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
contextWindow: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface IClaudeSessionInfo {
|
|
44
|
+
session_id: string;
|
|
45
|
+
path: string;
|
|
46
|
+
modified_at: number;
|
|
47
|
+
created_at: number;
|
|
48
|
+
preview: string;
|
|
49
|
+
cwd: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface IClaudeSessionList {
|
|
53
|
+
sessions: IClaudeSessionInfo[];
|
|
54
|
+
// The realpath-resolved JupyterLab working directory. `claude --resume
|
|
55
|
+
// <id>` is cwd-scoped, so the frontend pairs this with the session id to
|
|
56
|
+
// produce a copyable shell command.
|
|
57
|
+
currentCwd: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type ClaudeSessionScope = 'cwd' | 'all';
|
|
61
|
+
|
|
62
|
+
export enum ClaudeToolType {
|
|
63
|
+
ClaudeCodeTools = 'claude-code:built-in-tools',
|
|
64
|
+
JupyterUITools = 'nbi:built-in-jupyter-ui-tools'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type SkillScope = 'user' | 'project';
|
|
68
|
+
|
|
69
|
+
export type ClaudeMCPScope = 'user' | 'project' | 'local';
|
|
70
|
+
export type ClaudeMCPTransport = 'stdio' | 'sse' | 'http';
|
|
71
|
+
|
|
72
|
+
export type PluginScope = 'user' | 'project' | 'local';
|
|
73
|
+
|
|
74
|
+
// Claude's `claude plugin list --json` output schema isn't formally
|
|
75
|
+
// documented; we forward the raw object and let the panel fish out fields
|
|
76
|
+
// it cares about. Defining only the fields we observe today as optional
|
|
77
|
+
// keeps newer Claude releases from getting truncated.
|
|
78
|
+
export interface IPluginInfo {
|
|
79
|
+
id?: string;
|
|
80
|
+
name?: string;
|
|
81
|
+
scope?: PluginScope | string;
|
|
82
|
+
enabled?: boolean;
|
|
83
|
+
marketplace?: string;
|
|
84
|
+
version?: string;
|
|
85
|
+
description?: string;
|
|
86
|
+
[key: string]: unknown;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface IPluginMarketplaceInfo {
|
|
90
|
+
name?: string;
|
|
91
|
+
source?: string;
|
|
92
|
+
scope?: PluginScope | string;
|
|
93
|
+
description?: string;
|
|
94
|
+
version?: string;
|
|
95
|
+
plugin_count?: number;
|
|
96
|
+
plugin_names?: string[];
|
|
97
|
+
[key: string]: unknown;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface IPluginMarketplacePluginInfo extends IPluginInfo {
|
|
101
|
+
source?: unknown;
|
|
102
|
+
category?: string;
|
|
103
|
+
tags?: string[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface IClaudeMCPServer {
|
|
107
|
+
name: string;
|
|
108
|
+
scope: ClaudeMCPScope;
|
|
109
|
+
transport: ClaudeMCPTransport | string;
|
|
110
|
+
command: string;
|
|
111
|
+
args: string[];
|
|
112
|
+
env: Record<string, string>;
|
|
113
|
+
url: string;
|
|
114
|
+
headers: Record<string, string>;
|
|
115
|
+
disabledForWorkspace: boolean;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface IClaudeMCPAddInput {
|
|
119
|
+
name: string;
|
|
120
|
+
scope: ClaudeMCPScope;
|
|
121
|
+
transport: ClaudeMCPTransport;
|
|
122
|
+
commandOrUrl: string;
|
|
123
|
+
args?: string[];
|
|
124
|
+
env?: Record<string, string>;
|
|
125
|
+
headers?: Record<string, string>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function claudeMCPServerFromWire(wire: any): IClaudeMCPServer {
|
|
129
|
+
return {
|
|
130
|
+
name: String(wire?.name ?? ''),
|
|
131
|
+
scope: (wire?.scope ?? 'user') as ClaudeMCPScope,
|
|
132
|
+
transport: String(wire?.transport ?? 'stdio'),
|
|
133
|
+
command: String(wire?.command ?? ''),
|
|
134
|
+
args: Array.isArray(wire?.args) ? wire.args.map(String) : [],
|
|
135
|
+
env:
|
|
136
|
+
wire?.env && typeof wire.env === 'object'
|
|
137
|
+
? Object.fromEntries(
|
|
138
|
+
Object.entries(wire.env).map(([k, v]) => [String(k), String(v)])
|
|
139
|
+
)
|
|
140
|
+
: {},
|
|
141
|
+
url: String(wire?.url ?? ''),
|
|
142
|
+
headers:
|
|
143
|
+
wire?.headers && typeof wire.headers === 'object'
|
|
144
|
+
? Object.fromEntries(
|
|
145
|
+
Object.entries(wire.headers).map(([k, v]) => [String(k), String(v)])
|
|
146
|
+
)
|
|
147
|
+
: {},
|
|
148
|
+
disabledForWorkspace: Boolean(wire?.disabled_for_workspace)
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface ISkillSummary {
|
|
153
|
+
scope: SkillScope;
|
|
154
|
+
name: string;
|
|
155
|
+
description: string;
|
|
156
|
+
allowedTools: string[];
|
|
157
|
+
rootPath: string;
|
|
158
|
+
files: string[];
|
|
159
|
+
source: string;
|
|
160
|
+
managed: boolean;
|
|
161
|
+
managedSource: string;
|
|
162
|
+
managedRef: string;
|
|
163
|
+
// User-imported GitHub skills that opted into auto-sync. Distinct
|
|
164
|
+
// from `managed`: tracking skills are editable and never auto-removed,
|
|
165
|
+
// only manually replaced when the user clicks Sync.
|
|
166
|
+
tracksUpstream: boolean;
|
|
167
|
+
trackingRef: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface IReconcileResult {
|
|
171
|
+
added: number;
|
|
172
|
+
updated: number;
|
|
173
|
+
removed: number;
|
|
174
|
+
unchanged: number;
|
|
175
|
+
errors: string[];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface ISkillDetail extends ISkillSummary {
|
|
179
|
+
body: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface ISkillsContext {
|
|
183
|
+
projectRoot: string;
|
|
184
|
+
projectName: string;
|
|
185
|
+
userSkillsDir: string;
|
|
186
|
+
projectSkillsDir: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface ISkillImportPreview {
|
|
190
|
+
name: string;
|
|
191
|
+
description: string;
|
|
192
|
+
allowedTools: string[];
|
|
193
|
+
body: string;
|
|
194
|
+
files: string[];
|
|
195
|
+
sourceUrl: string;
|
|
196
|
+
canonicalUrl: string;
|
|
197
|
+
existsInUserScope: boolean;
|
|
198
|
+
existsInProjectScope: boolean;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Exported for direct testing of the wire-format contract. The snake_case
|
|
202
|
+
// keys it consumes (managed_source, managed_ref, tracks_upstream,
|
|
203
|
+
// tracking_ref, allowed_tools) are the load-bearing JSON shape between
|
|
204
|
+
// the Tornado handlers and the panel; a typo here would silently corrupt
|
|
205
|
+
// user state ("I toggled it on but it didn't stick").
|
|
206
|
+
export function skillFromWire(wire: any): ISkillDetail {
|
|
207
|
+
return {
|
|
208
|
+
scope: wire.scope,
|
|
209
|
+
name: wire.name,
|
|
210
|
+
description: wire.description,
|
|
211
|
+
allowedTools: wire.allowed_tools ?? [],
|
|
212
|
+
rootPath: wire.root_path,
|
|
213
|
+
files: wire.files ?? [],
|
|
214
|
+
source: wire.source ?? '',
|
|
215
|
+
managed: Boolean(wire.managed),
|
|
216
|
+
managedSource: wire.managed_source ?? '',
|
|
217
|
+
managedRef: wire.managed_ref ?? '',
|
|
218
|
+
tracksUpstream: Boolean(wire.tracks_upstream),
|
|
219
|
+
trackingRef: wire.tracking_ref ?? '',
|
|
220
|
+
body: wire.body ?? ''
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface ISyncSkillResult {
|
|
225
|
+
updated: boolean;
|
|
226
|
+
ref: string;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export interface ISyncAllTrackingEntry {
|
|
230
|
+
scope: SkillScope;
|
|
231
|
+
name: string;
|
|
232
|
+
updated?: boolean;
|
|
233
|
+
ref?: string;
|
|
234
|
+
error?: string;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function claudeModelFromWire(wire: any): IClaudeModelInfo {
|
|
238
|
+
return {
|
|
239
|
+
id: wire.id,
|
|
240
|
+
name: wire.name,
|
|
241
|
+
contextWindow: wire.context_window
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export interface ICellOutputFeatureFlag {
|
|
246
|
+
enabled: boolean;
|
|
247
|
+
locked: boolean;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export interface ICellOutputFeatures {
|
|
251
|
+
explain_error: ICellOutputFeatureFlag;
|
|
252
|
+
output_followup: ICellOutputFeatureFlag;
|
|
253
|
+
output_toolbar: ICellOutputFeatureFlag;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Per-action flags (the whole-toolbar gate `output_toolbar` is checked
|
|
257
|
+
// separately by callers).
|
|
258
|
+
export type CellOutputActionFlag = Exclude<
|
|
259
|
+
keyof ICellOutputFeatures,
|
|
260
|
+
'output_toolbar'
|
|
261
|
+
>;
|
|
262
|
+
|
|
263
|
+
// Boolean admin policies covering settings panel toggles. Mirrors
|
|
264
|
+
// FEATURE_POLICY_NAMES in extension.py — keep them in sync.
|
|
265
|
+
export type FeaturePolicyName =
|
|
266
|
+
| 'explain_error'
|
|
267
|
+
| 'output_followup'
|
|
268
|
+
| 'output_toolbar'
|
|
269
|
+
| 'claude_mode'
|
|
270
|
+
| 'claude_continue_conversation'
|
|
271
|
+
| 'claude_code_tools'
|
|
272
|
+
| 'claude_jupyter_ui_tools'
|
|
273
|
+
| 'claude_setting_source_user'
|
|
274
|
+
| 'claude_setting_source_project'
|
|
275
|
+
| 'store_github_access_token'
|
|
276
|
+
| 'skills_management'
|
|
277
|
+
| 'claude_mcp_management'
|
|
278
|
+
| 'claude_plugins_management'
|
|
279
|
+
| 'terminal_drag_drop'
|
|
280
|
+
| 'refresh_open_files_on_disk_change';
|
|
281
|
+
|
|
282
|
+
export type IFeaturePolicies = Record<
|
|
283
|
+
FeaturePolicyName,
|
|
284
|
+
ICellOutputFeatureFlag
|
|
285
|
+
>;
|
|
286
|
+
|
|
287
|
+
// Non-boolean settings whose value is locked when an admin sets the
|
|
288
|
+
// corresponding env var. The value itself is served via its existing
|
|
289
|
+
// capabilities field; this only carries the locked flag.
|
|
290
|
+
export type SettingLockName =
|
|
291
|
+
| 'chat_model_provider'
|
|
292
|
+
| 'chat_model_id'
|
|
293
|
+
| 'inline_completion_model_provider'
|
|
294
|
+
| 'inline_completion_model_id'
|
|
295
|
+
| 'claude_chat_model'
|
|
296
|
+
| 'claude_inline_completion_model'
|
|
297
|
+
| 'claude_api_key'
|
|
298
|
+
| 'claude_base_url';
|
|
299
|
+
|
|
300
|
+
export type ISettingLocks = Record<SettingLockName, { locked: boolean }>;
|
|
301
|
+
|
|
302
|
+
// Shared frozen object returned by NBIConfig.tourOverrides when no
|
|
303
|
+
// admin overrides are present. Stable identity matters for downstream
|
|
304
|
+
// consumers (memoized command-palette label, useMemo deps).
|
|
305
|
+
const EMPTY_TOUR_OVERRIDES: Readonly<Record<string, any>> = Object.freeze({});
|
|
306
|
+
|
|
307
|
+
export class NBIConfig {
|
|
308
|
+
get userHomeDir(): string {
|
|
309
|
+
return this.capabilities.user_home_dir;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
get userConfigDir(): string {
|
|
313
|
+
return this.capabilities.nbi_user_config_dir;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
get llmProviders(): [any] {
|
|
317
|
+
return this.capabilities.llm_providers;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
get chatModels(): [any] {
|
|
321
|
+
return this.capabilities.chat_models;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
get inlineCompletionModels(): [any] {
|
|
325
|
+
return this.capabilities.inline_completion_models;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
get defaultChatMode(): string {
|
|
329
|
+
return this.capabilities.default_chat_mode;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
get chatModel(): any {
|
|
333
|
+
return this.capabilities.chat_model;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
get chatModelSupportsVision(): boolean {
|
|
337
|
+
return this.capabilities.chat_model_supports_vision === true;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
get inlineCompletionModel(): any {
|
|
341
|
+
return this.capabilities.inline_completion_model;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
get usingGitHubCopilotModel(): boolean {
|
|
345
|
+
return (
|
|
346
|
+
this.chatModel.provider === GITHUB_COPILOT_PROVIDER_ID ||
|
|
347
|
+
this.inlineCompletionModel.provider === GITHUB_COPILOT_PROVIDER_ID
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
get storeGitHubAccessToken(): boolean {
|
|
352
|
+
return this.capabilities.store_github_access_token === true;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
get inlineCompletionDebouncerDelay(): number {
|
|
356
|
+
return Number.isInteger(this.capabilities.inline_completion_debouncer_delay)
|
|
357
|
+
? this.capabilities.inline_completion_debouncer_delay
|
|
358
|
+
: 200;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
get toolConfig(): any {
|
|
362
|
+
return this.capabilities.tool_config;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
get mcpServers(): any {
|
|
366
|
+
return this.toolConfig.mcpServers;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
getMCPServer(serverId: string): any {
|
|
370
|
+
return this.toolConfig.mcpServers.find(
|
|
371
|
+
(server: any) => server.id === serverId
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
getMCPServerPrompt(serverId: string, promptName: string): any {
|
|
376
|
+
const server = this.getMCPServer(serverId);
|
|
377
|
+
if (server) {
|
|
378
|
+
return server.prompts.find((prompt: any) => prompt.name === promptName);
|
|
379
|
+
}
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
get mcpServerSettings(): any {
|
|
384
|
+
return this.capabilities.mcp_server_settings;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
get claudeSettings(): any {
|
|
388
|
+
return this.capabilities.claude_settings;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
get claudeModels(): IClaudeModelInfo[] {
|
|
392
|
+
return (this.capabilities.claude_models ?? []).map(claudeModelFromWire);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
get isInClaudeCodeMode(): boolean {
|
|
396
|
+
return this.claudeSettings.enabled === true;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
get isClaudeCliAvailable(): boolean {
|
|
400
|
+
return this.capabilities.claude_cli_available === true;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
get isOpenCodeCliAvailable(): boolean {
|
|
404
|
+
return this.capabilities.opencode_cli_available === true;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
get isPiCliAvailable(): boolean {
|
|
408
|
+
return this.capabilities.pi_cli_available === true;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
get isGitHubCopilotCliAvailable(): boolean {
|
|
412
|
+
return this.capabilities.github_copilot_cli_available === true;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
get isCodexCliAvailable(): boolean {
|
|
416
|
+
return this.capabilities.codex_cli_available === true;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
isCodingAgentLauncherDisabledByPolicy(launcherId: string): boolean {
|
|
420
|
+
// Fail closed when the field is missing or malformed: an admin denylist
|
|
421
|
+
// must not silently disappear if capabilities haven't loaded yet or a
|
|
422
|
+
// backend regression drops the field. The companion `is*CliAvailable`
|
|
423
|
+
// getters already default to false until capabilities arrive, so on
|
|
424
|
+
// first paint the tile is hidden regardless; this just ensures the
|
|
425
|
+
// policy gate stays in effect even if a future change pre-seeds those
|
|
426
|
+
// flags.
|
|
427
|
+
const list = this.capabilities.disabled_coding_agent_launchers;
|
|
428
|
+
if (Array.isArray(list)) {
|
|
429
|
+
return list.includes(launcherId);
|
|
430
|
+
}
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
get chatFeedbackEnabled(): boolean {
|
|
435
|
+
return this.capabilities.chat_feedback_enabled === true;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Admin-supplied tour-copy overrides, served from the capabilities
|
|
439
|
+
// response after server-side validation. Returns the raw dict; the
|
|
440
|
+
// tour module decides how to apply it. Defaults to a shared frozen
|
|
441
|
+
// empty object so callers can spread/access keys without
|
|
442
|
+
// null-checking AND the getter doesn't allocate a fresh `{}` on every
|
|
443
|
+
// read (the JupyterLab command palette polls a command's label
|
|
444
|
+
// thunk on every keystroke, so identity stability matters).
|
|
445
|
+
get tourOverrides(): Record<string, any> {
|
|
446
|
+
const v = this.capabilities.tour_overrides;
|
|
447
|
+
return v && typeof v === 'object' ? v : EMPTY_TOUR_OVERRIDES;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
get allowGithubSkillImport(): boolean {
|
|
451
|
+
return this.capabilities.allow_github_skill_import !== false;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
get additionalSkippedWorkspaceDirectories(): string[] {
|
|
455
|
+
const v = this.capabilities.additional_skipped_workspace_directories;
|
|
456
|
+
return Array.isArray(v) ? v : [];
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
get allowGithubPluginImport(): boolean {
|
|
460
|
+
// Default-open: missing/undefined means the org hasn't gated this, so
|
|
461
|
+
// older backends without the flag continue to allow the GitHub
|
|
462
|
+
// affordance. Mirrors `cellOutputFeatures` polarity.
|
|
463
|
+
return this.capabilities.allow_github_plugin_import !== false;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
get cellOutputFeatures(): ICellOutputFeatures {
|
|
467
|
+
const v = this.capabilities.cell_output_features ?? {};
|
|
468
|
+
return {
|
|
469
|
+
explain_error: {
|
|
470
|
+
enabled: v.explain_error?.enabled !== false,
|
|
471
|
+
locked: v.explain_error?.locked === true
|
|
472
|
+
},
|
|
473
|
+
output_followup: {
|
|
474
|
+
enabled: v.output_followup?.enabled !== false,
|
|
475
|
+
locked: v.output_followup?.locked === true
|
|
476
|
+
},
|
|
477
|
+
output_toolbar: {
|
|
478
|
+
enabled: v.output_toolbar?.enabled !== false,
|
|
479
|
+
locked: v.output_toolbar?.locked === true
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
get featurePolicies(): IFeaturePolicies {
|
|
485
|
+
const v = this.capabilities.feature_policies ?? {};
|
|
486
|
+
const names: FeaturePolicyName[] = [
|
|
487
|
+
'explain_error',
|
|
488
|
+
'output_followup',
|
|
489
|
+
'output_toolbar',
|
|
490
|
+
'claude_mode',
|
|
491
|
+
'claude_continue_conversation',
|
|
492
|
+
'claude_code_tools',
|
|
493
|
+
'claude_jupyter_ui_tools',
|
|
494
|
+
'claude_setting_source_user',
|
|
495
|
+
'claude_setting_source_project',
|
|
496
|
+
'store_github_access_token',
|
|
497
|
+
'skills_management',
|
|
498
|
+
'claude_mcp_management',
|
|
499
|
+
'claude_plugins_management',
|
|
500
|
+
'terminal_drag_drop',
|
|
501
|
+
'refresh_open_files_on_disk_change'
|
|
502
|
+
];
|
|
503
|
+
// Policies that default *open* when the capability field is missing,
|
|
504
|
+
// covering two cases: admin-only management gates (no user toggle) where
|
|
505
|
+
// a new frontend on an older backend must keep the tab visible, and the
|
|
506
|
+
// open-files refresh watcher whose documented default is on so its
|
|
507
|
+
// first ticks before capabilities land don't silently no-op. The other
|
|
508
|
+
// policies pair with a user toggle and default closed (missing means
|
|
509
|
+
// "no user pref recorded yet, treat as off").
|
|
510
|
+
const defaultOpen: ReadonlySet<FeaturePolicyName> = new Set([
|
|
511
|
+
'skills_management',
|
|
512
|
+
'claude_mcp_management',
|
|
513
|
+
'claude_plugins_management',
|
|
514
|
+
'refresh_open_files_on_disk_change'
|
|
515
|
+
]);
|
|
516
|
+
const result = {} as IFeaturePolicies;
|
|
517
|
+
for (const name of names) {
|
|
518
|
+
const entry = v[name];
|
|
519
|
+
// Strict polarity: only default-open when the entry is wholly absent
|
|
520
|
+
// (old backend). A malformed entry (string "false", null, missing
|
|
521
|
+
// `enabled` field) falls through to closed for default-closed gates
|
|
522
|
+
// and stays open only when the field is explicitly true for default-open
|
|
523
|
+
// gates — never silently land in the open bucket.
|
|
524
|
+
let enabled: boolean;
|
|
525
|
+
if (entry === undefined) {
|
|
526
|
+
enabled = defaultOpen.has(name);
|
|
527
|
+
} else {
|
|
528
|
+
enabled = entry.enabled === true;
|
|
529
|
+
}
|
|
530
|
+
result[name] = {
|
|
531
|
+
enabled,
|
|
532
|
+
locked: entry?.locked === true
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
return result;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
get settingLocks(): ISettingLocks {
|
|
539
|
+
const v = this.capabilities.setting_locks ?? {};
|
|
540
|
+
const names: SettingLockName[] = [
|
|
541
|
+
'chat_model_provider',
|
|
542
|
+
'chat_model_id',
|
|
543
|
+
'inline_completion_model_provider',
|
|
544
|
+
'inline_completion_model_id',
|
|
545
|
+
'claude_chat_model',
|
|
546
|
+
'claude_inline_completion_model',
|
|
547
|
+
'claude_api_key',
|
|
548
|
+
'claude_base_url'
|
|
549
|
+
];
|
|
550
|
+
const result = {} as ISettingLocks;
|
|
551
|
+
for (const name of names) {
|
|
552
|
+
result[name] = { locked: v[name]?.locked === true };
|
|
553
|
+
}
|
|
554
|
+
return result;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
capabilities: any = {};
|
|
558
|
+
chatParticipants: IChatParticipant[] = [];
|
|
559
|
+
|
|
560
|
+
changed = new Signal<this, void>(this);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export class NBIAPI {
|
|
564
|
+
static _loginStatus = GitHubCopilotLoginStatus.NotLoggedIn;
|
|
565
|
+
static _deviceVerificationInfo: IDeviceVerificationInfo = {
|
|
566
|
+
verificationURI: '',
|
|
567
|
+
userCode: ''
|
|
568
|
+
};
|
|
569
|
+
static _webSocket: WebSocket;
|
|
570
|
+
static _messageReceived = new Signal<unknown, any>(this);
|
|
571
|
+
static config = new NBIConfig();
|
|
572
|
+
static configChanged = this.config.changed;
|
|
573
|
+
static githubLoginStatusChanged = new Signal<unknown, void>(this);
|
|
574
|
+
static skillsReloaded = new Signal<unknown, void>(this);
|
|
575
|
+
// Emits each time the Claude agent sends its 20s keepalive (#252 follow-up).
|
|
576
|
+
// The chat sidebar uses it to drive the "Generating" indicator's pulse
|
|
577
|
+
// and to swap to a "server may be slow" copy when the gap stretches.
|
|
578
|
+
static claudeCodeHeartbeat = new Signal<unknown, void>(this);
|
|
579
|
+
|
|
580
|
+
static async initialize() {
|
|
581
|
+
await this.fetchCapabilities();
|
|
582
|
+
this.updateGitHubLoginStatus();
|
|
583
|
+
|
|
584
|
+
NBIAPI.initializeWebsocket();
|
|
585
|
+
|
|
586
|
+
this._messageReceived.connect((_, msg) => {
|
|
587
|
+
msg = JSON.parse(msg);
|
|
588
|
+
if (
|
|
589
|
+
msg.type === BackendMessageType.MCPServerStatusChange ||
|
|
590
|
+
msg.type === BackendMessageType.ClaudeCodeStatusChange
|
|
591
|
+
) {
|
|
592
|
+
this.fetchCapabilities();
|
|
593
|
+
} else if (
|
|
594
|
+
msg.type === BackendMessageType.GitHubCopilotLoginStatusChange
|
|
595
|
+
) {
|
|
596
|
+
// The Copilot chat-model catalogue is fetched lazily once the bearer
|
|
597
|
+
// token is minted (issue #258), so the model dropdown depends on a
|
|
598
|
+
// capabilities refresh in addition to the login-status update.
|
|
599
|
+
Promise.all([
|
|
600
|
+
this.updateGitHubLoginStatus(),
|
|
601
|
+
this.fetchCapabilities()
|
|
602
|
+
]).then(() => {
|
|
603
|
+
this.githubLoginStatusChanged.emit();
|
|
604
|
+
});
|
|
605
|
+
} else if (msg.type === BackendMessageType.SkillsReloaded) {
|
|
606
|
+
this.skillsReloaded.emit();
|
|
607
|
+
} else if (msg.type === BackendMessageType.ClaudeCodeHeartbeat) {
|
|
608
|
+
this.claudeCodeHeartbeat.emit();
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
static async initializeWebsocket() {
|
|
614
|
+
const serverSettings = ServerConnection.makeSettings();
|
|
615
|
+
const wsUrl = URLExt.join(
|
|
616
|
+
serverSettings.wsUrl,
|
|
617
|
+
'notebook-intelligence',
|
|
618
|
+
'copilot'
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
this._webSocket = new serverSettings.WebSocket(wsUrl);
|
|
622
|
+
this._webSocket.onmessage = msg => {
|
|
623
|
+
this._messageReceived.emit(msg.data);
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
this._webSocket.onerror = msg => {
|
|
627
|
+
console.error(`Websocket error: ${msg}. Closing...`);
|
|
628
|
+
this._webSocket.close();
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
this._webSocket.onclose = msg => {
|
|
632
|
+
console.log(`Websocket is closed: ${msg.reason}. Reconnecting...`);
|
|
633
|
+
setTimeout(() => {
|
|
634
|
+
NBIAPI.initializeWebsocket();
|
|
635
|
+
}, 1000);
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
static getLoginStatus(): GitHubCopilotLoginStatus {
|
|
640
|
+
return this._loginStatus;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
static getDeviceVerificationInfo(): IDeviceVerificationInfo {
|
|
644
|
+
return this._deviceVerificationInfo;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
static getGHLoginRequired() {
|
|
648
|
+
return (
|
|
649
|
+
this.config.usingGitHubCopilotModel &&
|
|
650
|
+
NBIAPI.getLoginStatus() === GitHubCopilotLoginStatus.NotLoggedIn
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
static getChatEnabled() {
|
|
655
|
+
return (
|
|
656
|
+
this.config.isInClaudeCodeMode ||
|
|
657
|
+
(this.config.chatModel.provider === GITHUB_COPILOT_PROVIDER_ID
|
|
658
|
+
? !this.getGHLoginRequired()
|
|
659
|
+
: this.config.llmProviders.find(
|
|
660
|
+
provider => provider.id === this.config.chatModel.provider
|
|
661
|
+
))
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
static getInlineCompletionEnabled() {
|
|
666
|
+
return (
|
|
667
|
+
this.config.isInClaudeCodeMode ||
|
|
668
|
+
(this.config.inlineCompletionModel.provider === GITHUB_COPILOT_PROVIDER_ID
|
|
669
|
+
? !this.getGHLoginRequired()
|
|
670
|
+
: this.config.llmProviders.find(
|
|
671
|
+
provider =>
|
|
672
|
+
provider.id === this.config.inlineCompletionModel.provider
|
|
673
|
+
))
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
static async loginToGitHub() {
|
|
678
|
+
this._loginStatus = GitHubCopilotLoginStatus.ActivatingDevice;
|
|
679
|
+
return new Promise((resolve, reject) => {
|
|
680
|
+
requestAPI<any>('gh-login', { method: 'POST' })
|
|
681
|
+
.then(data => {
|
|
682
|
+
resolve({
|
|
683
|
+
verificationURI: data.verification_uri,
|
|
684
|
+
userCode: data.user_code
|
|
685
|
+
});
|
|
686
|
+
this.updateGitHubLoginStatus();
|
|
687
|
+
})
|
|
688
|
+
.catch(reason => {
|
|
689
|
+
console.error(`Failed to login to GitHub Copilot.\n${reason}`);
|
|
690
|
+
reject(reason);
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
static async logoutFromGitHub() {
|
|
696
|
+
this._loginStatus = GitHubCopilotLoginStatus.ActivatingDevice;
|
|
697
|
+
return new Promise((resolve, reject) => {
|
|
698
|
+
requestAPI<any>('gh-logout', { method: 'GET' })
|
|
699
|
+
.then(data => {
|
|
700
|
+
this.updateGitHubLoginStatus().then(() => {
|
|
701
|
+
resolve(data);
|
|
702
|
+
});
|
|
703
|
+
})
|
|
704
|
+
.catch(reason => {
|
|
705
|
+
console.error(`Failed to logout from GitHub Copilot.\n${reason}`);
|
|
706
|
+
reject(reason);
|
|
707
|
+
});
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
static async updateGitHubLoginStatus() {
|
|
712
|
+
return new Promise<void>((resolve, reject) => {
|
|
713
|
+
requestAPI<any>('gh-login-status')
|
|
714
|
+
.then(response => {
|
|
715
|
+
this._loginStatus = response.status;
|
|
716
|
+
this._deviceVerificationInfo.verificationURI =
|
|
717
|
+
response.verification_uri || '';
|
|
718
|
+
this._deviceVerificationInfo.userCode = response.user_code || '';
|
|
719
|
+
resolve();
|
|
720
|
+
})
|
|
721
|
+
.catch(reason => {
|
|
722
|
+
console.error(
|
|
723
|
+
`Failed to fetch GitHub Copilot login status.\n${reason}`
|
|
724
|
+
);
|
|
725
|
+
reject(reason);
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
static async fetchCapabilities(): Promise<void> {
|
|
731
|
+
return new Promise<void>((resolve, reject) => {
|
|
732
|
+
requestAPI<any>('capabilities', { method: 'GET' })
|
|
733
|
+
.then(data => {
|
|
734
|
+
const oldConfig = {
|
|
735
|
+
capabilities: structuredClone(this.config.capabilities),
|
|
736
|
+
chatParticipants: structuredClone(this.config.chatParticipants)
|
|
737
|
+
};
|
|
738
|
+
this.config.capabilities = structuredClone(data);
|
|
739
|
+
this.config.chatParticipants = structuredClone(
|
|
740
|
+
data.chat_participants
|
|
741
|
+
);
|
|
742
|
+
const newConfig = {
|
|
743
|
+
capabilities: structuredClone(this.config.capabilities),
|
|
744
|
+
chatParticipants: structuredClone(this.config.chatParticipants)
|
|
745
|
+
};
|
|
746
|
+
if (JSON.stringify(newConfig) !== JSON.stringify(oldConfig)) {
|
|
747
|
+
this.configChanged.emit();
|
|
748
|
+
}
|
|
749
|
+
resolve();
|
|
750
|
+
})
|
|
751
|
+
.catch(reason => {
|
|
752
|
+
console.error(`Failed to get extension capabilities.\n${reason}`);
|
|
753
|
+
reject(reason);
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
static async setConfig(config: any) {
|
|
759
|
+
requestAPI<any>('config', {
|
|
760
|
+
method: 'POST',
|
|
761
|
+
body: JSON.stringify(config)
|
|
762
|
+
})
|
|
763
|
+
.then(data => {
|
|
764
|
+
NBIAPI.fetchCapabilities();
|
|
765
|
+
})
|
|
766
|
+
.catch(reason => {
|
|
767
|
+
console.error(`Failed to set NBI config.\n${reason}`);
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
static async updateOllamaModelList(): Promise<void> {
|
|
772
|
+
return new Promise<void>((resolve, reject) => {
|
|
773
|
+
requestAPI<any>('update-provider-models', {
|
|
774
|
+
method: 'POST',
|
|
775
|
+
body: JSON.stringify({ provider: 'ollama' })
|
|
776
|
+
})
|
|
777
|
+
.then(async data => {
|
|
778
|
+
await NBIAPI.fetchCapabilities();
|
|
779
|
+
resolve();
|
|
780
|
+
})
|
|
781
|
+
.catch(reason => {
|
|
782
|
+
console.error(`Failed to update ollama model list.\n${reason}`);
|
|
783
|
+
reject(reason);
|
|
784
|
+
});
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
static async updateClaudeModelList(): Promise<void> {
|
|
789
|
+
return new Promise<void>((resolve, reject) => {
|
|
790
|
+
requestAPI<any>('update-provider-models', {
|
|
791
|
+
method: 'POST',
|
|
792
|
+
body: JSON.stringify({ provider: 'claude' })
|
|
793
|
+
})
|
|
794
|
+
.then(async data => {
|
|
795
|
+
await NBIAPI.fetchCapabilities();
|
|
796
|
+
resolve();
|
|
797
|
+
})
|
|
798
|
+
.catch(reason => {
|
|
799
|
+
console.error(`Failed to update Claude model list.\n${reason}`);
|
|
800
|
+
reject(reason);
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
static async getMCPConfigFile(): Promise<any> {
|
|
806
|
+
return new Promise<any>((resolve, reject) => {
|
|
807
|
+
requestAPI<any>('mcp-config-file', { method: 'GET' })
|
|
808
|
+
.then(async data => {
|
|
809
|
+
resolve(data);
|
|
810
|
+
})
|
|
811
|
+
.catch(reason => {
|
|
812
|
+
console.error(`Failed to get MCP config file.\n${reason}`);
|
|
813
|
+
reject(reason);
|
|
814
|
+
});
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
static async setMCPConfigFile(config: any): Promise<any> {
|
|
819
|
+
return new Promise<any>((resolve, reject) => {
|
|
820
|
+
requestAPI<any>('mcp-config-file', {
|
|
821
|
+
method: 'POST',
|
|
822
|
+
body: JSON.stringify(config)
|
|
823
|
+
})
|
|
824
|
+
.then(async data => {
|
|
825
|
+
resolve(data);
|
|
826
|
+
})
|
|
827
|
+
.catch(reason => {
|
|
828
|
+
console.error(`Failed to set MCP config file.\n${reason}`);
|
|
829
|
+
reject(reason);
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
static async listSkills(): Promise<ISkillSummary[]> {
|
|
835
|
+
const data = await requestAPI<any>('skills', { method: 'GET' });
|
|
836
|
+
return (data.skills ?? []).map(skillFromWire);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
static async getSkillsContext(): Promise<ISkillsContext> {
|
|
840
|
+
const data = await requestAPI<any>('skills/context', { method: 'GET' });
|
|
841
|
+
return {
|
|
842
|
+
projectRoot: data.project_root ?? '',
|
|
843
|
+
projectName: data.project_name ?? '',
|
|
844
|
+
userSkillsDir: data.user_skills_dir ?? '',
|
|
845
|
+
projectSkillsDir: data.project_skills_dir ?? ''
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
static async readSkill(
|
|
850
|
+
scope: SkillScope,
|
|
851
|
+
name: string
|
|
852
|
+
): Promise<ISkillDetail> {
|
|
853
|
+
const data = await requestAPI<any>(
|
|
854
|
+
`skills/${scope}/${encodeURIComponent(name)}`,
|
|
855
|
+
{ method: 'GET' }
|
|
856
|
+
);
|
|
857
|
+
return skillFromWire(data.skill);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
static async createSkill(payload: {
|
|
861
|
+
scope: SkillScope;
|
|
862
|
+
name: string;
|
|
863
|
+
description: string;
|
|
864
|
+
allowedTools: string[];
|
|
865
|
+
body: string;
|
|
866
|
+
}): Promise<ISkillDetail> {
|
|
867
|
+
const data = await requestAPI<any>('skills', {
|
|
868
|
+
method: 'POST',
|
|
869
|
+
body: JSON.stringify({
|
|
870
|
+
scope: payload.scope,
|
|
871
|
+
name: payload.name,
|
|
872
|
+
description: payload.description,
|
|
873
|
+
allowed_tools: payload.allowedTools,
|
|
874
|
+
body: payload.body
|
|
875
|
+
})
|
|
876
|
+
});
|
|
877
|
+
return skillFromWire(data.skill);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
static async updateSkill(
|
|
881
|
+
scope: SkillScope,
|
|
882
|
+
name: string,
|
|
883
|
+
payload: {
|
|
884
|
+
description?: string;
|
|
885
|
+
allowedTools?: string[];
|
|
886
|
+
body?: string;
|
|
887
|
+
tracksUpstream?: boolean;
|
|
888
|
+
}
|
|
889
|
+
): Promise<ISkillDetail> {
|
|
890
|
+
const wire: any = {};
|
|
891
|
+
if (payload.description !== undefined) {
|
|
892
|
+
wire.description = payload.description;
|
|
893
|
+
}
|
|
894
|
+
if (payload.allowedTools !== undefined) {
|
|
895
|
+
wire.allowed_tools = payload.allowedTools;
|
|
896
|
+
}
|
|
897
|
+
if (payload.body !== undefined) {
|
|
898
|
+
wire.body = payload.body;
|
|
899
|
+
}
|
|
900
|
+
if (payload.tracksUpstream !== undefined) {
|
|
901
|
+
wire.tracks_upstream = payload.tracksUpstream;
|
|
902
|
+
}
|
|
903
|
+
const data = await requestAPI<any>(
|
|
904
|
+
`skills/${scope}/${encodeURIComponent(name)}`,
|
|
905
|
+
{
|
|
906
|
+
method: 'PUT',
|
|
907
|
+
body: JSON.stringify(wire)
|
|
908
|
+
}
|
|
909
|
+
);
|
|
910
|
+
return skillFromWire(data.skill);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
static async deleteSkill(scope: SkillScope, name: string): Promise<void> {
|
|
914
|
+
await requestAPI<any>(`skills/${scope}/${encodeURIComponent(name)}`, {
|
|
915
|
+
method: 'DELETE'
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
static async previewSkillImport(url: string): Promise<ISkillImportPreview> {
|
|
920
|
+
const data = await requestAPI<any>('skills/import/preview', {
|
|
921
|
+
method: 'POST',
|
|
922
|
+
body: JSON.stringify({ url })
|
|
923
|
+
});
|
|
924
|
+
const p = data.preview;
|
|
925
|
+
return {
|
|
926
|
+
name: p.name,
|
|
927
|
+
description: p.description ?? '',
|
|
928
|
+
allowedTools: p.allowed_tools ?? [],
|
|
929
|
+
body: p.body ?? '',
|
|
930
|
+
files: p.files ?? [],
|
|
931
|
+
sourceUrl: p.source_url ?? '',
|
|
932
|
+
canonicalUrl: p.canonical_url ?? '',
|
|
933
|
+
existsInUserScope: p.exists_in_user_scope === true,
|
|
934
|
+
existsInProjectScope: p.exists_in_project_scope === true
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
static async importSkill(payload: {
|
|
939
|
+
url: string;
|
|
940
|
+
scope: SkillScope;
|
|
941
|
+
name?: string;
|
|
942
|
+
overwrite?: boolean;
|
|
943
|
+
tracksUpstream?: boolean;
|
|
944
|
+
}): Promise<ISkillDetail> {
|
|
945
|
+
const wire: any = { url: payload.url, scope: payload.scope };
|
|
946
|
+
if (payload.name) {
|
|
947
|
+
wire.name = payload.name;
|
|
948
|
+
}
|
|
949
|
+
if (payload.overwrite) {
|
|
950
|
+
wire.overwrite = true;
|
|
951
|
+
}
|
|
952
|
+
if (payload.tracksUpstream) {
|
|
953
|
+
wire.tracks_upstream = true;
|
|
954
|
+
}
|
|
955
|
+
const data = await requestAPI<any>('skills/import', {
|
|
956
|
+
method: 'POST',
|
|
957
|
+
body: JSON.stringify(wire)
|
|
958
|
+
});
|
|
959
|
+
return skillFromWire(data.skill);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
static async syncTrackingSkill(
|
|
963
|
+
scope: SkillScope,
|
|
964
|
+
name: string
|
|
965
|
+
): Promise<ISyncSkillResult> {
|
|
966
|
+
const data = await requestAPI<any>(
|
|
967
|
+
`skills/${scope}/${encodeURIComponent(name)}/sync`,
|
|
968
|
+
{ method: 'POST' }
|
|
969
|
+
);
|
|
970
|
+
return {
|
|
971
|
+
updated: Boolean(data.updated),
|
|
972
|
+
ref: data.ref ?? ''
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
static async syncAllTrackingSkills(): Promise<ISyncAllTrackingEntry[]> {
|
|
977
|
+
const data = await requestAPI<any>('skills/sync-all-tracking', {
|
|
978
|
+
method: 'POST'
|
|
979
|
+
});
|
|
980
|
+
if (!Array.isArray(data?.results)) {
|
|
981
|
+
return [];
|
|
982
|
+
}
|
|
983
|
+
return data.results.map((r: any) => ({
|
|
984
|
+
scope: r.scope,
|
|
985
|
+
name: r.name,
|
|
986
|
+
updated: typeof r.updated === 'boolean' ? r.updated : undefined,
|
|
987
|
+
ref: typeof r.ref === 'string' ? r.ref : undefined,
|
|
988
|
+
error: typeof r.error === 'string' ? r.error : undefined
|
|
989
|
+
}));
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
static async listClaudeMCPServers(): Promise<IClaudeMCPServer[]> {
|
|
993
|
+
const data = await requestAPI<any>('claude-mcp');
|
|
994
|
+
return Array.isArray(data?.servers)
|
|
995
|
+
? data.servers.map(claudeMCPServerFromWire)
|
|
996
|
+
: [];
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
static async addClaudeMCPServer(
|
|
1000
|
+
input: IClaudeMCPAddInput
|
|
1001
|
+
): Promise<IClaudeMCPServer> {
|
|
1002
|
+
const body: any = {
|
|
1003
|
+
name: input.name,
|
|
1004
|
+
scope: input.scope,
|
|
1005
|
+
transport: input.transport,
|
|
1006
|
+
command_or_url: input.commandOrUrl
|
|
1007
|
+
};
|
|
1008
|
+
if (input.args && input.args.length) {
|
|
1009
|
+
body.args = input.args;
|
|
1010
|
+
}
|
|
1011
|
+
if (input.env && Object.keys(input.env).length) {
|
|
1012
|
+
body.env = input.env;
|
|
1013
|
+
}
|
|
1014
|
+
if (input.headers && Object.keys(input.headers).length) {
|
|
1015
|
+
body.headers = input.headers;
|
|
1016
|
+
}
|
|
1017
|
+
const data = await requestAPI<any>('claude-mcp', {
|
|
1018
|
+
method: 'POST',
|
|
1019
|
+
body: JSON.stringify(body)
|
|
1020
|
+
});
|
|
1021
|
+
return claudeMCPServerFromWire(data.server);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
static async removeClaudeMCPServer(
|
|
1025
|
+
name: string,
|
|
1026
|
+
scope: ClaudeMCPScope
|
|
1027
|
+
): Promise<void> {
|
|
1028
|
+
await requestAPI<any>(`claude-mcp/${scope}/${encodeURIComponent(name)}`, {
|
|
1029
|
+
method: 'DELETE'
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
static async setClaudeMCPServerDisabled(
|
|
1034
|
+
name: string,
|
|
1035
|
+
scope: ClaudeMCPScope,
|
|
1036
|
+
disabled: boolean
|
|
1037
|
+
): Promise<IClaudeMCPServer> {
|
|
1038
|
+
const data = await requestAPI<any>(
|
|
1039
|
+
`claude-mcp/${scope}/${encodeURIComponent(name)}`,
|
|
1040
|
+
{
|
|
1041
|
+
method: 'PATCH',
|
|
1042
|
+
body: JSON.stringify({ disabled_for_workspace: disabled })
|
|
1043
|
+
}
|
|
1044
|
+
);
|
|
1045
|
+
return claudeMCPServerFromWire(data.server);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
static async listPlugins(): Promise<IPluginInfo[]> {
|
|
1049
|
+
const data = await requestAPI<any>('plugins');
|
|
1050
|
+
return Array.isArray(data?.plugins) ? (data.plugins as IPluginInfo[]) : [];
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
static async installPlugin(
|
|
1054
|
+
plugin: string,
|
|
1055
|
+
scope: PluginScope = 'user'
|
|
1056
|
+
): Promise<void> {
|
|
1057
|
+
await requestAPI<any>('plugins', {
|
|
1058
|
+
method: 'POST',
|
|
1059
|
+
body: JSON.stringify({ plugin, scope })
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
static async uninstallPlugin(
|
|
1064
|
+
plugin: string,
|
|
1065
|
+
scope: PluginScope = 'user'
|
|
1066
|
+
): Promise<void> {
|
|
1067
|
+
await requestAPI<any>(`plugins/${scope}/${encodeURIComponent(plugin)}`, {
|
|
1068
|
+
method: 'DELETE'
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
static async setPluginEnabled(
|
|
1073
|
+
plugin: string,
|
|
1074
|
+
scope: PluginScope,
|
|
1075
|
+
enabled: boolean
|
|
1076
|
+
): Promise<void> {
|
|
1077
|
+
await requestAPI<any>(`plugins/${scope}/${encodeURIComponent(plugin)}`, {
|
|
1078
|
+
method: 'POST',
|
|
1079
|
+
body: JSON.stringify({ action: enabled ? 'enable' : 'disable' })
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
static async listPluginMarketplaces(): Promise<IPluginMarketplaceInfo[]> {
|
|
1084
|
+
const data = await requestAPI<any>('plugins/marketplace');
|
|
1085
|
+
return Array.isArray(data?.marketplaces)
|
|
1086
|
+
? (data.marketplaces as IPluginMarketplaceInfo[])
|
|
1087
|
+
: [];
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
static async listPluginMarketplacePlugins(
|
|
1091
|
+
marketplace: string
|
|
1092
|
+
): Promise<IPluginMarketplacePluginInfo[]> {
|
|
1093
|
+
const data = await requestAPI<any>(
|
|
1094
|
+
`plugins/marketplace/${encodeURIComponent(marketplace)}/plugins`
|
|
1095
|
+
);
|
|
1096
|
+
return Array.isArray(data?.plugins)
|
|
1097
|
+
? (data.plugins as IPluginMarketplacePluginInfo[])
|
|
1098
|
+
: [];
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
static async addPluginMarketplace(
|
|
1102
|
+
source: string,
|
|
1103
|
+
scope: PluginScope = 'user'
|
|
1104
|
+
): Promise<void> {
|
|
1105
|
+
await requestAPI<any>('plugins/marketplace', {
|
|
1106
|
+
method: 'POST',
|
|
1107
|
+
body: JSON.stringify({ source, scope })
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
static async removePluginMarketplace(name: string): Promise<void> {
|
|
1112
|
+
await requestAPI<any>(`plugins/marketplace/${encodeURIComponent(name)}`, {
|
|
1113
|
+
method: 'DELETE'
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
static async updatePluginMarketplace(name: string): Promise<void> {
|
|
1118
|
+
await requestAPI<any>(
|
|
1119
|
+
`plugins/marketplace/${encodeURIComponent(name)}/update`,
|
|
1120
|
+
{
|
|
1121
|
+
method: 'POST',
|
|
1122
|
+
body: '{}'
|
|
1123
|
+
}
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
static async reconcileManagedSkills(): Promise<IReconcileResult> {
|
|
1128
|
+
const data = await requestAPI<any>('skills/reconcile', {
|
|
1129
|
+
method: 'POST'
|
|
1130
|
+
});
|
|
1131
|
+
return {
|
|
1132
|
+
added: Number(data.added ?? 0),
|
|
1133
|
+
updated: Number(data.updated ?? 0),
|
|
1134
|
+
removed: Number(data.removed ?? 0),
|
|
1135
|
+
unchanged: Number(data.unchanged ?? 0),
|
|
1136
|
+
errors: Array.isArray(data.errors) ? data.errors.map(String) : []
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
static async renameSkill(
|
|
1141
|
+
scope: SkillScope,
|
|
1142
|
+
name: string,
|
|
1143
|
+
newName: string
|
|
1144
|
+
): Promise<ISkillDetail> {
|
|
1145
|
+
const data = await requestAPI<any>(
|
|
1146
|
+
`skills/${scope}/${encodeURIComponent(name)}/rename`,
|
|
1147
|
+
{
|
|
1148
|
+
method: 'POST',
|
|
1149
|
+
body: JSON.stringify({ new_name: newName })
|
|
1150
|
+
}
|
|
1151
|
+
);
|
|
1152
|
+
return skillFromWire(data.skill);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
static async readBundleFile(
|
|
1156
|
+
scope: SkillScope,
|
|
1157
|
+
name: string,
|
|
1158
|
+
path: string
|
|
1159
|
+
): Promise<string> {
|
|
1160
|
+
const data = await requestAPI<any>(
|
|
1161
|
+
`skills/${scope}/${encodeURIComponent(name)}/files?path=${encodeURIComponent(path)}`,
|
|
1162
|
+
{ method: 'GET' }
|
|
1163
|
+
);
|
|
1164
|
+
return data.content;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
static async writeBundleFile(
|
|
1168
|
+
scope: SkillScope,
|
|
1169
|
+
name: string,
|
|
1170
|
+
path: string,
|
|
1171
|
+
content: string
|
|
1172
|
+
): Promise<void> {
|
|
1173
|
+
await requestAPI<any>(
|
|
1174
|
+
`skills/${scope}/${encodeURIComponent(name)}/files?path=${encodeURIComponent(path)}`,
|
|
1175
|
+
{
|
|
1176
|
+
method: 'PUT',
|
|
1177
|
+
body: JSON.stringify({ content })
|
|
1178
|
+
}
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
static async deleteBundleFile(
|
|
1183
|
+
scope: SkillScope,
|
|
1184
|
+
name: string,
|
|
1185
|
+
path: string
|
|
1186
|
+
): Promise<void> {
|
|
1187
|
+
await requestAPI<any>(
|
|
1188
|
+
`skills/${scope}/${encodeURIComponent(name)}/files?path=${encodeURIComponent(path)}`,
|
|
1189
|
+
{ method: 'DELETE' }
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
static async renameBundleFile(
|
|
1194
|
+
scope: SkillScope,
|
|
1195
|
+
name: string,
|
|
1196
|
+
from: string,
|
|
1197
|
+
to: string
|
|
1198
|
+
): Promise<void> {
|
|
1199
|
+
await requestAPI<any>(
|
|
1200
|
+
`skills/${scope}/${encodeURIComponent(name)}/files/rename`,
|
|
1201
|
+
{
|
|
1202
|
+
method: 'POST',
|
|
1203
|
+
body: JSON.stringify({ from, to })
|
|
1204
|
+
}
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* Subscribe to inbound websocket messages for a single request, forwarding
|
|
1210
|
+
* them to `responseEmitter`. The subscription auto-disconnects when the
|
|
1211
|
+
* server emits StreamEnd, preventing per-request listener accumulation.
|
|
1212
|
+
*/
|
|
1213
|
+
private static _subscribeUntilStreamEnd(
|
|
1214
|
+
messageId: string,
|
|
1215
|
+
responseEmitter: IChatCompletionResponseEmitter
|
|
1216
|
+
): void {
|
|
1217
|
+
const handler = (_: unknown, msg: any) => {
|
|
1218
|
+
const parsed = JSON.parse(msg);
|
|
1219
|
+
if (parsed.id !== messageId) {
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
responseEmitter.emit(parsed);
|
|
1223
|
+
if (parsed.type === BackendMessageType.StreamEnd) {
|
|
1224
|
+
this._messageReceived.disconnect(handler);
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1227
|
+
this._messageReceived.connect(handler);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
static async chatRequest(
|
|
1231
|
+
messageId: string,
|
|
1232
|
+
chatId: string,
|
|
1233
|
+
prompt: string,
|
|
1234
|
+
language: string,
|
|
1235
|
+
currentDirectory: string,
|
|
1236
|
+
filename: string,
|
|
1237
|
+
additionalContext: IContextItem[],
|
|
1238
|
+
chatMode: string,
|
|
1239
|
+
toolSelections: IToolSelections,
|
|
1240
|
+
responseEmitter: IChatCompletionResponseEmitter
|
|
1241
|
+
) {
|
|
1242
|
+
this._subscribeUntilStreamEnd(messageId, responseEmitter);
|
|
1243
|
+
this._webSocket.send(
|
|
1244
|
+
JSON.stringify({
|
|
1245
|
+
id: messageId,
|
|
1246
|
+
type: RequestDataType.ChatRequest,
|
|
1247
|
+
data: {
|
|
1248
|
+
chatId,
|
|
1249
|
+
prompt,
|
|
1250
|
+
language,
|
|
1251
|
+
currentDirectory,
|
|
1252
|
+
filename,
|
|
1253
|
+
additionalContext,
|
|
1254
|
+
chatMode,
|
|
1255
|
+
toolSelections
|
|
1256
|
+
}
|
|
1257
|
+
})
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
static async reloadMCPServers(): Promise<any> {
|
|
1262
|
+
return new Promise<any>((resolve, reject) => {
|
|
1263
|
+
requestAPI<any>('reload-mcp-servers', { method: 'POST' })
|
|
1264
|
+
.then(async data => {
|
|
1265
|
+
await NBIAPI.fetchCapabilities();
|
|
1266
|
+
resolve(data);
|
|
1267
|
+
})
|
|
1268
|
+
.catch(reason => {
|
|
1269
|
+
console.error(`Failed to reload MCP servers.\n${reason}`);
|
|
1270
|
+
reject(reason);
|
|
1271
|
+
});
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
static async generateCode(
|
|
1276
|
+
messageId: string,
|
|
1277
|
+
chatId: string,
|
|
1278
|
+
prompt: string,
|
|
1279
|
+
prefix: string,
|
|
1280
|
+
suffix: string,
|
|
1281
|
+
existingCode: string,
|
|
1282
|
+
language: string,
|
|
1283
|
+
filename: string,
|
|
1284
|
+
responseEmitter: IChatCompletionResponseEmitter
|
|
1285
|
+
) {
|
|
1286
|
+
this._subscribeUntilStreamEnd(messageId, responseEmitter);
|
|
1287
|
+
this._webSocket.send(
|
|
1288
|
+
JSON.stringify({
|
|
1289
|
+
id: messageId,
|
|
1290
|
+
type: RequestDataType.GenerateCode,
|
|
1291
|
+
data: {
|
|
1292
|
+
chatId,
|
|
1293
|
+
prompt,
|
|
1294
|
+
prefix,
|
|
1295
|
+
suffix,
|
|
1296
|
+
existingCode,
|
|
1297
|
+
language,
|
|
1298
|
+
filename
|
|
1299
|
+
}
|
|
1300
|
+
})
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
static async sendChatUserInput(messageId: string, data: any) {
|
|
1305
|
+
this._webSocket.send(
|
|
1306
|
+
JSON.stringify({
|
|
1307
|
+
id: messageId,
|
|
1308
|
+
type: RequestDataType.ChatUserInput,
|
|
1309
|
+
data
|
|
1310
|
+
})
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
static async sendWebSocketMessage(
|
|
1315
|
+
messageId: string,
|
|
1316
|
+
messageType: RequestDataType,
|
|
1317
|
+
data: any
|
|
1318
|
+
) {
|
|
1319
|
+
this._webSocket.send(
|
|
1320
|
+
JSON.stringify({ id: messageId, type: messageType, data })
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
static async inlineCompletionsRequest(
|
|
1325
|
+
chatId: string,
|
|
1326
|
+
messageId: string,
|
|
1327
|
+
prefix: string,
|
|
1328
|
+
suffix: string,
|
|
1329
|
+
language: string,
|
|
1330
|
+
filename: string,
|
|
1331
|
+
responseEmitter: IChatCompletionResponseEmitter
|
|
1332
|
+
) {
|
|
1333
|
+
this._subscribeUntilStreamEnd(messageId, responseEmitter);
|
|
1334
|
+
this._webSocket.send(
|
|
1335
|
+
JSON.stringify({
|
|
1336
|
+
id: messageId,
|
|
1337
|
+
type: RequestDataType.InlineCompletionRequest,
|
|
1338
|
+
data: {
|
|
1339
|
+
chatId,
|
|
1340
|
+
prefix,
|
|
1341
|
+
suffix,
|
|
1342
|
+
language,
|
|
1343
|
+
filename
|
|
1344
|
+
}
|
|
1345
|
+
})
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
static async uploadFile(
|
|
1350
|
+
file: File
|
|
1351
|
+
): Promise<{ serverPath: string; filename: string }> {
|
|
1352
|
+
const formData = new FormData();
|
|
1353
|
+
formData.append('file', file, file.name);
|
|
1354
|
+
return requestAPI<{ serverPath: string; filename: string }>('upload-file', {
|
|
1355
|
+
method: 'POST',
|
|
1356
|
+
body: formData
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
static async listClaudeSessions(
|
|
1361
|
+
scope: ClaudeSessionScope = 'all'
|
|
1362
|
+
): Promise<IClaudeSessionList> {
|
|
1363
|
+
interface IWireResponse {
|
|
1364
|
+
sessions?: IClaudeSessionInfo[];
|
|
1365
|
+
current_cwd?: string;
|
|
1366
|
+
}
|
|
1367
|
+
return new Promise<IClaudeSessionList>((resolve, reject) => {
|
|
1368
|
+
requestAPI<IWireResponse>(`claude-sessions?scope=${scope}`, {
|
|
1369
|
+
method: 'GET'
|
|
1370
|
+
})
|
|
1371
|
+
.then(data => {
|
|
1372
|
+
resolve({
|
|
1373
|
+
sessions: data.sessions ?? [],
|
|
1374
|
+
currentCwd: data.current_cwd ?? ''
|
|
1375
|
+
});
|
|
1376
|
+
})
|
|
1377
|
+
.catch(reason => {
|
|
1378
|
+
console.error(`Failed to list Claude sessions.\n${reason}`);
|
|
1379
|
+
reject(reason);
|
|
1380
|
+
});
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
static async resumeClaudeSession(sessionId: string): Promise<void> {
|
|
1385
|
+
return new Promise<void>((resolve, reject) => {
|
|
1386
|
+
requestAPI<any>('claude-sessions/resume', {
|
|
1387
|
+
method: 'POST',
|
|
1388
|
+
body: JSON.stringify({ session_id: sessionId })
|
|
1389
|
+
})
|
|
1390
|
+
.then(() => {
|
|
1391
|
+
resolve();
|
|
1392
|
+
})
|
|
1393
|
+
.catch(reason => {
|
|
1394
|
+
console.error(`Failed to resume Claude session.\n${reason}`);
|
|
1395
|
+
reject(reason);
|
|
1396
|
+
});
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
static async emitTelemetryEvent(event: ITelemetryEvent): Promise<void> {
|
|
1401
|
+
const assistantMode = this.config.isInClaudeCodeMode
|
|
1402
|
+
? AssistantMode.Claude
|
|
1403
|
+
: AssistantMode.Default;
|
|
1404
|
+
|
|
1405
|
+
event.data = {
|
|
1406
|
+
...(event.data || {}),
|
|
1407
|
+
assistantMode
|
|
1408
|
+
};
|
|
1409
|
+
|
|
1410
|
+
return new Promise<void>((resolve, reject) => {
|
|
1411
|
+
requestAPI<any>('emit-telemetry-event', {
|
|
1412
|
+
method: 'POST',
|
|
1413
|
+
body: JSON.stringify(event)
|
|
1414
|
+
})
|
|
1415
|
+
.then(async data => {
|
|
1416
|
+
resolve();
|
|
1417
|
+
})
|
|
1418
|
+
.catch(reason => {
|
|
1419
|
+
console.error(`Failed to emit telemetry event.\n${reason}`);
|
|
1420
|
+
reject(reason);
|
|
1421
|
+
});
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
}
|