@theia/ai-claude-code 1.65.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/lib/browser/claude-code-chat-agent.d.ts +82 -0
- package/lib/browser/claude-code-chat-agent.d.ts.map +1 -0
- package/lib/browser/claude-code-chat-agent.js +518 -0
- package/lib/browser/claude-code-chat-agent.js.map +1 -0
- package/lib/browser/claude-code-command-contribution.d.ts +16 -0
- package/lib/browser/claude-code-command-contribution.d.ts.map +1 -0
- package/lib/browser/claude-code-command-contribution.js +86 -0
- package/lib/browser/claude-code-command-contribution.js.map +1 -0
- package/lib/browser/claude-code-edit-tool-service.d.ts +61 -0
- package/lib/browser/claude-code-edit-tool-service.d.ts.map +1 -0
- package/lib/browser/claude-code-edit-tool-service.js +219 -0
- package/lib/browser/claude-code-edit-tool-service.js.map +1 -0
- package/lib/browser/claude-code-file-edit-backup-service.d.ts +57 -0
- package/lib/browser/claude-code-file-edit-backup-service.d.ts.map +1 -0
- package/lib/browser/claude-code-file-edit-backup-service.js +92 -0
- package/lib/browser/claude-code-file-edit-backup-service.js.map +1 -0
- package/lib/browser/claude-code-frontend-module.d.ts +5 -0
- package/lib/browser/claude-code-frontend-module.d.ts.map +1 -0
- package/lib/browser/claude-code-frontend-module.js +83 -0
- package/lib/browser/claude-code-frontend-module.js.map +1 -0
- package/lib/browser/claude-code-frontend-service.d.ts +40 -0
- package/lib/browser/claude-code-frontend-service.d.ts.map +1 -0
- package/lib/browser/claude-code-frontend-service.js +190 -0
- package/lib/browser/claude-code-frontend-service.js.map +1 -0
- package/lib/browser/claude-code-slash-commands-contribution.d.ts +17 -0
- package/lib/browser/claude-code-slash-commands-contribution.d.ts.map +1 -0
- package/lib/browser/claude-code-slash-commands-contribution.js +154 -0
- package/lib/browser/claude-code-slash-commands-contribution.js.map +1 -0
- package/lib/browser/claude-code-tool-call-content.d.ts +8 -0
- package/lib/browser/claude-code-tool-call-content.d.ts.map +1 -0
- package/lib/browser/claude-code-tool-call-content.js +30 -0
- package/lib/browser/claude-code-tool-call-content.js.map +1 -0
- package/lib/browser/renderers/bash-tool-renderer.d.ts +10 -0
- package/lib/browser/renderers/bash-tool-renderer.d.ts.map +1 -0
- package/lib/browser/renderers/bash-tool-renderer.js +71 -0
- package/lib/browser/renderers/bash-tool-renderer.js.map +1 -0
- package/lib/browser/renderers/collapsible-tool-renderer.d.ts +13 -0
- package/lib/browser/renderers/collapsible-tool-renderer.d.ts.map +1 -0
- package/lib/browser/renderers/collapsible-tool-renderer.js +48 -0
- package/lib/browser/renderers/collapsible-tool-renderer.js.map +1 -0
- package/lib/browser/renderers/edit-tool-renderer.d.ts +16 -0
- package/lib/browser/renderers/edit-tool-renderer.d.ts.map +1 -0
- package/lib/browser/renderers/edit-tool-renderer.js +134 -0
- package/lib/browser/renderers/edit-tool-renderer.js.map +1 -0
- package/lib/browser/renderers/glob-tool-renderer.d.ts +14 -0
- package/lib/browser/renderers/glob-tool-renderer.d.ts.map +1 -0
- package/lib/browser/renderers/glob-tool-renderer.js +107 -0
- package/lib/browser/renderers/glob-tool-renderer.js.map +1 -0
- package/lib/browser/renderers/grep-tool-renderer.d.ts +14 -0
- package/lib/browser/renderers/grep-tool-renderer.d.ts.map +1 -0
- package/lib/browser/renderers/grep-tool-renderer.js +157 -0
- package/lib/browser/renderers/grep-tool-renderer.js.map +1 -0
- package/lib/browser/renderers/ls-tool-renderer.d.ts +16 -0
- package/lib/browser/renderers/ls-tool-renderer.d.ts.map +1 -0
- package/lib/browser/renderers/ls-tool-renderer.js +116 -0
- package/lib/browser/renderers/ls-tool-renderer.js.map +1 -0
- package/lib/browser/renderers/multiedit-tool-renderer.d.ts +16 -0
- package/lib/browser/renderers/multiedit-tool-renderer.d.ts.map +1 -0
- package/lib/browser/renderers/multiedit-tool-renderer.js +152 -0
- package/lib/browser/renderers/multiedit-tool-renderer.js.map +1 -0
- package/lib/browser/renderers/read-tool-renderer.d.ts +16 -0
- package/lib/browser/renderers/read-tool-renderer.d.ts.map +1 -0
- package/lib/browser/renderers/read-tool-renderer.js +121 -0
- package/lib/browser/renderers/read-tool-renderer.js.map +1 -0
- package/lib/browser/renderers/todo-write-renderer.d.ts +10 -0
- package/lib/browser/renderers/todo-write-renderer.d.ts.map +1 -0
- package/lib/browser/renderers/todo-write-renderer.js +132 -0
- package/lib/browser/renderers/todo-write-renderer.js.map +1 -0
- package/lib/browser/renderers/web-fetch-tool-renderer.d.ts +10 -0
- package/lib/browser/renderers/web-fetch-tool-renderer.d.ts.map +1 -0
- package/lib/browser/renderers/web-fetch-tool-renderer.js +82 -0
- package/lib/browser/renderers/web-fetch-tool-renderer.js.map +1 -0
- package/lib/browser/renderers/write-tool-renderer.d.ts +16 -0
- package/lib/browser/renderers/write-tool-renderer.d.ts.map +1 -0
- package/lib/browser/renderers/write-tool-renderer.js +113 -0
- package/lib/browser/renderers/write-tool-renderer.js.map +1 -0
- package/lib/common/claude-code-preferences.d.ts +4 -0
- package/lib/common/claude-code-preferences.d.ts.map +1 -0
- package/lib/common/claude-code-preferences.js +33 -0
- package/lib/common/claude-code-preferences.js.map +1 -0
- package/lib/common/claude-code-service.d.ts +231 -0
- package/lib/common/claude-code-service.d.ts.map +1 -0
- package/lib/common/claude-code-service.js +82 -0
- package/lib/common/claude-code-service.js.map +1 -0
- package/lib/common/index.d.ts +2 -0
- package/lib/common/index.d.ts.map +1 -0
- package/lib/common/index.js +20 -0
- package/lib/common/index.js.map +1 -0
- package/lib/node/claude-code-backend-module.d.ts +4 -0
- package/lib/node/claude-code-backend-module.d.ts.map +1 -0
- package/lib/node/claude-code-backend-module.js +35 -0
- package/lib/node/claude-code-backend-module.js.map +1 -0
- package/lib/node/claude-code-service-impl.d.ts +32 -0
- package/lib/node/claude-code-service-impl.d.ts.map +1 -0
- package/lib/node/claude-code-service-impl.js +426 -0
- package/lib/node/claude-code-service-impl.js.map +1 -0
- package/lib/package.spec.d.ts +1 -0
- package/lib/package.spec.d.ts.map +1 -0
- package/lib/package.spec.js +26 -0
- package/lib/package.spec.js.map +1 -0
- package/package.json +57 -0
- package/src/browser/claude-code-chat-agent.ts +591 -0
- package/src/browser/claude-code-command-contribution.ts +80 -0
- package/src/browser/claude-code-edit-tool-service.ts +313 -0
- package/src/browser/claude-code-file-edit-backup-service.ts +141 -0
- package/src/browser/claude-code-frontend-module.ts +100 -0
- package/src/browser/claude-code-frontend-service.ts +215 -0
- package/src/browser/claude-code-slash-commands-contribution.ts +175 -0
- package/src/browser/claude-code-tool-call-content.ts +30 -0
- package/src/browser/renderers/bash-tool-renderer.tsx +97 -0
- package/src/browser/renderers/collapsible-tool-renderer.tsx +78 -0
- package/src/browser/renderers/edit-tool-renderer.tsx +180 -0
- package/src/browser/renderers/glob-tool-renderer.tsx +136 -0
- package/src/browser/renderers/grep-tool-renderer.tsx +190 -0
- package/src/browser/renderers/ls-tool-renderer.tsx +160 -0
- package/src/browser/renderers/multiedit-tool-renderer.tsx +204 -0
- package/src/browser/renderers/read-tool-renderer.tsx +170 -0
- package/src/browser/renderers/todo-write-renderer.tsx +178 -0
- package/src/browser/renderers/web-fetch-tool-renderer.tsx +108 -0
- package/src/browser/renderers/write-tool-renderer.tsx +155 -0
- package/src/browser/style/claude-code-tool-renderers.css +487 -0
- package/src/common/claude-code-preferences.ts +33 -0
- package/src/common/claude-code-service.ts +303 -0
- package/src/common/index.ts +17 -0
- package/src/node/claude-code-backend-module.ts +42 -0
- package/src/node/claude-code-service-impl.ts +462 -0
- package/src/package.spec.ts +27 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 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 { CancellationToken, generateUuid, ILogger, PreferenceService } from '@theia/core';
|
|
18
|
+
import { FileUri } from '@theia/core/lib/common/file-uri';
|
|
19
|
+
import { inject, injectable, LazyServiceIdentifier } from '@theia/core/shared/inversify';
|
|
20
|
+
import {
|
|
21
|
+
OutputChannel,
|
|
22
|
+
OutputChannelManager,
|
|
23
|
+
OutputChannelSeverity
|
|
24
|
+
} from '@theia/output/lib/browser/output-channel';
|
|
25
|
+
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
26
|
+
import {
|
|
27
|
+
ClaudeCodeClient,
|
|
28
|
+
ClaudeCodeOptions,
|
|
29
|
+
ClaudeCodeRequest,
|
|
30
|
+
ClaudeCodeService,
|
|
31
|
+
SDKMessage,
|
|
32
|
+
StreamMessage,
|
|
33
|
+
ToolApprovalResponseMessage
|
|
34
|
+
} from '../common/claude-code-service';
|
|
35
|
+
import { CLAUDE_CODE_EXECUTABLE_PATH_PREF } from '../common/claude-code-preferences';
|
|
36
|
+
|
|
37
|
+
export const API_KEY_PREF = 'ai-features.anthropic.AnthropicApiKey';
|
|
38
|
+
|
|
39
|
+
@injectable()
|
|
40
|
+
export class ClaudeCodeClientImpl implements ClaudeCodeClient {
|
|
41
|
+
protected tokenHandlers = new Map<string, (token?: StreamMessage) => void>();
|
|
42
|
+
protected errorHandlers = new Map<string, (error: Error) => void>();
|
|
43
|
+
|
|
44
|
+
// invoked by the backend
|
|
45
|
+
sendToken(streamId: string, token?: StreamMessage): void {
|
|
46
|
+
const handler = this.tokenHandlers.get(streamId);
|
|
47
|
+
if (handler) {
|
|
48
|
+
handler(token);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// invoked by the backend
|
|
53
|
+
sendError(streamId: string, error: Error): void {
|
|
54
|
+
const handler = this.errorHandlers.get(streamId);
|
|
55
|
+
if (handler) {
|
|
56
|
+
handler(error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
registerTokenHandler(streamId: string, handler: (token?: StreamMessage) => void): void {
|
|
61
|
+
this.tokenHandlers.set(streamId, handler);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
registerErrorHandler(streamId: string, handler: (error: Error) => void): void {
|
|
65
|
+
this.errorHandlers.set(streamId, handler);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
unregisterHandlers(streamId: string): void {
|
|
69
|
+
this.tokenHandlers.delete(streamId);
|
|
70
|
+
this.errorHandlers.delete(streamId);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface StreamState {
|
|
75
|
+
id: string;
|
|
76
|
+
tokens: (StreamMessage | undefined)[];
|
|
77
|
+
isComplete: boolean;
|
|
78
|
+
hasError: boolean;
|
|
79
|
+
error?: Error;
|
|
80
|
+
pendingResolve?: () => void;
|
|
81
|
+
pendingReject?: (error: Error) => void;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@injectable()
|
|
85
|
+
export class ClaudeCodeFrontendService {
|
|
86
|
+
|
|
87
|
+
@inject(ClaudeCodeService)
|
|
88
|
+
protected claudeCodeBackendService: ClaudeCodeService;
|
|
89
|
+
|
|
90
|
+
@inject(new LazyServiceIdentifier(() => ClaudeCodeClientImpl))
|
|
91
|
+
protected client: ClaudeCodeClientImpl;
|
|
92
|
+
|
|
93
|
+
@inject(WorkspaceService)
|
|
94
|
+
protected readonly workspaceService: WorkspaceService;
|
|
95
|
+
|
|
96
|
+
@inject(PreferenceService)
|
|
97
|
+
protected preferenceService: PreferenceService;
|
|
98
|
+
|
|
99
|
+
@inject(OutputChannelManager)
|
|
100
|
+
protected readonly outputChannelManager: OutputChannelManager;
|
|
101
|
+
|
|
102
|
+
@inject(ILogger)
|
|
103
|
+
protected logger: ILogger;
|
|
104
|
+
|
|
105
|
+
protected streams = new Map<string, StreamState>();
|
|
106
|
+
|
|
107
|
+
async send(request: ClaudeCodeRequest, cancellationToken?: CancellationToken): Promise<AsyncIterable<StreamMessage>> {
|
|
108
|
+
const streamState: StreamState = {
|
|
109
|
+
id: this.generateStreamId(),
|
|
110
|
+
tokens: [],
|
|
111
|
+
isComplete: false,
|
|
112
|
+
hasError: false
|
|
113
|
+
};
|
|
114
|
+
this.streams.set(streamState.id, streamState);
|
|
115
|
+
this.setupStreamHandlers(streamState);
|
|
116
|
+
|
|
117
|
+
cancellationToken?.onCancellationRequested(() => this.claudeCodeBackendService.cancel(streamState.id));
|
|
118
|
+
|
|
119
|
+
const roots = await this.workspaceService.roots;
|
|
120
|
+
const rootsUris = roots.map(root => FileUri.fsPath(root.resource.toString()));
|
|
121
|
+
|
|
122
|
+
const prompt = request.prompt;
|
|
123
|
+
const apiKey = this.preferenceService.get<string>(API_KEY_PREF, undefined);
|
|
124
|
+
const claudeCodePath = this.preferenceService.get<string>(CLAUDE_CODE_EXECUTABLE_PATH_PREF, undefined);
|
|
125
|
+
this.getOutputChannel()?.appendLine(JSON.stringify(request, undefined, 2));
|
|
126
|
+
|
|
127
|
+
await this.claudeCodeBackendService.send({
|
|
128
|
+
prompt,
|
|
129
|
+
apiKey,
|
|
130
|
+
claudeCodePath,
|
|
131
|
+
options: <ClaudeCodeOptions>{
|
|
132
|
+
cwd: rootsUris[0],
|
|
133
|
+
...request.options
|
|
134
|
+
}
|
|
135
|
+
}, streamState.id);
|
|
136
|
+
|
|
137
|
+
return this.createAsyncIterable(streamState);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
protected generateStreamId(): string {
|
|
141
|
+
return generateUuid();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
protected setupStreamHandlers(streamState: StreamState): void {
|
|
145
|
+
this.client.registerTokenHandler(streamState.id, (token?: SDKMessage) => {
|
|
146
|
+
if (token === undefined) {
|
|
147
|
+
streamState.isComplete = true;
|
|
148
|
+
} else {
|
|
149
|
+
this.getOutputChannel()?.appendLine(JSON.stringify(token, undefined, 2));
|
|
150
|
+
streamState.tokens.push(token);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Resolve any pending iterator
|
|
154
|
+
if (streamState.pendingResolve) {
|
|
155
|
+
streamState.pendingResolve();
|
|
156
|
+
streamState.pendingResolve = undefined;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
this.client.registerErrorHandler(streamState.id, (error: Error) => {
|
|
161
|
+
streamState.hasError = true;
|
|
162
|
+
streamState.error = error;
|
|
163
|
+
this.getOutputChannel()?.appendLine(JSON.stringify(error, undefined, 2), OutputChannelSeverity.Error);
|
|
164
|
+
|
|
165
|
+
// Reject any pending iterator
|
|
166
|
+
if (streamState.pendingReject) {
|
|
167
|
+
streamState.pendingReject(error);
|
|
168
|
+
streamState.pendingReject = undefined;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
protected async *createAsyncIterable(streamState: StreamState): AsyncIterable<StreamMessage> {
|
|
174
|
+
let currentIndex = 0;
|
|
175
|
+
|
|
176
|
+
while (true) {
|
|
177
|
+
// Check for available tokens
|
|
178
|
+
if (currentIndex < streamState.tokens.length) {
|
|
179
|
+
const token = streamState.tokens[currentIndex];
|
|
180
|
+
currentIndex++;
|
|
181
|
+
if (token !== undefined) {
|
|
182
|
+
yield token;
|
|
183
|
+
}
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (streamState.isComplete) {
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
if (streamState.hasError && streamState.error) {
|
|
191
|
+
throw streamState.error;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Wait for next token
|
|
195
|
+
await new Promise<void>((resolve, reject) => {
|
|
196
|
+
streamState.pendingResolve = resolve;
|
|
197
|
+
streamState.pendingReject = reject;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Cleanup
|
|
202
|
+
this.client.unregisterHandlers(streamState.id);
|
|
203
|
+
this.streams.delete(streamState.id);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
sendApprovalResponse(response: ToolApprovalResponseMessage): void {
|
|
207
|
+
this.getOutputChannel()?.appendLine(JSON.stringify(response, undefined, 2));
|
|
208
|
+
this.claudeCodeBackendService.handleApprovalResponse(response);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
protected getOutputChannel(): OutputChannel | undefined {
|
|
212
|
+
return this.outputChannelManager.getChannel('Claude Code');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 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 { CHAT_VIEW_LANGUAGE_ID } from '@theia/ai-chat-ui/lib/browser/chat-view-language-contribution';
|
|
18
|
+
import { URI } from '@theia/core';
|
|
19
|
+
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
|
|
20
|
+
import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
|
|
21
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
22
|
+
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
23
|
+
import * as monaco from '@theia/monaco-editor-core';
|
|
24
|
+
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
25
|
+
import { CLAUDE_CHAT_AGENT_ID } from './claude-code-chat-agent';
|
|
26
|
+
|
|
27
|
+
const CLAUDE_COMMANDS = '.claude/commands';
|
|
28
|
+
|
|
29
|
+
interface StaticSlashCommand {
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@injectable()
|
|
35
|
+
export class ClaudeCodeSlashCommandsContribution implements FrontendApplicationContribution {
|
|
36
|
+
|
|
37
|
+
private readonly staticCommands: StaticSlashCommand[] = [
|
|
38
|
+
{
|
|
39
|
+
name: 'clear',
|
|
40
|
+
description: 'Create a new session',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'compact',
|
|
44
|
+
description: 'Compact conversation with optional focus instructions',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'config',
|
|
48
|
+
description: 'Open Claude Code Configuration',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'init',
|
|
52
|
+
description: 'Initialize project with CLAUDE.md guide',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'memory',
|
|
56
|
+
description: 'Edit CLAUDE.md memory file',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'review',
|
|
60
|
+
description: 'Request code review',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'resume',
|
|
64
|
+
description: 'Resume a session',
|
|
65
|
+
}
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
@inject(ContextKeyService)
|
|
69
|
+
protected readonly contextKeyService: ContextKeyService;
|
|
70
|
+
|
|
71
|
+
@inject(WorkspaceService)
|
|
72
|
+
protected readonly workspaceService: WorkspaceService;
|
|
73
|
+
|
|
74
|
+
@inject(FileService)
|
|
75
|
+
protected readonly fileService: FileService;
|
|
76
|
+
|
|
77
|
+
onStart(): void {
|
|
78
|
+
monaco.languages.registerCompletionItemProvider(CHAT_VIEW_LANGUAGE_ID, {
|
|
79
|
+
triggerCharacters: ['/'],
|
|
80
|
+
provideCompletionItems: (model, position, _context, _token) =>
|
|
81
|
+
this.provideSlashCompletions(model, position),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
protected async provideSlashCompletions(
|
|
86
|
+
model: monaco.editor.ITextModel,
|
|
87
|
+
position: monaco.Position
|
|
88
|
+
): Promise<monaco.languages.CompletionList> {
|
|
89
|
+
const isClaudeCode = this.contextKeyService.match(`chatInputReceivingAgent == '${CLAUDE_CHAT_AGENT_ID}'`);
|
|
90
|
+
if (!isClaudeCode) {
|
|
91
|
+
return { suggestions: [] };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const completionRange = this.getCompletionRange(model, position, '/');
|
|
95
|
+
if (completionRange === undefined) {
|
|
96
|
+
return { suggestions: [] };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const suggestions: monaco.languages.CompletionItem[] = [];
|
|
101
|
+
|
|
102
|
+
// Add static commands
|
|
103
|
+
this.staticCommands.forEach(command => {
|
|
104
|
+
suggestions.push({
|
|
105
|
+
insertText: `${command.name} `,
|
|
106
|
+
kind: monaco.languages.CompletionItemKind.Function,
|
|
107
|
+
label: command.name,
|
|
108
|
+
range: completionRange,
|
|
109
|
+
detail: command.description
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Add dynamic commands from .claude/commands directory
|
|
114
|
+
const roots = this.workspaceService.tryGetRoots();
|
|
115
|
+
if (roots.length >= 1) {
|
|
116
|
+
const uri = roots[0].resource;
|
|
117
|
+
const claudeCommandsUri = uri.resolve(CLAUDE_COMMANDS);
|
|
118
|
+
const files = await this.listFilesDirectly(claudeCommandsUri);
|
|
119
|
+
const commands = files
|
|
120
|
+
.filter(file => file.endsWith('.md'))
|
|
121
|
+
.map(file => file.replace(/\.md$/, ''));
|
|
122
|
+
|
|
123
|
+
commands.forEach(commandName => {
|
|
124
|
+
suggestions.push({
|
|
125
|
+
insertText: `${commandName} `,
|
|
126
|
+
kind: monaco.languages.CompletionItemKind.Function,
|
|
127
|
+
label: commandName,
|
|
128
|
+
range: completionRange,
|
|
129
|
+
detail: `Claude command: ${commandName}`
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { suggestions };
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error('Error in Claude completion provider:', error);
|
|
137
|
+
return { suggestions: [] };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
protected getCompletionRange(model: monaco.editor.ITextModel, position: monaco.Position, triggerCharacter: string): monaco.Range | undefined {
|
|
142
|
+
const wordInfo = model.getWordUntilPosition(position);
|
|
143
|
+
const lineContent = model.getLineContent(position.lineNumber);
|
|
144
|
+
|
|
145
|
+
// one to the left, and -1 for 0-based index
|
|
146
|
+
const characterBeforeCurrentWord = lineContent[wordInfo.startColumn - 1 - 1];
|
|
147
|
+
if (characterBeforeCurrentWord !== triggerCharacter) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return new monaco.Range(
|
|
152
|
+
position.lineNumber,
|
|
153
|
+
wordInfo.startColumn,
|
|
154
|
+
position.lineNumber,
|
|
155
|
+
position.column
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
protected async listFilesDirectly(uri: URI): Promise<string[]> {
|
|
160
|
+
const result: string[] = [];
|
|
161
|
+
if (!await this.fileService.exists(uri)) {
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const stat = await this.fileService.resolve(uri);
|
|
166
|
+
if (stat && stat.isDirectory && stat.children) {
|
|
167
|
+
for (const child of stat.children) {
|
|
168
|
+
result.push(child.resource.path.base);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 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 { ToolCallChatResponseContentImpl } from '@theia/ai-chat/lib/common';
|
|
18
|
+
import { ToolCallResult } from '@theia/ai-core';
|
|
19
|
+
|
|
20
|
+
export class ClaudeCodeToolCallChatResponseContent extends ToolCallChatResponseContentImpl {
|
|
21
|
+
static readonly type = 'claude-code-tool-call';
|
|
22
|
+
|
|
23
|
+
constructor(id?: string, name?: string, arg_string?: string, finished?: boolean, result?: ToolCallResult) {
|
|
24
|
+
super(id, name, arg_string, finished, result);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static is(content: unknown): content is ClaudeCodeToolCallChatResponseContent {
|
|
28
|
+
return content instanceof ClaudeCodeToolCallChatResponseContent;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 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 { ChatResponsePartRenderer } from '@theia/ai-chat-ui/lib/browser/chat-response-part-renderer';
|
|
18
|
+
import { ResponseNode } from '@theia/ai-chat-ui/lib/browser/chat-tree-view';
|
|
19
|
+
import { ChatResponseContent, ToolCallChatResponseContent } from '@theia/ai-chat/lib/common';
|
|
20
|
+
import { codicon } from '@theia/core/lib/browser';
|
|
21
|
+
import { injectable } from '@theia/core/shared/inversify';
|
|
22
|
+
import * as React from '@theia/core/shared/react';
|
|
23
|
+
import { ReactNode } from '@theia/core/shared/react';
|
|
24
|
+
import { ClaudeCodeToolCallChatResponseContent } from '../claude-code-tool-call-content';
|
|
25
|
+
import { CollapsibleToolRenderer } from './collapsible-tool-renderer';
|
|
26
|
+
|
|
27
|
+
interface BashToolInput {
|
|
28
|
+
command: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
timeout?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@injectable()
|
|
34
|
+
export class BashToolRenderer implements ChatResponsePartRenderer<ToolCallChatResponseContent> {
|
|
35
|
+
|
|
36
|
+
canHandle(response: ChatResponseContent): number {
|
|
37
|
+
if (ClaudeCodeToolCallChatResponseContent.is(response) && response.name === 'Bash') {
|
|
38
|
+
return 15; // Higher than default ToolCallPartRenderer (10)
|
|
39
|
+
}
|
|
40
|
+
return -1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
render(response: ToolCallChatResponseContent, parentNode: ResponseNode): ReactNode {
|
|
44
|
+
try {
|
|
45
|
+
const input = JSON.parse(response.arguments || '{}') as BashToolInput;
|
|
46
|
+
return <BashToolComponent input={input} />;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.warn('Failed to parse Bash tool input:', error);
|
|
49
|
+
return <div className="claude-code-tool error">Failed to parse Bash tool data</div>;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const BashToolComponent: React.FC<{
|
|
55
|
+
input: BashToolInput;
|
|
56
|
+
}> = ({ input }) => {
|
|
57
|
+
const compactHeader = (
|
|
58
|
+
<>
|
|
59
|
+
<div className="claude-code-tool header-left">
|
|
60
|
+
<span className="claude-code-tool title">Terminal</span>
|
|
61
|
+
<span className={`${codicon('terminal')} claude-code-tool icon`} />
|
|
62
|
+
<span className="claude-code-tool command">{input.command}</span>
|
|
63
|
+
</div>
|
|
64
|
+
<div className="claude-code-tool header-right">
|
|
65
|
+
{input.timeout && (
|
|
66
|
+
<span className="claude-code-tool badge">Timeout: {input.timeout}ms</span>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
</>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const expandedContent = input.description ? (
|
|
73
|
+
<div className="claude-code-tool details">
|
|
74
|
+
<div className="claude-code-tool detail-row">
|
|
75
|
+
<span className="claude-code-tool detail-label">Command</span>
|
|
76
|
+
<code className="claude-code-tool detail-value">{input.command}</code>
|
|
77
|
+
</div>
|
|
78
|
+
<div className="claude-code-tool detail-row">
|
|
79
|
+
<span className="claude-code-tool detail-label">Description</span>
|
|
80
|
+
<span className="claude-code-tool detail-value">{input.description}</span>
|
|
81
|
+
</div>
|
|
82
|
+
{input.timeout && (
|
|
83
|
+
<div className="claude-code-tool detail-row">
|
|
84
|
+
<span className="claude-code-tool detail-label">Timeout</span>
|
|
85
|
+
<span className="claude-code-tool detail-value">{input.timeout}ms</span>
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
) : undefined;
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<CollapsibleToolRenderer
|
|
93
|
+
compactHeader={compactHeader}
|
|
94
|
+
expandedContent={expandedContent}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 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 { codicon } from '@theia/core/lib/browser';
|
|
18
|
+
import * as React from '@theia/core/shared/react';
|
|
19
|
+
import { ReactNode } from '@theia/core/shared/react';
|
|
20
|
+
|
|
21
|
+
interface CollapsibleToolRendererProps {
|
|
22
|
+
compactHeader: ReactNode;
|
|
23
|
+
expandedContent?: ReactNode;
|
|
24
|
+
onHeaderClick?: () => void;
|
|
25
|
+
headerStyle?: React.CSSProperties;
|
|
26
|
+
defaultExpanded?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const CollapsibleToolRenderer: React.FC<CollapsibleToolRendererProps> = ({
|
|
30
|
+
compactHeader,
|
|
31
|
+
expandedContent,
|
|
32
|
+
onHeaderClick,
|
|
33
|
+
headerStyle,
|
|
34
|
+
defaultExpanded = false
|
|
35
|
+
}) => {
|
|
36
|
+
const [isExpanded, setIsExpanded] = React.useState(defaultExpanded);
|
|
37
|
+
|
|
38
|
+
const hasExpandableContent = expandedContent !== undefined;
|
|
39
|
+
|
|
40
|
+
const handleHeaderClick = (event: React.MouseEvent) => {
|
|
41
|
+
// Check if the clicked element or any of its parents has the 'clickable' class
|
|
42
|
+
// If so, don't trigger collapse/expand behavior
|
|
43
|
+
const target = event.target as HTMLElement;
|
|
44
|
+
if (target.closest('.clickable-element')) {
|
|
45
|
+
onHeaderClick?.();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Normal header click behavior
|
|
50
|
+
if (hasExpandableContent) {
|
|
51
|
+
setIsExpanded(!isExpanded);
|
|
52
|
+
}
|
|
53
|
+
onHeaderClick?.();
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="claude-code-tool container">
|
|
58
|
+
<div
|
|
59
|
+
className={`claude-code-tool header${hasExpandableContent ? ' expandable' : ''}`}
|
|
60
|
+
onClick={handleHeaderClick}
|
|
61
|
+
style={{
|
|
62
|
+
cursor: hasExpandableContent || onHeaderClick ? 'pointer' : 'default',
|
|
63
|
+
...headerStyle
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
{hasExpandableContent && (
|
|
67
|
+
<span className={`${codicon(isExpanded ? 'chevron-down' : 'chevron-right')} claude-code-tool expand-icon`} />
|
|
68
|
+
)}
|
|
69
|
+
{compactHeader}
|
|
70
|
+
</div>
|
|
71
|
+
{hasExpandableContent && isExpanded && (
|
|
72
|
+
<div className="claude-code-tool expanded-content">
|
|
73
|
+
{expandedContent}
|
|
74
|
+
</div>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
};
|