@jupyterlite/ai 0.8.0 → 0.9.0-a0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/lib/agent.d.ts +233 -0
  2. package/lib/agent.js +604 -0
  3. package/lib/chat-model.d.ts +195 -0
  4. package/lib/chat-model.js +590 -0
  5. package/lib/completion/completion-provider.d.ts +83 -0
  6. package/lib/completion/completion-provider.js +209 -0
  7. package/lib/completion/index.d.ts +1 -0
  8. package/lib/completion/index.js +1 -0
  9. package/lib/components/clear-button.d.ts +18 -0
  10. package/lib/components/clear-button.js +31 -0
  11. package/lib/components/index.d.ts +3 -0
  12. package/lib/components/index.js +3 -0
  13. package/lib/components/model-select.d.ts +19 -0
  14. package/lib/components/model-select.js +154 -0
  15. package/lib/components/stop-button.d.ts +3 -3
  16. package/lib/components/stop-button.js +8 -9
  17. package/lib/components/token-usage-display.d.ts +45 -0
  18. package/lib/components/token-usage-display.js +74 -0
  19. package/lib/components/tool-select.d.ts +27 -0
  20. package/lib/components/tool-select.js +130 -0
  21. package/lib/icons.d.ts +3 -1
  22. package/lib/icons.js +10 -13
  23. package/lib/index.d.ts +4 -5
  24. package/lib/index.js +322 -167
  25. package/lib/mcp/browser.d.ts +68 -0
  26. package/lib/mcp/browser.js +132 -0
  27. package/lib/models/settings-model.d.ts +69 -0
  28. package/lib/models/settings-model.js +295 -0
  29. package/lib/providers/built-in-providers.d.ts +9 -0
  30. package/lib/providers/built-in-providers.js +192 -0
  31. package/lib/providers/models.d.ts +37 -0
  32. package/lib/providers/models.js +28 -0
  33. package/lib/providers/provider-registry.d.ts +94 -0
  34. package/lib/providers/provider-registry.js +155 -0
  35. package/lib/tokens.d.ts +157 -86
  36. package/lib/tokens.js +16 -12
  37. package/lib/tools/commands.d.ts +11 -0
  38. package/lib/tools/commands.js +126 -0
  39. package/lib/tools/file.d.ts +27 -0
  40. package/lib/tools/file.js +262 -0
  41. package/lib/tools/notebook.d.ts +40 -0
  42. package/lib/tools/notebook.js +762 -0
  43. package/lib/tools/tool-registry.d.ts +35 -0
  44. package/lib/tools/tool-registry.js +55 -0
  45. package/lib/widgets/ai-settings.d.ts +39 -0
  46. package/lib/widgets/ai-settings.js +506 -0
  47. package/lib/widgets/chat-wrapper.d.ts +144 -0
  48. package/lib/widgets/chat-wrapper.js +390 -0
  49. package/lib/widgets/provider-config-dialog.d.ts +13 -0
  50. package/lib/widgets/provider-config-dialog.js +104 -0
  51. package/package.json +150 -41
  52. package/schema/settings-model.json +153 -0
  53. package/src/agent.ts +800 -0
  54. package/src/chat-model.ts +770 -0
  55. package/src/completion/completion-provider.ts +308 -0
  56. package/src/completion/index.ts +1 -0
  57. package/src/components/clear-button.tsx +56 -0
  58. package/src/components/index.ts +3 -0
  59. package/src/components/model-select.tsx +245 -0
  60. package/src/components/stop-button.tsx +11 -11
  61. package/src/components/token-usage-display.tsx +130 -0
  62. package/src/components/tool-select.tsx +218 -0
  63. package/src/icons.ts +12 -14
  64. package/src/index.ts +468 -238
  65. package/src/mcp/browser.ts +213 -0
  66. package/src/models/settings-model.ts +409 -0
  67. package/src/providers/built-in-providers.ts +216 -0
  68. package/src/providers/models.ts +79 -0
  69. package/src/providers/provider-registry.ts +189 -0
  70. package/src/tokens.ts +203 -90
  71. package/src/tools/commands.ts +151 -0
  72. package/src/tools/file.ts +307 -0
  73. package/src/tools/notebook.ts +964 -0
  74. package/src/tools/tool-registry.ts +63 -0
  75. package/src/types.d.ts +4 -0
  76. package/src/widgets/ai-settings.tsx +1100 -0
  77. package/src/widgets/chat-wrapper.tsx +543 -0
  78. package/src/widgets/provider-config-dialog.tsx +256 -0
  79. package/style/base.css +335 -14
  80. package/style/icons/jupyternaut-lite.svg +1 -1
  81. package/lib/base-completer.d.ts +0 -49
  82. package/lib/base-completer.js +0 -14
  83. package/lib/chat-handler.d.ts +0 -56
  84. package/lib/chat-handler.js +0 -201
  85. package/lib/completion-provider.d.ts +0 -34
  86. package/lib/completion-provider.js +0 -32
  87. package/lib/default-prompts.d.ts +0 -2
  88. package/lib/default-prompts.js +0 -31
  89. package/lib/default-providers/Anthropic/completer.d.ts +0 -12
  90. package/lib/default-providers/Anthropic/completer.js +0 -46
  91. package/lib/default-providers/Anthropic/settings-schema.json +0 -70
  92. package/lib/default-providers/ChromeAI/completer.d.ts +0 -12
  93. package/lib/default-providers/ChromeAI/completer.js +0 -56
  94. package/lib/default-providers/ChromeAI/instructions.d.ts +0 -6
  95. package/lib/default-providers/ChromeAI/instructions.js +0 -42
  96. package/lib/default-providers/ChromeAI/settings-schema.json +0 -18
  97. package/lib/default-providers/Gemini/completer.d.ts +0 -12
  98. package/lib/default-providers/Gemini/completer.js +0 -48
  99. package/lib/default-providers/Gemini/instructions.d.ts +0 -2
  100. package/lib/default-providers/Gemini/instructions.js +0 -9
  101. package/lib/default-providers/Gemini/settings-schema.json +0 -64
  102. package/lib/default-providers/MistralAI/completer.d.ts +0 -13
  103. package/lib/default-providers/MistralAI/completer.js +0 -52
  104. package/lib/default-providers/MistralAI/instructions.d.ts +0 -2
  105. package/lib/default-providers/MistralAI/instructions.js +0 -18
  106. package/lib/default-providers/MistralAI/settings-schema.json +0 -75
  107. package/lib/default-providers/Ollama/completer.d.ts +0 -12
  108. package/lib/default-providers/Ollama/completer.js +0 -43
  109. package/lib/default-providers/Ollama/instructions.d.ts +0 -2
  110. package/lib/default-providers/Ollama/instructions.js +0 -70
  111. package/lib/default-providers/Ollama/settings-schema.json +0 -143
  112. package/lib/default-providers/OpenAI/completer.d.ts +0 -12
  113. package/lib/default-providers/OpenAI/completer.js +0 -43
  114. package/lib/default-providers/OpenAI/settings-schema.json +0 -628
  115. package/lib/default-providers/WebLLM/completer.d.ts +0 -21
  116. package/lib/default-providers/WebLLM/completer.js +0 -127
  117. package/lib/default-providers/WebLLM/instructions.d.ts +0 -6
  118. package/lib/default-providers/WebLLM/instructions.js +0 -32
  119. package/lib/default-providers/WebLLM/settings-schema.json +0 -19
  120. package/lib/default-providers/index.d.ts +0 -2
  121. package/lib/default-providers/index.js +0 -179
  122. package/lib/provider.d.ts +0 -144
  123. package/lib/provider.js +0 -412
  124. package/lib/settings/base.json +0 -7
  125. package/lib/settings/index.d.ts +0 -3
  126. package/lib/settings/index.js +0 -3
  127. package/lib/settings/panel.d.ts +0 -226
  128. package/lib/settings/panel.js +0 -510
  129. package/lib/settings/textarea.d.ts +0 -2
  130. package/lib/settings/textarea.js +0 -18
  131. package/lib/settings/utils.d.ts +0 -2
  132. package/lib/settings/utils.js +0 -4
  133. package/lib/types/ai-model.d.ts +0 -24
  134. package/lib/types/ai-model.js +0 -5
  135. package/schema/chat.json +0 -28
  136. package/schema/provider-registry.json +0 -29
  137. package/schema/system-prompts.json +0 -22
  138. package/src/base-completer.ts +0 -75
  139. package/src/chat-handler.ts +0 -262
  140. package/src/completion-provider.ts +0 -64
  141. package/src/default-prompts.ts +0 -33
  142. package/src/default-providers/Anthropic/completer.ts +0 -59
  143. package/src/default-providers/ChromeAI/completer.ts +0 -73
  144. package/src/default-providers/ChromeAI/instructions.ts +0 -45
  145. package/src/default-providers/Gemini/completer.ts +0 -61
  146. package/src/default-providers/Gemini/instructions.ts +0 -9
  147. package/src/default-providers/MistralAI/completer.ts +0 -69
  148. package/src/default-providers/MistralAI/instructions.ts +0 -18
  149. package/src/default-providers/Ollama/completer.ts +0 -54
  150. package/src/default-providers/Ollama/instructions.ts +0 -70
  151. package/src/default-providers/OpenAI/completer.ts +0 -54
  152. package/src/default-providers/WebLLM/completer.ts +0 -151
  153. package/src/default-providers/WebLLM/instructions.ts +0 -33
  154. package/src/default-providers/index.ts +0 -211
  155. package/src/global.d.ts +0 -9
  156. package/src/provider.ts +0 -514
  157. package/src/settings/index.ts +0 -3
  158. package/src/settings/panel.tsx +0 -773
  159. package/src/settings/textarea.tsx +0 -33
  160. package/src/settings/utils.ts +0 -5
  161. package/src/types/ai-model.ts +0 -37
  162. package/src/types/service-worker.d.ts +0 -6
