@theia/ai-terminal 1.46.0-next.241

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 ADDED
@@ -0,0 +1,31 @@
1
+ <div align='center'>
2
+
3
+ <br />
4
+
5
+ <img src='https://raw.githubusercontent.com/eclipse-theia/theia/master/logo/theia.svg?sanitize=true' alt='theia-ext-logo' width='100px' />
6
+
7
+ <h2>ECLIPSE THEIA - AI Terminal EXTENSION</h2>
8
+
9
+ <hr />
10
+
11
+ </div>
12
+
13
+ ## Description
14
+
15
+ The `@theia/ai-terminal` extension contributes an overlay to the terminal view.\
16
+ The overlay can be used to ask a dedicated `TerminalAgent` for suggestions of terminal commands.
17
+
18
+ ## Additional Information
19
+
20
+ - [Theia - GitHub](https://github.com/eclipse-theia/theia)
21
+ - [Theia - Website](https://theia-ide.org/)
22
+
23
+ ## License
24
+
25
+ - [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/)
26
+ - [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)
27
+
28
+ ## Trademark
29
+
30
+ "Theia" is a trademark of the Eclipse Foundation
31
+ <https://www.eclipse.org/theia>
@@ -0,0 +1,27 @@
1
+ import { Agent, CommunicationRecordingService, LanguageModelRegistry, LanguageModelRequirement, PromptService } from '@theia/ai-core/lib/common';
2
+ import { ILogger } from '@theia/core';
3
+ export declare class AiTerminalAgent implements Agent {
4
+ protected recordingService: CommunicationRecordingService;
5
+ id: string;
6
+ name: string;
7
+ description: string;
8
+ variables: never[];
9
+ functions: never[];
10
+ agentSpecificVariables: {
11
+ name: string;
12
+ usedInPrompt: boolean;
13
+ description: string;
14
+ }[];
15
+ promptTemplates: {
16
+ id: string;
17
+ name: string;
18
+ description: string;
19
+ template: string;
20
+ }[];
21
+ languageModelRequirements: LanguageModelRequirement[];
22
+ protected languageModelRegistry: LanguageModelRegistry;
23
+ protected promptService: PromptService;
24
+ protected logger: ILogger;
25
+ getCommands(userRequest: string, cwd: string, shell: string, recentTerminalContents: string[]): Promise<string[]>;
26
+ }
27
+ //# sourceMappingURL=ai-terminal-agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-terminal-agent.d.ts","sourceRoot":"","sources":["../../src/browser/ai-terminal-agent.ts"],"names":[],"mappings":"AAgBA,OAAO,EACH,KAAK,EACL,6BAA6B,EAG7B,qBAAqB,EAAE,wBAAwB,EAC/C,aAAa,EAChB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAgB,OAAO,EAAE,MAAM,aAAa,CAAC;AAUpD,qBACa,eAAgB,YAAW,KAAK;IAEzC,SAAS,CAAC,gBAAgB,EAAE,6BAA6B,CAAC;IAE1D,EAAE,SAAwB;IAC1B,IAAI,SAAwB;IAC5B,WAAW,SAEoI;IAC/I,SAAS,UAAM;IACf,SAAS,UAAM;IACf,sBAAsB;;;;QAKpB;IACF,eAAe;;;;;QA8Db;IACF,yBAAyB,EAAE,wBAAwB,EAAE,CAKnD;IAGF,SAAS,CAAC,qBAAqB,EAAE,qBAAqB,CAAC;IAGvD,SAAS,CAAC,aAAa,EAAE,aAAa,CAAC;IAGvC,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC;IAEpB,WAAW,CACb,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,sBAAsB,EAAE,MAAM,EAAE,GACjC,OAAO,CAAC,MAAM,EAAE,CAAC;CAsFvB"}
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ // *****************************************************************************
3
+ // Copyright (C) 2024 EclipseSource GmbH.
4
+ //
5
+ // This program and the accompanying materials are made available under the
6
+ // terms of the Eclipse Public License v. 2.0 which is available at
7
+ // http://www.eclipse.org/legal/epl-2.0.
8
+ //
9
+ // This Source Code may also be made available under the following Secondary
10
+ // Licenses when the conditions for such availability set forth in the Eclipse
11
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
+ // with the GNU Classpath Exception which is available at
13
+ // https://www.gnu.org/software/classpath/license.html.
14
+ //
15
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16
+ // *****************************************************************************
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.AiTerminalAgent = void 0;
19
+ const tslib_1 = require("tslib");
20
+ const common_1 = require("@theia/ai-core/lib/common");
21
+ const core_1 = require("@theia/core");
22
+ const inversify_1 = require("@theia/core/shared/inversify");
23
+ const zod_1 = require("zod");
24
+ const zod_to_json_schema_1 = require("zod-to-json-schema");
25
+ const Commands = zod_1.z.object({
26
+ commands: zod_1.z.array(zod_1.z.string()),
27
+ });
28
+ let AiTerminalAgent = class AiTerminalAgent {
29
+ constructor() {
30
+ this.id = 'Terminal Assistant';
31
+ this.name = 'Terminal Assistant';
32
+ this.description = 'This agent provides assistance to write and execute arbitrary terminal commands. \
33
+ Based on the user\'s request, it suggests commands and allows the user to directly paste and execute them in the terminal. \
34
+ It accesses the current directory, environment and the recent terminal output of the terminal session to provide context-aware assistance';
35
+ this.variables = [];
36
+ this.functions = [];
37
+ this.agentSpecificVariables = [
38
+ { name: 'userRequest', usedInPrompt: true, description: 'The user\'s question or request.' },
39
+ { name: 'shell', usedInPrompt: true, description: 'The shell being used, e.g., /usr/bin/zsh.' },
40
+ { name: 'cwd', usedInPrompt: true, description: 'The current working directory.' },
41
+ { name: 'recentTerminalContents', usedInPrompt: true, description: 'The last 0 to 50 recent lines visible in the terminal.' }
42
+ ];
43
+ this.promptTemplates = [
44
+ {
45
+ id: 'terminal-system',
46
+ name: 'AI Terminal System Prompt',
47
+ description: 'Prompt for the AI Terminal Assistant',
48
+ template: `{{!-- Made improvements or adaptations to this prompt template? We’d love for you to share it with the community! Contribute back here:
49
+ https://github.com/eclipse-theia/theia/discussions/new?category=prompt-template-contribution --}}
50
+ # Instructions
51
+ Generate one or more command suggestions based on the user's request, considering the shell being used,
52
+ the current working directory, and the recent terminal contents. Provide the best suggestion first,
53
+ followed by other relevant suggestions if the user asks for further options.
54
+
55
+ Parameters:
56
+ - user-request: The user's question or request.
57
+ - shell: The shell being used, e.g., /usr/bin/zsh.
58
+ - cwd: The current working directory.
59
+ - recent-terminal-contents: The last 0 to 50 recent lines visible in the terminal.
60
+
61
+ Return the result in the following JSON format:
62
+ {
63
+ "commands": [
64
+ "best_command_suggestion",
65
+ "next_best_command_suggestion",
66
+ "another_command_suggestion"
67
+ ]
68
+ }
69
+
70
+ ## Example
71
+ user-request: "How do I commit changes?"
72
+ shell: "/usr/bin/zsh"
73
+ cwd: "/home/user/project"
74
+ recent-terminal-contents:
75
+ git status
76
+ On branch main
77
+ Your branch is up to date with 'origin/main'.
78
+ nothing to commit, working tree clean
79
+
80
+ ## Expected JSON output
81
+ \`\`\`json
82
+ \{
83
+ "commands": [
84
+ "git commit",
85
+ "git commit --amend",
86
+ "git commit -a"
87
+ ]
88
+ }
89
+ \`\`\`
90
+ `
91
+ },
92
+ {
93
+ id: 'terminal-user',
94
+ name: 'AI Terminal User Prompt',
95
+ description: 'Prompt that contains the user request',
96
+ template: `{{!-- Made improvements or adaptations to this prompt template? We’d love for you to share it with the community! Contribute back here:
97
+ https://github.com/eclipse-theia/theia/discussions/new?category=prompt-template-contribution --}}
98
+ user-request: {{userRequest}}
99
+ shell: {{shell}}
100
+ cwd: {{cwd}}
101
+ recent-terminal-contents:
102
+ {{recentTerminalContents}}
103
+ `
104
+ }
105
+ ];
106
+ this.languageModelRequirements = [
107
+ {
108
+ purpose: 'suggest-terminal-commands',
109
+ identifier: 'openai/gpt-4o',
110
+ }
111
+ ];
112
+ }
113
+ async getCommands(userRequest, cwd, shell, recentTerminalContents) {
114
+ const lm = await this.languageModelRegistry.selectLanguageModel({
115
+ agent: this.id,
116
+ ...this.languageModelRequirements[0]
117
+ });
118
+ if (!lm) {
119
+ this.logger.error('No language model available for the AI Terminal Agent.');
120
+ return [];
121
+ }
122
+ const parameters = {
123
+ userRequest,
124
+ shell,
125
+ cwd,
126
+ recentTerminalContents
127
+ };
128
+ const systemMessage = await this.promptService.getPrompt('terminal-system', parameters).then(p => p === null || p === void 0 ? void 0 : p.text);
129
+ const request = await this.promptService.getPrompt('terminal-user', parameters).then(p => p === null || p === void 0 ? void 0 : p.text);
130
+ if (!systemMessage || !request) {
131
+ this.logger.error('The prompt service didn\'t return prompts for the AI Terminal Agent.');
132
+ return [];
133
+ }
134
+ // since we do not actually hold complete conversions, the request/response pair is considered a session
135
+ const sessionId = (0, core_1.generateUuid)();
136
+ const requestId = (0, core_1.generateUuid)();
137
+ this.recordingService.recordRequest({
138
+ agentId: this.id,
139
+ sessionId,
140
+ requestId,
141
+ request,
142
+ systemMessage
143
+ });
144
+ try {
145
+ const result = await lm.request({
146
+ messages: [
147
+ {
148
+ actor: 'ai',
149
+ type: 'text',
150
+ query: systemMessage
151
+ },
152
+ {
153
+ actor: 'user',
154
+ type: 'text',
155
+ query: request
156
+ }
157
+ ],
158
+ response_format: {
159
+ type: 'json_schema',
160
+ json_schema: {
161
+ name: 'terminal-commands',
162
+ description: 'Suggested terminal commands based on the user request',
163
+ schema: (0, zod_to_json_schema_1.default)(Commands)
164
+ }
165
+ }
166
+ });
167
+ if ((0, common_1.isLanguageModelParsedResponse)(result)) {
168
+ // model returned structured output
169
+ const parsedResult = Commands.safeParse(result.parsed);
170
+ if (parsedResult.success) {
171
+ const response = JSON.stringify(parsedResult.data.commands);
172
+ this.recordingService.recordResponse({ agentId: this.id, sessionId, requestId, response, systemMessage });
173
+ return parsedResult.data.commands;
174
+ }
175
+ }
176
+ // fall back to agent-based parsing of result
177
+ const jsonResult = await (0, common_1.getJsonOfResponse)(result);
178
+ const responseTextFromJSON = JSON.stringify(jsonResult);
179
+ this.recordingService.recordResponse({ agentId: this.id, sessionId, requestId, response: responseTextFromJSON });
180
+ const parsedJsonResult = Commands.safeParse(jsonResult);
181
+ if (parsedJsonResult.success) {
182
+ return parsedJsonResult.data.commands;
183
+ }
184
+ return [];
185
+ }
186
+ catch (error) {
187
+ this.logger.error('Error obtaining the command suggestions.', error);
188
+ return [];
189
+ }
190
+ }
191
+ };
192
+ exports.AiTerminalAgent = AiTerminalAgent;
193
+ tslib_1.__decorate([
194
+ (0, inversify_1.inject)(common_1.CommunicationRecordingService),
195
+ tslib_1.__metadata("design:type", Object)
196
+ ], AiTerminalAgent.prototype, "recordingService", void 0);
197
+ tslib_1.__decorate([
198
+ (0, inversify_1.inject)(common_1.LanguageModelRegistry),
199
+ tslib_1.__metadata("design:type", Object)
200
+ ], AiTerminalAgent.prototype, "languageModelRegistry", void 0);
201
+ tslib_1.__decorate([
202
+ (0, inversify_1.inject)(common_1.PromptService),
203
+ tslib_1.__metadata("design:type", Object)
204
+ ], AiTerminalAgent.prototype, "promptService", void 0);
205
+ tslib_1.__decorate([
206
+ (0, inversify_1.inject)(core_1.ILogger),
207
+ tslib_1.__metadata("design:type", Object)
208
+ ], AiTerminalAgent.prototype, "logger", void 0);
209
+ exports.AiTerminalAgent = AiTerminalAgent = tslib_1.__decorate([
210
+ (0, inversify_1.injectable)()
211
+ ], AiTerminalAgent);
212
+ //# sourceMappingURL=ai-terminal-agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-terminal-agent.js","sourceRoot":"","sources":["../../src/browser/ai-terminal-agent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { CommandContribution, CommandRegistry, MenuContribution, MenuModelRegistry } from '@theia/core';
2
+ import { KeybindingContribution, KeybindingRegistry } from '@theia/core/lib/browser';
3
+ import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service';
4
+ import { AiTerminalAgent } from './ai-terminal-agent';
5
+ import { AICommandHandlerFactory } from '@theia/ai-core/lib/browser/ai-command-handler-factory';
6
+ export declare class AiTerminalCommandContribution implements CommandContribution, MenuContribution, KeybindingContribution {
7
+ protected terminalService: TerminalService;
8
+ protected terminalAgent: AiTerminalAgent;
9
+ protected commandHandlerFactory: AICommandHandlerFactory;
10
+ private readonly agentService;
11
+ registerKeybindings(keybindings: KeybindingRegistry): void;
12
+ registerMenus(menus: MenuModelRegistry): void;
13
+ registerCommands(commands: CommandRegistry): void;
14
+ }
15
+ //# sourceMappingURL=ai-terminal-contribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-terminal-contribution.d.ts","sourceRoot":"","sources":["../../src/browser/ai-terminal-contribution.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACxG,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAErF,OAAO,EAAE,eAAe,EAAE,MAAM,mDAAmD,CAAC;AAGpF,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uDAAuD,CAAC;AAQhG,qBACa,6BAA8B,YAAW,mBAAmB,EAAE,gBAAgB,EAAE,sBAAsB;IAG/G,SAAS,CAAC,eAAe,EAAE,eAAe,CAAC;IAG3C,SAAS,CAAC,aAAa,EAAE,eAAe,CAAC;IAGzC,SAAS,CAAC,qBAAqB,EAAE,uBAAuB,CAAC;IAGzD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAE5C,mBAAmB,CAAC,WAAW,EAAE,kBAAkB,GAAG,IAAI;IAO1D,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;IAM7C,gBAAgB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;CAYpD"}
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ // *****************************************************************************
3
+ // Copyright (C) 2024 EclipseSource GmbH.
4
+ //
5
+ // This program and the accompanying materials are made available under the
6
+ // terms of the Eclipse Public License v. 2.0 which is available at
7
+ // http://www.eclipse.org/legal/epl-2.0.
8
+ //
9
+ // This Source Code may also be made available under the following Secondary
10
+ // Licenses when the conditions for such availability set forth in the Eclipse
11
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
+ // with the GNU Classpath Exception which is available at
13
+ // https://www.gnu.org/software/classpath/license.html.
14
+ //
15
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16
+ // *****************************************************************************
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.AiTerminalCommandContribution = void 0;
19
+ const tslib_1 = require("tslib");
20
+ const browser_1 = require("@theia/ai-core/lib/browser");
21
+ const inversify_1 = require("@theia/core/shared/inversify");
22
+ const terminal_service_1 = require("@theia/terminal/lib/browser/base/terminal-service");
23
+ const terminal_frontend_contribution_1 = require("@theia/terminal/lib/browser/terminal-frontend-contribution");
24
+ const terminal_widget_impl_1 = require("@theia/terminal/lib/browser/terminal-widget-impl");
25
+ const ai_terminal_agent_1 = require("./ai-terminal-agent");
26
+ const ai_command_handler_factory_1 = require("@theia/ai-core/lib/browser/ai-command-handler-factory");
27
+ const ai_core_1 = require("@theia/ai-core");
28
+ const AI_TERMINAL_COMMAND = {
29
+ id: 'ai-terminal:open',
30
+ label: 'Ask the AI'
31
+ };
32
+ let AiTerminalCommandContribution = class AiTerminalCommandContribution {
33
+ registerKeybindings(keybindings) {
34
+ keybindings.registerKeybinding({
35
+ command: AI_TERMINAL_COMMAND.id,
36
+ keybinding: 'ctrlcmd+i',
37
+ when: `terminalFocus && ${browser_1.EXPERIMENTAL_AI_CONTEXT_KEY}`
38
+ });
39
+ }
40
+ registerMenus(menus) {
41
+ menus.registerMenuAction([...terminal_frontend_contribution_1.TerminalMenus.TERMINAL_CONTEXT_MENU, '_5'], {
42
+ when: browser_1.EXPERIMENTAL_AI_CONTEXT_KEY,
43
+ commandId: AI_TERMINAL_COMMAND.id
44
+ });
45
+ }
46
+ registerCommands(commands) {
47
+ commands.registerCommand(AI_TERMINAL_COMMAND, this.commandHandlerFactory({
48
+ execute: () => {
49
+ if (this.terminalService.currentTerminal instanceof terminal_widget_impl_1.TerminalWidgetImpl && this.agentService.isEnabled(this.terminalAgent.id)) {
50
+ new AiTerminalChatWidget(this.terminalService.currentTerminal, this.terminalAgent);
51
+ }
52
+ }
53
+ }));
54
+ }
55
+ };
56
+ exports.AiTerminalCommandContribution = AiTerminalCommandContribution;
57
+ tslib_1.__decorate([
58
+ (0, inversify_1.inject)(terminal_service_1.TerminalService),
59
+ tslib_1.__metadata("design:type", Object)
60
+ ], AiTerminalCommandContribution.prototype, "terminalService", void 0);
61
+ tslib_1.__decorate([
62
+ (0, inversify_1.inject)(ai_terminal_agent_1.AiTerminalAgent),
63
+ tslib_1.__metadata("design:type", ai_terminal_agent_1.AiTerminalAgent)
64
+ ], AiTerminalCommandContribution.prototype, "terminalAgent", void 0);
65
+ tslib_1.__decorate([
66
+ (0, inversify_1.inject)(ai_command_handler_factory_1.AICommandHandlerFactory),
67
+ tslib_1.__metadata("design:type", Function)
68
+ ], AiTerminalCommandContribution.prototype, "commandHandlerFactory", void 0);
69
+ tslib_1.__decorate([
70
+ (0, inversify_1.inject)(ai_core_1.AgentService),
71
+ tslib_1.__metadata("design:type", Object)
72
+ ], AiTerminalCommandContribution.prototype, "agentService", void 0);
73
+ exports.AiTerminalCommandContribution = AiTerminalCommandContribution = tslib_1.__decorate([
74
+ (0, inversify_1.injectable)()
75
+ ], AiTerminalCommandContribution);
76
+ class AiTerminalChatWidget {
77
+ constructor(terminalWidget, terminalAgent) {
78
+ this.terminalWidget = terminalWidget;
79
+ this.terminalAgent = terminalAgent;
80
+ this.haveResult = false;
81
+ this.chatContainer = document.createElement('div');
82
+ this.chatContainer.className = 'ai-terminal-chat-container';
83
+ const chatCloseButton = document.createElement('span');
84
+ chatCloseButton.className = 'closeButton codicon codicon-close';
85
+ chatCloseButton.onclick = () => this.dispose();
86
+ this.chatContainer.appendChild(chatCloseButton);
87
+ const chatResultContainer = document.createElement('div');
88
+ chatResultContainer.className = 'ai-terminal-chat-result';
89
+ this.chatResultParagraph = document.createElement('p');
90
+ this.chatResultParagraph.textContent = 'How can I help you?';
91
+ chatResultContainer.appendChild(this.chatResultParagraph);
92
+ this.chatContainer.appendChild(chatResultContainer);
93
+ this.chatInputContainer = document.createElement('div');
94
+ this.chatInputContainer.className = 'ai-terminal-chat-input-container';
95
+ this.chatInput = document.createElement('textarea');
96
+ this.chatInput.className = 'theia-input theia-ChatInput';
97
+ this.chatInput.placeholder = 'Ask about a terminal command...';
98
+ this.chatInput.onkeydown = event => {
99
+ if (event.key === 'Enter' && !event.shiftKey) {
100
+ event.preventDefault();
101
+ if (!this.haveResult) {
102
+ this.send();
103
+ }
104
+ else {
105
+ this.terminalWidget.sendText(this.chatResultParagraph.innerText);
106
+ this.dispose();
107
+ }
108
+ }
109
+ else if (event.key === 'Escape') {
110
+ this.dispose();
111
+ }
112
+ else if (event.key === 'ArrowUp' && this.haveResult) {
113
+ this.updateChatResult(this.getNextCommandIndex(1));
114
+ }
115
+ else if (event.key === 'ArrowDown' && this.haveResult) {
116
+ this.updateChatResult(this.getNextCommandIndex(-1));
117
+ }
118
+ };
119
+ this.chatInputContainer.appendChild(this.chatInput);
120
+ const chatInputOptionsContainer = document.createElement('div');
121
+ const chatInputOptionsSpan = document.createElement('span');
122
+ chatInputOptionsSpan.className = 'codicon codicon-send option';
123
+ chatInputOptionsSpan.title = 'Send';
124
+ chatInputOptionsSpan.onclick = () => this.send();
125
+ chatInputOptionsContainer.appendChild(chatInputOptionsSpan);
126
+ this.chatInputContainer.appendChild(chatInputOptionsContainer);
127
+ this.chatContainer.appendChild(this.chatInputContainer);
128
+ terminalWidget.node.appendChild(this.chatContainer);
129
+ this.chatInput.focus();
130
+ }
131
+ async send() {
132
+ const userRequest = this.chatInput.value;
133
+ if (userRequest) {
134
+ this.chatInput.value = '';
135
+ this.chatResultParagraph.innerText = 'Loading';
136
+ this.chatResultParagraph.className = 'loading';
137
+ const cwd = (await this.terminalWidget.cwd).toString();
138
+ const processInfo = await this.terminalWidget.processInfo;
139
+ const shell = processInfo.executable;
140
+ const recentTerminalContents = this.getRecentTerminalCommands();
141
+ this.commands = await this.terminalAgent.getCommands(userRequest, cwd, shell, recentTerminalContents);
142
+ if (this.commands.length > 0) {
143
+ this.chatResultParagraph.className = 'command';
144
+ this.chatResultParagraph.innerText = this.commands[0];
145
+ this.chatInput.placeholder = 'Hit enter to confirm';
146
+ if (this.commands.length > 1) {
147
+ this.chatInput.placeholder += ' or use ⇅ to show alternatives...';
148
+ }
149
+ this.haveResult = true;
150
+ }
151
+ else {
152
+ this.chatResultParagraph.className = '';
153
+ this.chatResultParagraph.innerText = 'No results';
154
+ this.chatInput.placeholder = 'Try again...';
155
+ }
156
+ }
157
+ }
158
+ getRecentTerminalCommands() {
159
+ const maxLines = 100;
160
+ return this.terminalWidget.buffer.getLines(0, this.terminalWidget.buffer.length > maxLines ? maxLines : this.terminalWidget.buffer.length);
161
+ }
162
+ getNextCommandIndex(step) {
163
+ const currentIndex = this.commands.indexOf(this.chatResultParagraph.innerText);
164
+ const nextIndex = (currentIndex + step + this.commands.length) % this.commands.length;
165
+ return nextIndex;
166
+ }
167
+ updateChatResult(index) {
168
+ this.chatResultParagraph.innerText = this.commands[index];
169
+ }
170
+ dispose() {
171
+ this.chatInput.value = '';
172
+ this.terminalWidget.node.removeChild(this.chatContainer);
173
+ this.terminalWidget.getTerminal().focus();
174
+ }
175
+ }
176
+ //# sourceMappingURL=ai-terminal-contribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-terminal-contribution.js","sourceRoot":"","sources":["../../src/browser/ai-terminal-contribution.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { ContainerModule } from '@theia/core/shared/inversify';
2
+ import '../../src/browser/style/ai-terminal.css';
3
+ declare const _default: ContainerModule;
4
+ export default _default;
5
+ //# sourceMappingURL=ai-terminal-frontend-module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-terminal-frontend-module.d.ts","sourceRoot":"","sources":["../../src/browser/ai-terminal-frontend-module.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAI/D,OAAO,yCAAyC,CAAC;;AAEjD,wBAQG"}
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ // *****************************************************************************
3
+ // Copyright (C) 2024 EclipseSource GmbH.
4
+ //
5
+ // This program and the accompanying materials are made available under the
6
+ // terms of the Eclipse Public License v. 2.0 which is available at
7
+ // http://www.eclipse.org/legal/epl-2.0.
8
+ //
9
+ // This Source Code may also be made available under the following Secondary
10
+ // Licenses when the conditions for such availability set forth in the Eclipse
11
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
+ // with the GNU Classpath Exception which is available at
13
+ // https://www.gnu.org/software/classpath/license.html.
14
+ //
15
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16
+ // *****************************************************************************
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ const common_1 = require("@theia/ai-core/lib/common");
19
+ const core_1 = require("@theia/core");
20
+ const browser_1 = require("@theia/core/lib/browser");
21
+ const inversify_1 = require("@theia/core/shared/inversify");
22
+ const ai_terminal_agent_1 = require("./ai-terminal-agent");
23
+ const ai_terminal_contribution_1 = require("./ai-terminal-contribution");
24
+ require("../../src/browser/style/ai-terminal.css");
25
+ exports.default = new inversify_1.ContainerModule(bind => {
26
+ bind(ai_terminal_contribution_1.AiTerminalCommandContribution).toSelf().inSingletonScope();
27
+ for (const identifier of [core_1.CommandContribution, core_1.MenuContribution, browser_1.KeybindingContribution]) {
28
+ bind(identifier).toService(ai_terminal_contribution_1.AiTerminalCommandContribution);
29
+ }
30
+ bind(ai_terminal_agent_1.AiTerminalAgent).toSelf().inSingletonScope();
31
+ bind(common_1.Agent).toService(ai_terminal_agent_1.AiTerminalAgent);
32
+ });
33
+ //# sourceMappingURL=ai-terminal-frontend-module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-terminal-frontend-module.js","sourceRoot":"","sources":["../../src/browser/ai-terminal-frontend-module.ts"],"names":[],"mappings":";AAAA,gFAAgF;AAChF,yCAAyC;AACzC,EAAE;AACF,2EAA2E;AAC3E,mEAAmE;AACnE,wCAAwC;AACxC,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,yDAAyD;AACzD,uDAAuD;AACvD,EAAE;AACF,gFAAgF;AAChF,gFAAgF;;AAEhF,sDAAkD;AAClD,sCAAoE;AACpE,qDAAiE;AACjE,4DAA+D;AAC/D,2DAAsD;AACtD,yEAA2E;AAE3E,mDAAiD;AAEjD,kBAAe,IAAI,2BAAe,CAAC,IAAI,CAAC,EAAE;IACtC,IAAI,CAAC,wDAA6B,CAAC,CAAC,MAAM,EAAE,CAAC,gBAAgB,EAAE,CAAC;IAChE,KAAK,MAAM,UAAU,IAAI,CAAC,0BAAmB,EAAE,uBAAgB,EAAE,gCAAsB,CAAC,EAAE,CAAC;QACvF,IAAI,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,wDAA6B,CAAC,CAAC;IAC9D,CAAC;IAED,IAAI,CAAC,mCAAe,CAAC,CAAC,MAAM,EAAE,CAAC,gBAAgB,EAAE,CAAC;IAClD,IAAI,CAAC,cAAK,CAAC,CAAC,SAAS,CAAC,mCAAe,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=package.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package.spec.d.ts","sourceRoot":"","sources":["../src/package.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,26 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 EclipseSource GmbH and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+ /* note: this bogus test file is required so that
17
+ we are able to run mocha unit tests on this
18
+ package, without having any actual unit tests in it.
19
+ This way a coverage report will be generated,
20
+ showing 0% coverage, instead of no report.
21
+ This file can be removed once we have real unit
22
+ tests in place. */
23
+ describe('ai-terminal package', () => {
24
+ it('support code coverage statistics', () => true);
25
+ });
26
+ //# sourceMappingURL=package.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package.spec.js","sourceRoot":"","sources":["../src/package.spec.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,oDAAoD;AACpD,EAAE;AACF,2EAA2E;AAC3E,mEAAmE;AACnE,wCAAwC;AACxC,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,yDAAyD;AACzD,uDAAuD;AACvD,EAAE;AACF,gFAAgF;AAChF,gFAAgF;AAEhF;;;;;;qBAMqB;AAErB,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IAEjC,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AACvD,CAAC,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@theia/ai-terminal",
3
+ "version": "1.46.0-next.241+4295c1a8c",
4
+ "description": "Theia - AI Terminal Extension",
5
+ "dependencies": {
6
+ "@theia/ai-chat": "1.46.0-next.241+4295c1a8c",
7
+ "@theia/ai-core": "1.46.0-next.241+4295c1a8c",
8
+ "@theia/core": "1.46.0-next.241+4295c1a8c",
9
+ "@theia/terminal": "1.46.0-next.241+4295c1a8c",
10
+ "zod": "^3.23.8",
11
+ "zod-to-json-schema": "^3.23.2"
12
+ },
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "theiaExtensions": [
17
+ {
18
+ "frontend": "lib/browser/ai-terminal-frontend-module"
19
+ }
20
+ ],
21
+ "keywords": [
22
+ "theia-extension"
23
+ ],
24
+ "license": "EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/eclipse-theia/theia.git"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/eclipse-theia/theia/issues"
31
+ },
32
+ "homepage": "https://github.com/eclipse-theia/theia",
33
+ "files": [
34
+ "lib",
35
+ "src"
36
+ ],
37
+ "scripts": {
38
+ "build": "theiaext build",
39
+ "clean": "theiaext clean",
40
+ "compile": "theiaext compile",
41
+ "lint": "theiaext lint",
42
+ "test": "theiaext test",
43
+ "watch": "theiaext watch"
44
+ },
45
+ "devDependencies": {
46
+ "@theia/ext-scripts": "1.56.0"
47
+ },
48
+ "nyc": {
49
+ "extends": "../../configs/nyc.json"
50
+ },
51
+ "gitHead": "4295c1a8c8b5d23471f274265c798bac9e6baecc"
52
+ }
@@ -0,0 +1,223 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 EclipseSource GmbH.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import {
18
+ Agent,
19
+ CommunicationRecordingService,
20
+ getJsonOfResponse,
21
+ isLanguageModelParsedResponse,
22
+ LanguageModelRegistry, LanguageModelRequirement,
23
+ PromptService
24
+ } from '@theia/ai-core/lib/common';
25
+ import { generateUuid, ILogger } from '@theia/core';
26
+ import { inject, injectable } from '@theia/core/shared/inversify';
27
+ import { z } from 'zod';
28
+ import zodToJsonSchema from 'zod-to-json-schema';
29
+
30
+ const Commands = z.object({
31
+ commands: z.array(z.string()),
32
+ });
33
+ type Commands = z.infer<typeof Commands>;
34
+
35
+ @injectable()
36
+ export class AiTerminalAgent implements Agent {
37
+ @inject(CommunicationRecordingService)
38
+ protected recordingService: CommunicationRecordingService;
39
+
40
+ id = 'Terminal Assistant';
41
+ name = 'Terminal Assistant';
42
+ description = 'This agent provides assistance to write and execute arbitrary terminal commands. \
43
+ Based on the user\'s request, it suggests commands and allows the user to directly paste and execute them in the terminal. \
44
+ It accesses the current directory, environment and the recent terminal output of the terminal session to provide context-aware assistance';
45
+ variables = [];
46
+ functions = [];
47
+ agentSpecificVariables = [
48
+ { name: 'userRequest', usedInPrompt: true, description: 'The user\'s question or request.' },
49
+ { name: 'shell', usedInPrompt: true, description: 'The shell being used, e.g., /usr/bin/zsh.' },
50
+ { name: 'cwd', usedInPrompt: true, description: 'The current working directory.' },
51
+ { name: 'recentTerminalContents', usedInPrompt: true, description: 'The last 0 to 50 recent lines visible in the terminal.' }
52
+ ];
53
+ promptTemplates = [
54
+ {
55
+ id: 'terminal-system',
56
+ name: 'AI Terminal System Prompt',
57
+ description: 'Prompt for the AI Terminal Assistant',
58
+ template: `{{!-- Made improvements or adaptations to this prompt template? We’d love for you to share it with the community! Contribute back here:
59
+ https://github.com/eclipse-theia/theia/discussions/new?category=prompt-template-contribution --}}
60
+ # Instructions
61
+ Generate one or more command suggestions based on the user's request, considering the shell being used,
62
+ the current working directory, and the recent terminal contents. Provide the best suggestion first,
63
+ followed by other relevant suggestions if the user asks for further options.
64
+
65
+ Parameters:
66
+ - user-request: The user's question or request.
67
+ - shell: The shell being used, e.g., /usr/bin/zsh.
68
+ - cwd: The current working directory.
69
+ - recent-terminal-contents: The last 0 to 50 recent lines visible in the terminal.
70
+
71
+ Return the result in the following JSON format:
72
+ {
73
+ "commands": [
74
+ "best_command_suggestion",
75
+ "next_best_command_suggestion",
76
+ "another_command_suggestion"
77
+ ]
78
+ }
79
+
80
+ ## Example
81
+ user-request: "How do I commit changes?"
82
+ shell: "/usr/bin/zsh"
83
+ cwd: "/home/user/project"
84
+ recent-terminal-contents:
85
+ git status
86
+ On branch main
87
+ Your branch is up to date with 'origin/main'.
88
+ nothing to commit, working tree clean
89
+
90
+ ## Expected JSON output
91
+ \`\`\`json
92
+ \{
93
+ "commands": [
94
+ "git commit",
95
+ "git commit --amend",
96
+ "git commit -a"
97
+ ]
98
+ }
99
+ \`\`\`
100
+ `
101
+ },
102
+ {
103
+ id: 'terminal-user',
104
+ name: 'AI Terminal User Prompt',
105
+ description: 'Prompt that contains the user request',
106
+ template: `{{!-- Made improvements or adaptations to this prompt template? We’d love for you to share it with the community! Contribute back here:
107
+ https://github.com/eclipse-theia/theia/discussions/new?category=prompt-template-contribution --}}
108
+ user-request: {{userRequest}}
109
+ shell: {{shell}}
110
+ cwd: {{cwd}}
111
+ recent-terminal-contents:
112
+ {{recentTerminalContents}}
113
+ `
114
+ }
115
+ ];
116
+ languageModelRequirements: LanguageModelRequirement[] = [
117
+ {
118
+ purpose: 'suggest-terminal-commands',
119
+ identifier: 'openai/gpt-4o',
120
+ }
121
+ ];
122
+
123
+ @inject(LanguageModelRegistry)
124
+ protected languageModelRegistry: LanguageModelRegistry;
125
+
126
+ @inject(PromptService)
127
+ protected promptService: PromptService;
128
+
129
+ @inject(ILogger)
130
+ protected logger: ILogger;
131
+
132
+ async getCommands(
133
+ userRequest: string,
134
+ cwd: string,
135
+ shell: string,
136
+ recentTerminalContents: string[],
137
+ ): Promise<string[]> {
138
+ const lm = await this.languageModelRegistry.selectLanguageModel({
139
+ agent: this.id,
140
+ ...this.languageModelRequirements[0]
141
+ });
142
+ if (!lm) {
143
+ this.logger.error('No language model available for the AI Terminal Agent.');
144
+ return [];
145
+ }
146
+
147
+ const parameters = {
148
+ userRequest,
149
+ shell,
150
+ cwd,
151
+ recentTerminalContents
152
+ };
153
+
154
+ const systemMessage = await this.promptService.getPrompt('terminal-system', parameters).then(p => p?.text);
155
+ const request = await this.promptService.getPrompt('terminal-user', parameters).then(p => p?.text);
156
+ if (!systemMessage || !request) {
157
+ this.logger.error('The prompt service didn\'t return prompts for the AI Terminal Agent.');
158
+ return [];
159
+ }
160
+
161
+ // since we do not actually hold complete conversions, the request/response pair is considered a session
162
+ const sessionId = generateUuid();
163
+ const requestId = generateUuid();
164
+ this.recordingService.recordRequest({
165
+ agentId: this.id,
166
+ sessionId,
167
+ requestId,
168
+ request,
169
+ systemMessage
170
+ });
171
+
172
+ try {
173
+ const result = await lm.request({
174
+ messages: [
175
+ {
176
+ actor: 'ai',
177
+ type: 'text',
178
+ query: systemMessage
179
+ },
180
+ {
181
+ actor: 'user',
182
+ type: 'text',
183
+ query: request
184
+ }
185
+ ],
186
+ response_format: {
187
+ type: 'json_schema',
188
+ json_schema: {
189
+ name: 'terminal-commands',
190
+ description: 'Suggested terminal commands based on the user request',
191
+ schema: zodToJsonSchema(Commands)
192
+ }
193
+ }
194
+ });
195
+
196
+ if (isLanguageModelParsedResponse(result)) {
197
+ // model returned structured output
198
+ const parsedResult = Commands.safeParse(result.parsed);
199
+ if (parsedResult.success) {
200
+ const response = JSON.stringify(parsedResult.data.commands);
201
+ this.recordingService.recordResponse({ agentId: this.id, sessionId, requestId, response, systemMessage });
202
+ return parsedResult.data.commands;
203
+ }
204
+ }
205
+
206
+ // fall back to agent-based parsing of result
207
+ const jsonResult = await getJsonOfResponse(result);
208
+ const responseTextFromJSON = JSON.stringify(jsonResult);
209
+ this.recordingService.recordResponse({ agentId: this.id, sessionId, requestId, response: responseTextFromJSON });
210
+ const parsedJsonResult = Commands.safeParse(jsonResult);
211
+ if (parsedJsonResult.success) {
212
+ return parsedJsonResult.data.commands;
213
+ }
214
+
215
+ return [];
216
+
217
+ } catch (error) {
218
+ this.logger.error('Error obtaining the command suggestions.', error);
219
+ return [];
220
+ }
221
+ }
222
+
223
+ }
@@ -0,0 +1,197 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 EclipseSource GmbH.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { EXPERIMENTAL_AI_CONTEXT_KEY } from '@theia/ai-core/lib/browser';
18
+ import { CommandContribution, CommandRegistry, MenuContribution, MenuModelRegistry } from '@theia/core';
19
+ import { KeybindingContribution, KeybindingRegistry } from '@theia/core/lib/browser';
20
+ import { inject, injectable } from '@theia/core/shared/inversify';
21
+ import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service';
22
+ import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
23
+ import { TerminalWidgetImpl } from '@theia/terminal/lib/browser/terminal-widget-impl';
24
+ import { AiTerminalAgent } from './ai-terminal-agent';
25
+ import { AICommandHandlerFactory } from '@theia/ai-core/lib/browser/ai-command-handler-factory';
26
+ import { AgentService } from '@theia/ai-core';
27
+
28
+ const AI_TERMINAL_COMMAND = {
29
+ id: 'ai-terminal:open',
30
+ label: 'Ask the AI'
31
+ };
32
+
33
+ @injectable()
34
+ export class AiTerminalCommandContribution implements CommandContribution, MenuContribution, KeybindingContribution {
35
+
36
+ @inject(TerminalService)
37
+ protected terminalService: TerminalService;
38
+
39
+ @inject(AiTerminalAgent)
40
+ protected terminalAgent: AiTerminalAgent;
41
+
42
+ @inject(AICommandHandlerFactory)
43
+ protected commandHandlerFactory: AICommandHandlerFactory;
44
+
45
+ @inject(AgentService)
46
+ private readonly agentService: AgentService;
47
+
48
+ registerKeybindings(keybindings: KeybindingRegistry): void {
49
+ keybindings.registerKeybinding({
50
+ command: AI_TERMINAL_COMMAND.id,
51
+ keybinding: 'ctrlcmd+i',
52
+ when: `terminalFocus && ${EXPERIMENTAL_AI_CONTEXT_KEY}`
53
+ });
54
+ }
55
+ registerMenus(menus: MenuModelRegistry): void {
56
+ menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_5'], {
57
+ when: EXPERIMENTAL_AI_CONTEXT_KEY,
58
+ commandId: AI_TERMINAL_COMMAND.id
59
+ });
60
+ }
61
+ registerCommands(commands: CommandRegistry): void {
62
+ commands.registerCommand(AI_TERMINAL_COMMAND, this.commandHandlerFactory({
63
+ execute: () => {
64
+ if (this.terminalService.currentTerminal instanceof TerminalWidgetImpl && this.agentService.isEnabled(this.terminalAgent.id)) {
65
+ new AiTerminalChatWidget(
66
+ this.terminalService.currentTerminal,
67
+ this.terminalAgent
68
+ );
69
+ }
70
+ }
71
+ }));
72
+ }
73
+ }
74
+
75
+ class AiTerminalChatWidget {
76
+
77
+ protected chatContainer: HTMLDivElement;
78
+ protected chatInput: HTMLTextAreaElement;
79
+ protected chatResultParagraph: HTMLParagraphElement;
80
+ protected chatInputContainer: HTMLDivElement;
81
+
82
+ protected haveResult = false;
83
+ commands: string[];
84
+
85
+ constructor(
86
+ protected terminalWidget: TerminalWidgetImpl,
87
+ protected terminalAgent: AiTerminalAgent
88
+ ) {
89
+ this.chatContainer = document.createElement('div');
90
+ this.chatContainer.className = 'ai-terminal-chat-container';
91
+
92
+ const chatCloseButton = document.createElement('span');
93
+ chatCloseButton.className = 'closeButton codicon codicon-close';
94
+ chatCloseButton.onclick = () => this.dispose();
95
+ this.chatContainer.appendChild(chatCloseButton);
96
+
97
+ const chatResultContainer = document.createElement('div');
98
+ chatResultContainer.className = 'ai-terminal-chat-result';
99
+ this.chatResultParagraph = document.createElement('p');
100
+ this.chatResultParagraph.textContent = 'How can I help you?';
101
+ chatResultContainer.appendChild(this.chatResultParagraph);
102
+ this.chatContainer.appendChild(chatResultContainer);
103
+
104
+ this.chatInputContainer = document.createElement('div');
105
+ this.chatInputContainer.className = 'ai-terminal-chat-input-container';
106
+
107
+ this.chatInput = document.createElement('textarea');
108
+ this.chatInput.className = 'theia-input theia-ChatInput';
109
+ this.chatInput.placeholder = 'Ask about a terminal command...';
110
+ this.chatInput.onkeydown = event => {
111
+ if (event.key === 'Enter' && !event.shiftKey) {
112
+ event.preventDefault();
113
+ if (!this.haveResult) {
114
+ this.send();
115
+ } else {
116
+ this.terminalWidget.sendText(this.chatResultParagraph.innerText);
117
+ this.dispose();
118
+ }
119
+ } else if (event.key === 'Escape') {
120
+ this.dispose();
121
+ } else if (event.key === 'ArrowUp' && this.haveResult) {
122
+ this.updateChatResult(this.getNextCommandIndex(1));
123
+ } else if (event.key === 'ArrowDown' && this.haveResult) {
124
+ this.updateChatResult(this.getNextCommandIndex(-1));
125
+ }
126
+ };
127
+ this.chatInputContainer.appendChild(this.chatInput);
128
+
129
+ const chatInputOptionsContainer = document.createElement('div');
130
+ const chatInputOptionsSpan = document.createElement('span');
131
+ chatInputOptionsSpan.className = 'codicon codicon-send option';
132
+ chatInputOptionsSpan.title = 'Send';
133
+ chatInputOptionsSpan.onclick = () => this.send();
134
+ chatInputOptionsContainer.appendChild(chatInputOptionsSpan);
135
+ this.chatInputContainer.appendChild(chatInputOptionsContainer);
136
+
137
+ this.chatContainer.appendChild(this.chatInputContainer);
138
+
139
+ terminalWidget.node.appendChild(this.chatContainer);
140
+
141
+ this.chatInput.focus();
142
+ }
143
+
144
+ protected async send(): Promise<void> {
145
+ const userRequest = this.chatInput.value;
146
+ if (userRequest) {
147
+ this.chatInput.value = '';
148
+
149
+ this.chatResultParagraph.innerText = 'Loading';
150
+ this.chatResultParagraph.className = 'loading';
151
+
152
+ const cwd = (await this.terminalWidget.cwd).toString();
153
+ const processInfo = await this.terminalWidget.processInfo;
154
+ const shell = processInfo.executable;
155
+ const recentTerminalContents = this.getRecentTerminalCommands();
156
+
157
+ this.commands = await this.terminalAgent.getCommands(userRequest, cwd, shell, recentTerminalContents);
158
+
159
+ if (this.commands.length > 0) {
160
+ this.chatResultParagraph.className = 'command';
161
+ this.chatResultParagraph.innerText = this.commands[0];
162
+ this.chatInput.placeholder = 'Hit enter to confirm';
163
+ if (this.commands.length > 1) {
164
+ this.chatInput.placeholder += ' or use ⇅ to show alternatives...';
165
+ }
166
+ this.haveResult = true;
167
+ } else {
168
+ this.chatResultParagraph.className = '';
169
+ this.chatResultParagraph.innerText = 'No results';
170
+ this.chatInput.placeholder = 'Try again...';
171
+ }
172
+ }
173
+ }
174
+
175
+ protected getRecentTerminalCommands(): string[] {
176
+ const maxLines = 100;
177
+ return this.terminalWidget.buffer.getLines(0,
178
+ this.terminalWidget.buffer.length > maxLines ? maxLines : this.terminalWidget.buffer.length
179
+ );
180
+ }
181
+
182
+ protected getNextCommandIndex(step: number): number {
183
+ const currentIndex = this.commands.indexOf(this.chatResultParagraph.innerText);
184
+ const nextIndex = (currentIndex + step + this.commands.length) % this.commands.length;
185
+ return nextIndex;
186
+ }
187
+
188
+ protected updateChatResult(index: number): void {
189
+ this.chatResultParagraph.innerText = this.commands[index];
190
+ }
191
+
192
+ protected dispose(): void {
193
+ this.chatInput.value = '';
194
+ this.terminalWidget.node.removeChild(this.chatContainer);
195
+ this.terminalWidget.getTerminal().focus();
196
+ }
197
+ }
@@ -0,0 +1,34 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 EclipseSource GmbH.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { Agent } from '@theia/ai-core/lib/common';
18
+ import { CommandContribution, MenuContribution } from '@theia/core';
19
+ import { KeybindingContribution } from '@theia/core/lib/browser';
20
+ import { ContainerModule } from '@theia/core/shared/inversify';
21
+ import { AiTerminalAgent } from './ai-terminal-agent';
22
+ import { AiTerminalCommandContribution } from './ai-terminal-contribution';
23
+
24
+ import '../../src/browser/style/ai-terminal.css';
25
+
26
+ export default new ContainerModule(bind => {
27
+ bind(AiTerminalCommandContribution).toSelf().inSingletonScope();
28
+ for (const identifier of [CommandContribution, MenuContribution, KeybindingContribution]) {
29
+ bind(identifier).toService(AiTerminalCommandContribution);
30
+ }
31
+
32
+ bind(AiTerminalAgent).toSelf().inSingletonScope();
33
+ bind(Agent).toService(AiTerminalAgent);
34
+ });
@@ -0,0 +1,94 @@
1
+ .ai-terminal-chat-container {
2
+ position: absolute;
3
+ bottom: 0;
4
+ left: 50%;
5
+ transform: translateX(-50%);
6
+ width: 100%;
7
+ max-width: 500px;
8
+ padding: 10px;
9
+ box-sizing: border-box;
10
+ background: var(--theia-menu-background);
11
+ color: var(--theia-menu-foreground);
12
+ margin-bottom: 12px;
13
+ display: flex;
14
+ flex-direction: column;
15
+ align-items: center;
16
+ border: 1px solid var(--theia-menu-border);
17
+ }
18
+
19
+ .ai-terminal-chat-container .closeButton {
20
+ position: absolute;
21
+ top: 1em;
22
+ right: 1em;
23
+ cursor: pointer;
24
+ }
25
+
26
+ .ai-terminal-chat-container .closeButton:hover {
27
+ color: var(--theia-menu-foreground);
28
+ }
29
+
30
+ .ai-terminal-chat-result {
31
+ width: 100%;
32
+ margin-bottom: 10px;
33
+ }
34
+
35
+ .ai-terminal-chat-input-container {
36
+ width: 100%;
37
+ display: flex;
38
+ align-items: center;
39
+ }
40
+
41
+ .ai-terminal-chat-input-container textarea {
42
+ flex-grow: 1;
43
+ height: 36px;
44
+ background-color: var(--theia-input-background);
45
+ border-radius: 4px;
46
+ box-sizing: border-box;
47
+ padding: 8px;
48
+ resize: none;
49
+ overflow: hidden;
50
+ line-height: 1.3rem;
51
+ margin-right: 10px; /* Add some space between textarea and button */
52
+ }
53
+
54
+ .ai-terminal-chat-input-container .option {
55
+ width: 21px;
56
+ height: 21px;
57
+ display: inline-block;
58
+ box-sizing: border-box;
59
+ user-select: none;
60
+ background-repeat: no-repeat;
61
+ background-position: center;
62
+ border: var(--theia-border-width) solid transparent;
63
+ opacity: 0.7;
64
+ cursor: pointer;
65
+ }
66
+
67
+ .ai-terminal-chat-input-container .option:hover {
68
+ opacity: 1;
69
+ }
70
+
71
+ @keyframes dots {
72
+ 0%,
73
+ 20% {
74
+ content: "";
75
+ }
76
+ 40% {
77
+ content: ".";
78
+ }
79
+ 60% {
80
+ content: "..";
81
+ }
82
+ 80%,
83
+ 100% {
84
+ content: "...";
85
+ }
86
+ }
87
+ .ai-terminal-chat-result p.loading::after {
88
+ content: "";
89
+ animation: dots 1s steps(1, end) infinite;
90
+ }
91
+
92
+ .ai-terminal-chat-result p.command {
93
+ font-family: "Droid Sans Mono", "monospace", monospace;
94
+ }
@@ -0,0 +1,28 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 EclipseSource GmbH and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ /* note: this bogus test file is required so that
18
+ we are able to run mocha unit tests on this
19
+ package, without having any actual unit tests in it.
20
+ This way a coverage report will be generated,
21
+ showing 0% coverage, instead of no report.
22
+ This file can be removed once we have real unit
23
+ tests in place. */
24
+
25
+ describe('ai-terminal package', () => {
26
+
27
+ it('support code coverage statistics', () => true);
28
+ });