package/src/tokens.ts CHANGED
@@ -1,158 +1,271 @@
1
- import { BaseChatModel } from '@langchain/core/language_models/chat_models';
2
- import { ReadonlyPartialJSONObject, Token } from '@lumino/coreutils';
1
+ import { Token } from '@lumino/coreutils';
3
2
  import { ISignal } from '@lumino/signaling';
4
- import { JSONSchema7 } from 'json-schema';
3
+ import { FunctionTool } from '@openai/agents';
4
+ import { LanguageModel } from 'ai';
5
+ import type { AISettingsModel } from './models/settings-model';
6
+ import type { IModelOptions } from './providers/models';
5
7
 
6
- import { IBaseCompleter } from './base-completer';
7
- import { AIChatModel, AICompleter } from './types/ai-model';
8
+ /**
9
+ * Type definition for a tool
10
+ */
11
+ export type ITool = FunctionTool<any, any, any>;
8
12
 
9
- export const PLUGIN_IDS = {
10
- chat: '@jupyterlite/ai:chat',
11
- chatCommandRegistry: '@jupyterlite/ai:autocompletion-registry',
12
- completer: '@jupyterlite/ai:completer',
13
- providerRegistry: '@jupyterlite/ai:provider-registry',
14
- settingsConnector: '@jupyterlite/ai:settings-connector',
15
- systemPrompts: '@jupyterlite/ai:system-prompts'
16
- };
13
+ /**
14
+ * Interface for token usage statistics from AI model interactions
15
+ */
16
+ export interface ITokenUsage {
17
+ /**
18
+ * Number of input tokens consumed (prompt tokens)
19
+ */
20
+ inputTokens: number;
17
21
 
18
- export type ModelRole = 'chat' | 'completer';
22
+ /**
23
+ * Number of output tokens generated (completion tokens)
24
+ */
25
+ outputTokens: number;
26
+ }
19
27
 
20
- export interface IDict<T = any> {
21
- [key: string]: T;
28
+ /**
29
+ * Interface for a named tool (tool with a name identifier)
30
+ */
31
+ export interface INamedTool {
32
+ /**
33
+ * The unique name of the tool
34
+ */
35
+ name: string;
36
+ /**
37
+ * The tool instance
38
+ */
39
+ tool: ITool;
22
40
  }
23
41
 
24
- export interface IType<T> {
25
- new (...args: any[]): T;
42
+ /**
43
+ * The tool registry interface for managing AI tools
44
+ */
45
+ export interface IToolRegistry {
46
+ /**
47
+ * The registered tools as a record (name -> tool mapping).
48
+ */
49
+ readonly tools: Record<string, ITool>;
50
+
51
+ /**
52
+ * The registered named tools array.
53
+ */
54
+ readonly namedTools: INamedTool[];
55
+
56
+ /**
57
+ * A signal triggered when the tools have changed.
58
+ */
59
+ readonly toolsChanged: ISignal<IToolRegistry, void>;
60
+
61
+ /**
62
+ * Add a new tool to the registry.
63
+ */
64
+ add(name: string, tool: ITool): void;
65
+
66
+ /**
67
+ * Get a tool for a given name.
68
+ * Return null if the name is not provided or if there is no registered tool with the
69
+ * given name.
70
+ */
71
+ get(name: string | null): ITool | null;
72
+
73
+ /**
74
+ * Remove a tool from the registry by name.
75
+ */
76
+ remove(name: string): boolean;
26
77
  }
27
78
 
28
79
  /**
29
- * The provider interface.
80
+ * The tool registry token.
81
+ */
82
+ export const IToolRegistry = new Token<IToolRegistry>(
83
+ '@jupyterlite/ai:tool-registry',
84
+ 'Tool registry for AI agent functionality'
85
+ );
86
+
87
+ /**
88
+ * Token for the chat provider registry.
30
89
  */
31
- export interface IAIProvider {
90
+ export const IChatProviderRegistry = new Token<IChatProviderRegistry>(
91
+ '@jupyterlite/ai:chat-provider-registry',
92
+ 'Registry for chat AI providers'
93
+ );
94
+
95
+ /**
96
+ * Token for the completion provider registry.
97
+ */
98
+ export const ICompletionProviderRegistry =
99
+ new Token<ICompletionProviderRegistry>(
100
+ '@jupyterlite/ai:completion-provider-registry',
101
+ 'Registry for completion providers'
102
+ );
103
+
104
+ /**
105
+ * Interface for a provider factory function that creates chat models
106
+ */
107
+ export interface IChatProviderFactory {
108
+ (options: IModelOptions): any; // Returns the model instance for @openai/agents
109
+ }
110
+
111
+ /**
112
+ * Interface for a provider factory function that creates completion models
113
+ */
114
+ export interface ICompletionProviderFactory {
115
+ (options: IModelOptions): LanguageModel;
116
+ }
117
+
118
+ /**
119
+ * Base information about a registered provider
120
+ */
121
+ export interface IBaseProviderInfo {
32
122
  /**
33
- * The name of the provider.
123
+ * Unique identifier for the provider
34
124
  */
35
- name: string;
125
+ id: string;
126
+
36
127
  /**
37
- * The chat model class to use.
128
+ * Display name for the provider
38
129
  */
39
- chat?: IType<BaseChatModel>;
130
+ name: string;
131
+
40
132
  /**
41
- * The completer class to use.
133
+ * Whether this provider requires an API key
42
134
  */
43
- completer?: IType<IBaseCompleter>;
135
+ requiresApiKey: boolean;
136
+
44
137
  /**
45
- * the settings schema for the provider.
138
+ * Default model names for this provider
46
139
  */
47
- settingsSchema?: any;
140
+ defaultModels: string[];
141
+
48
142
  /**
49
- * The instructions to be displayed in the settings, as helper to use the provider.
50
- * A markdown renderer is used to render the instructions.
143
+ * Whether this provider supports custom base URLs
51
144
  */
52
- instructions?: string;
145
+ supportsBaseURL?: boolean;
146
+
53
147
  /**
54
- * A function that extract the error message from the provider API error.
55
- * Default to `(error) => error.message`.
148
+ * Whether this provider supports custom headers
56
149
  */
57
- errorMessage?: (error: any) => string;
150
+ supportsHeaders?: boolean;
151
+
58
152
  /**
59
- * Compatibility check function, to determine if the provider is compatible with the
60
- * current environment.
153
+ * Whether this provider supports tool calling
61
154
  */
62
- compatibilityCheck?: () => Promise<string | null>;
155
+ supportsToolCalling?: boolean;
156
+
63
157
  /**
64
- * Whether to expose or not the chat model.
65
- *
66
- * ### CAUTION
67
- * This flag will expose the whole chat model API, which may contain private keys.
68
- * Be sure to use it with a model that does not expose sensitive information in the
69
- * API.
158
+ * Additional provider-specific configuration schema
70
159
  */
71
- exposeChatModel?: boolean;
160
+ customSettings?: Record<string, any>;
72
161
  }
73
162
 
74
163
  /**
75
- * The provider registry interface.
164
+ * Information about a chat provider
76
165
  */
77
- export interface IAIProviderRegistry {
166
+ export interface IChatProviderInfo extends IBaseProviderInfo {
78
167
  /**
79
- * Get the list of provider names.
168
+ * Factory function for creating chat models
80
169
  */
81
- readonly providers: string[];
170
+ factory: IChatProviderFactory;
171
+ }
172
+
173
+ /**
174
+ * Information about a completion provider
175
+ */
176
+ export interface ICompletionProviderInfo extends IBaseProviderInfo {
82
177
  /**
83
- * Add a new provider.
178
+ * Factory function for creating completion models
84
179
  */
85
- add(provider: IAIProvider): void;
180
+ factory: ICompletionProviderFactory;
181
+ }
182
+
183
+ /**
184
+ * Registry for chat AI providers
185
+ */
186
+ export interface IChatProviderRegistry {
86
187
  /**
87
- * Get the current provider name.
188
+ * The registered providers as a record (id -> info mapping).
88
189
  */
89
- currentName(role: ModelRole): string;
190
+ readonly providers: Record<string, IChatProviderInfo>;
191
+
90
192
  /**
91
- * Get the current completer of the completion provider.
193
+ * A signal triggered when providers have changed.
92
194
  */
93
- currentCompleter: AICompleter | null;
195
+ readonly providersChanged: ISignal<IChatProviderRegistry, void>;
196
+
94
197
  /**
95
- * Getter/setter for the completer system prompt.
198
+ * Register a new chat provider.
96
199
  */
97
- completerSystemPrompt: string;
200
+ registerProvider(info: IChatProviderInfo): void;
201
+
98
202
  /**
99
- * Get the current llm chat model.
203
+ * Unregister a chat provider.
100
204
  */
101
- currentChatModel: AIChatModel | null;
205
+ unregisterProvider(id: string): boolean;
206
+
102
207
  /**
103
- * Getter/setter for the chat system prompt.
208
+ * Get provider info by id.
104
209
  */
105
- chatSystemPrompt: string;
210
+ getProviderInfo(id: string): IChatProviderInfo | null;
211
+
106
212
  /**
107
- * Get the settings schema of a given provider.
213
+ * Create a chat model instance for the given provider.
108
214
  */
109
- getSettingsSchema(provider: string): JSONSchema7;
215
+ createChatModel(id: string, options: IModelOptions): any | null;
216
+
110
217
  /**
111
- * Get the instructions of a given provider.
218
+ * Get all available provider IDs.
112
219
  */
113
- getInstructions(provider: string): string | undefined;
220
+ getAvailableProviders(): string[];
221
+ }
222
+
223
+ /**
224
+ * Registry for completion providers
225
+ */
226
+ export interface ICompletionProviderRegistry {
114
227
  /**
115
- * Get the compatibility check function of a given provider.
228
+ * The registered providers as a record (id -> info mapping).
116
229
  */
117
- getCompatibilityCheck(
118
- provider: string
119
- ): (() => Promise<string | null>) | undefined;
230
+ readonly providers: Record<string, ICompletionProviderInfo>;
231
+
120
232
  /**
121
- * Format an error message from the current provider.
233
+ * A signal triggered when providers have changed.
122
234
  */
123
- formatErrorMessage(error: any): string;
235
+ readonly providersChanged: ISignal<ICompletionProviderRegistry, void>;
236
+
124
237
  /**
125
- * Set the completer provider.
126
- * Creates the provider if the name has changed, otherwise only updates its config.
127
- *
128
- * @param options - An object with the name and the settings of the provider to use.
238
+ * Register a new completion provider.
129
239
  */
130
- setCompleterProvider(settings: ReadonlyPartialJSONObject): void;
240
+ registerProvider(info: ICompletionProviderInfo): void;
241
+
131
242
  /**
132
- * Set the chat provider.
133
- * Creates the provider if the name has changed, otherwise only updates its config.
134
- *
135
- * @param options - An object with the name and the settings of the provider to use.
243
+ * Unregister a completion provider.
136
244
  */
137
- setChatProvider(settings: ReadonlyPartialJSONObject): void;
245
+ unregisterProvider(id: string): boolean;
246
+
138
247
  /**
139
- * A signal emitting when the provider or its settings has changed.
248
+ * Get provider info by id.
140
249
  */
141
- readonly providerChanged: ISignal<IAIProviderRegistry, ModelRole>;
250
+ getProviderInfo(id: string): ICompletionProviderInfo | null;
251
+
142
252
  /**
143
- * Get the current chat error;
253
+ * Create a completion model instance for the given provider.
144
254
  */
145
- readonly chatError: string;
255
+ createCompletionModel(
256
+ id: string,
257
+ options: IModelOptions
258
+ ): LanguageModel | null;
259
+
146
260
  /**
147
- * get the current completer error.
261
+ * Get all available provider IDs.
148
262
  */
149
- readonly completerError: string;
263
+ getAvailableProviders(): string[];
150
264
  }
151
265
 
152
266
  /**
153
- * The provider registry token.
267
+ * Token for the AI settings model.
154
268
  */
155
- export const IAIProviderRegistry = new Token<IAIProviderRegistry>(
156
- '@jupyterlite/ai:provider-registry',
157
- 'Provider for chat and completion LLM provider'
269
+ export const IAISettingsModel = new Token<AISettingsModel>(
270
+ '@jupyterlite/ai:IAISettingsModel'
158
271
  );
@@ -0,0 +1,151 @@
1
+ import { CommandRegistry } from '@lumino/commands';
2
+ import { tool } from '@openai/agents';
3
+ import { z } from 'zod';
4
+ import { ITool } from '../tokens';
5
+ import { AISettingsModel } from '../models/settings-model';
6
+
7
+ /**
8
+ * Create a tool to discover all available commands and their metadata
9
+ */
10
+ export function createDiscoverCommandsTool(commands: CommandRegistry): ITool {
11
+ return tool({
12
+ name: 'discover_commands',
13
+ description:
14
+ 'Discover all available JupyterLab commands with their metadata, arguments, and descriptions',
15
+ parameters: z.object({
16
+ // currently unused, but could be used to filter commands by a search term
17
+ query: z
18
+ .string()
19
+ .optional()
20
+ .nullable()
21
+ .describe('Optional search query to filter commands')
22
+ }),
23
+ execute: async () => {
24
+ try {
25
+ const commandList: Array<{
26
+ id: string;
27
+ label?: string;
28
+ caption?: string;
29
+ description?: string;
30
+ args?: any;
31
+ }> = [];
32
+
33
+ // Get all command IDs
34
+ const commandIds = commands.listCommands();
35
+
36
+ for (const id of commandIds) {
37
+ try {
38
+ // Get command metadata using various CommandRegistry methods
39
+ const description = await commands.describedBy(id);
40
+ const label = commands.label(id);
41
+ const caption = commands.caption(id);
42
+ const usage = commands.usage(id);
43
+
44
+ commandList.push({
45
+ id,
46
+ label: label || undefined,
47
+ caption: caption || undefined,
48
+ description: usage || undefined,
49
+ args: description?.args || undefined
50
+ });
51
+ } catch (error) {
52
+ // Some commands might not have descriptions, skip them
53
+ commandList.push({ id });
54
+ }
55
+ }
56
+
57
+ return {
58
+ success: true,
59
+ commandCount: commandList.length,
60
+ commands: commandList
61
+ };
62
+ } catch (error) {
63
+ return {
64
+ success: false,
65
+ error: `Failed to discover commands: ${error instanceof Error ? error.message : String(error)}`
66
+ };
67
+ }
68
+ }
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Create a tool to execute a specific JupyterLab command
74
+ */
75
+ export function createExecuteCommandTool(
76
+ commands: CommandRegistry,
77
+ settingsModel: AISettingsModel
78
+ ): ITool {
79
+ return tool({
80
+ name: 'execute_command',
81
+ description:
82
+ 'Execute a specific JupyterLab command with optional arguments',
83
+ parameters: z.object({
84
+ commandId: z.string().describe('The ID of the command to execute'),
85
+ args: z
86
+ .any()
87
+ .optional()
88
+ .describe('Optional arguments to pass to the command')
89
+ }),
90
+ needsApproval: async (_context, { commandId }) => {
91
+ // Use configurable list of commands requiring approval
92
+ const commandsRequiringApproval =
93
+ settingsModel.config.commandsRequiringApproval;
94
+
95
+ return commandsRequiringApproval.some(
96
+ cmd => commandId.includes(cmd) || cmd.includes(commandId)
97
+ );
98
+ },
99
+ execute: async (input: { commandId: string; args?: any }) => {
100
+ const { commandId, args } = input;
101
+
102
+ // Check if command exists
103
+ if (!commands.hasCommand(commandId)) {
104
+ return {
105
+ success: false,
106
+ error: `Command '${commandId}' does not exist. Use 'discover_commands' to see available commands.`
107
+ };
108
+ }
109
+
110
+ try {
111
+ // Execute the command
112
+ const result = await commands.execute(commandId, args);
113
+
114
+ // Handle Widget objects specially (including subclasses like DocumentWidget)
115
+ let serializedResult;
116
+ if (
117
+ result &&
118
+ typeof result === 'object' &&
119
+ (result.constructor?.name?.includes('Widget') || result.id)
120
+ ) {
121
+ serializedResult = {
122
+ type: result.constructor?.name || 'Widget',
123
+ id: result.id,
124
+ title: result.title?.label || result.title,
125
+ className: result.className
126
+ };
127
+ } else {
128
+ // For other objects, try JSON serialization with fallback
129
+ try {
130
+ serializedResult = JSON.parse(JSON.stringify(result));
131
+ } catch {
132
+ serializedResult = result
133
+ ? '[Complex object - cannot serialize]'
134
+ : 'Command executed successfully';
135
+ }
136
+ }
137
+
138
+ return {
139
+ success: true,
140
+ commandId,
141
+ result: serializedResult
142
+ };
143
+ } catch (error) {
144
+ return {
145
+ success: false,
146
+ error: `Failed to execute command '${commandId}': ${error instanceof Error ? error.message : String(error)}`
147
+ };
148
+ }
149
+ }
150
+ });
151
+ }