@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,204 @@
|
|
|
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 { LabelProvider } from '@theia/core/lib/browser';
|
|
21
|
+
import { URI } from '@theia/core/lib/common/uri';
|
|
22
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
23
|
+
import * as React from '@theia/core/shared/react';
|
|
24
|
+
import { ReactNode } from '@theia/core/shared/react';
|
|
25
|
+
import { EditorManager } from '@theia/editor/lib/browser';
|
|
26
|
+
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
27
|
+
import { ClaudeCodeToolCallChatResponseContent } from '../claude-code-tool-call-content';
|
|
28
|
+
import { CollapsibleToolRenderer } from './collapsible-tool-renderer';
|
|
29
|
+
|
|
30
|
+
interface EditOperation {
|
|
31
|
+
old_string: string;
|
|
32
|
+
new_string: string;
|
|
33
|
+
replace_all?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface MultiEditToolInput {
|
|
37
|
+
file_path: string;
|
|
38
|
+
edits: EditOperation[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@injectable()
|
|
42
|
+
export class MultiEditToolRenderer implements ChatResponsePartRenderer<ToolCallChatResponseContent> {
|
|
43
|
+
|
|
44
|
+
@inject(WorkspaceService)
|
|
45
|
+
protected readonly workspaceService: WorkspaceService;
|
|
46
|
+
|
|
47
|
+
@inject(LabelProvider)
|
|
48
|
+
protected readonly labelProvider: LabelProvider;
|
|
49
|
+
|
|
50
|
+
@inject(EditorManager)
|
|
51
|
+
protected readonly editorManager: EditorManager;
|
|
52
|
+
|
|
53
|
+
canHandle(response: ChatResponseContent): number {
|
|
54
|
+
if (ClaudeCodeToolCallChatResponseContent.is(response) && response.name === 'MultiEdit') {
|
|
55
|
+
return 15; // Higher than default ToolCallPartRenderer (10)
|
|
56
|
+
}
|
|
57
|
+
return -1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
render(response: ToolCallChatResponseContent, parentNode: ResponseNode): ReactNode {
|
|
61
|
+
try {
|
|
62
|
+
const input = JSON.parse(response.arguments || '{}') as MultiEditToolInput;
|
|
63
|
+
return <MultiEditToolComponent
|
|
64
|
+
input={input}
|
|
65
|
+
workspaceService={this.workspaceService}
|
|
66
|
+
labelProvider={this.labelProvider}
|
|
67
|
+
editorManager={this.editorManager}
|
|
68
|
+
/>;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.warn('Failed to parse MultiEdit tool input:', error);
|
|
71
|
+
return <div className="claude-code-tool error">Failed to parse MultiEdit tool data</div>;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const MultiEditToolComponent: React.FC<{
|
|
77
|
+
input: MultiEditToolInput;
|
|
78
|
+
workspaceService: WorkspaceService;
|
|
79
|
+
labelProvider: LabelProvider;
|
|
80
|
+
editorManager: EditorManager;
|
|
81
|
+
}> = ({ input, workspaceService, labelProvider, editorManager }) => {
|
|
82
|
+
const getFileName = (filePath: string): string => filePath.split('/').pop() || filePath;
|
|
83
|
+
const getWorkspaceRelativePath = async (filePath: string): Promise<string> => {
|
|
84
|
+
try {
|
|
85
|
+
const absoluteUri = new URI(filePath).parent;
|
|
86
|
+
const workspaceRelativePath = await workspaceService.getWorkspaceRelativePath(absoluteUri);
|
|
87
|
+
return workspaceRelativePath || '';
|
|
88
|
+
} catch {
|
|
89
|
+
return '';
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const getIcon = (filePath: string): string => {
|
|
94
|
+
try {
|
|
95
|
+
const uri = new URI(filePath);
|
|
96
|
+
return labelProvider.getIcon(uri) || 'codicon-file';
|
|
97
|
+
} catch {
|
|
98
|
+
return 'codicon-file';
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const handleOpenFile = async () => {
|
|
103
|
+
try {
|
|
104
|
+
const uri = new URI(input.file_path);
|
|
105
|
+
await editorManager.open(uri);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error('Failed to open file:', error);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const [relativePath, setRelativePath] = React.useState<string>('');
|
|
112
|
+
|
|
113
|
+
React.useEffect(() => {
|
|
114
|
+
getWorkspaceRelativePath(input.file_path).then(setRelativePath);
|
|
115
|
+
}, [input.file_path]);
|
|
116
|
+
|
|
117
|
+
const getChangeInfo = () => {
|
|
118
|
+
let totalOldLines = 0;
|
|
119
|
+
let totalNewLines = 0;
|
|
120
|
+
|
|
121
|
+
input.edits.forEach(edit => {
|
|
122
|
+
totalOldLines += edit.old_string.split('\n').length;
|
|
123
|
+
totalNewLines += edit.new_string.split('\n').length;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return { totalOldLines, totalNewLines };
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const replaceAllCount = input.edits.filter(edit => edit.replace_all).length;
|
|
130
|
+
const totalEdits = input.edits.length;
|
|
131
|
+
|
|
132
|
+
const compactHeader = (
|
|
133
|
+
<>
|
|
134
|
+
<div className="claude-code-tool header-left">
|
|
135
|
+
<span className="claude-code-tool title">Multi-editing</span>
|
|
136
|
+
<span className={`${getIcon(input.file_path)} claude-code-tool icon`} />
|
|
137
|
+
<span
|
|
138
|
+
className="claude-code-tool file-name clickable-element"
|
|
139
|
+
onClick={handleOpenFile}
|
|
140
|
+
title="Click to open file in editor"
|
|
141
|
+
>
|
|
142
|
+
{getFileName(input.file_path)}
|
|
143
|
+
</span>
|
|
144
|
+
{relativePath && <span className="claude-code-tool relative-path" title={relativePath}>{relativePath}</span>}
|
|
145
|
+
</div>
|
|
146
|
+
<div className="claude-code-tool header-right">
|
|
147
|
+
<span className="claude-code-tool badge deleted">-{getChangeInfo().totalOldLines}</span>
|
|
148
|
+
<span className="claude-code-tool badge added">+{getChangeInfo().totalNewLines}</span>
|
|
149
|
+
<span className="claude-code-tool badge">{totalEdits} edit{totalEdits !== 1 ? 's' : ''}</span>
|
|
150
|
+
{replaceAllCount > 0 && (
|
|
151
|
+
<span className="claude-code-tool badge">{replaceAllCount} replace-all</span>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
</>
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const expandedContent = (
|
|
158
|
+
<div className="claude-code-tool details">
|
|
159
|
+
<div className="claude-code-tool detail-row">
|
|
160
|
+
<span className="claude-code-tool detail-label">File Path</span>
|
|
161
|
+
<code className="claude-code-tool detail-value">{input.file_path}</code>
|
|
162
|
+
</div>
|
|
163
|
+
<div className="claude-code-tool detail-row">
|
|
164
|
+
<span className="claude-code-tool detail-label">Total Edits</span>
|
|
165
|
+
<span className="claude-code-tool detail-value">{totalEdits}</span>
|
|
166
|
+
</div>
|
|
167
|
+
{input.edits.map((edit, index) => (
|
|
168
|
+
<div key={index} className="claude-code-tool edit-preview">
|
|
169
|
+
<div className="claude-code-tool edit-preview-header">
|
|
170
|
+
<span className="claude-code-tool edit-preview-title">Edit {index + 1}</span>
|
|
171
|
+
{edit.replace_all && (
|
|
172
|
+
<span className="claude-code-tool edit-preview-badge">
|
|
173
|
+
Replace all
|
|
174
|
+
</span>
|
|
175
|
+
)}
|
|
176
|
+
</div>
|
|
177
|
+
<div className="claude-code-tool detail-row">
|
|
178
|
+
<span className="claude-code-tool detail-label">From</span>
|
|
179
|
+
<pre className="claude-code-tool detail-value code-preview">
|
|
180
|
+
{edit.old_string.length > 100
|
|
181
|
+
? edit.old_string.substring(0, 100) + '...'
|
|
182
|
+
: edit.old_string}
|
|
183
|
+
</pre>
|
|
184
|
+
</div>
|
|
185
|
+
<div className="claude-code-tool detail-row">
|
|
186
|
+
<span className="claude-code-tool detail-label">To</span>
|
|
187
|
+
<pre className="claude-code-tool detail-value code-preview">
|
|
188
|
+
{edit.new_string.length > 100
|
|
189
|
+
? edit.new_string.substring(0, 100) + '...'
|
|
190
|
+
: edit.new_string}
|
|
191
|
+
</pre>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
))}
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<CollapsibleToolRenderer
|
|
200
|
+
compactHeader={compactHeader}
|
|
201
|
+
expandedContent={expandedContent}
|
|
202
|
+
/>
|
|
203
|
+
);
|
|
204
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
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 { LabelProvider } from '@theia/core/lib/browser';
|
|
21
|
+
import { URI } from '@theia/core/lib/common/uri';
|
|
22
|
+
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
23
|
+
import * as React from '@theia/core/shared/react';
|
|
24
|
+
import { ReactNode } from '@theia/core/shared/react';
|
|
25
|
+
import { EditorManager } from '@theia/editor/lib/browser';
|
|
26
|
+
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
27
|
+
import { ClaudeCodeToolCallChatResponseContent } from '../claude-code-tool-call-content';
|
|
28
|
+
import { CollapsibleToolRenderer } from './collapsible-tool-renderer';
|
|
29
|
+
|
|
30
|
+
interface ReadToolInput {
|
|
31
|
+
file_path: string;
|
|
32
|
+
limit?: number;
|
|
33
|
+
offset?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@injectable()
|
|
37
|
+
export class ReadToolRenderer implements ChatResponsePartRenderer<ToolCallChatResponseContent> {
|
|
38
|
+
|
|
39
|
+
@inject(WorkspaceService)
|
|
40
|
+
protected readonly workspaceService: WorkspaceService;
|
|
41
|
+
|
|
42
|
+
@inject(LabelProvider)
|
|
43
|
+
protected readonly labelProvider: LabelProvider;
|
|
44
|
+
|
|
45
|
+
@inject(EditorManager)
|
|
46
|
+
protected readonly editorManager: EditorManager;
|
|
47
|
+
|
|
48
|
+
canHandle(response: ChatResponseContent): number {
|
|
49
|
+
if (ClaudeCodeToolCallChatResponseContent.is(response) && response.name === 'Read') {
|
|
50
|
+
return 15; // Higher than default ToolCallPartRenderer (10)
|
|
51
|
+
}
|
|
52
|
+
return -1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
render(response: ToolCallChatResponseContent, parentNode: ResponseNode): ReactNode {
|
|
56
|
+
try {
|
|
57
|
+
const input = JSON.parse(response.arguments || '{}') as ReadToolInput;
|
|
58
|
+
return <ReadToolComponent
|
|
59
|
+
input={input}
|
|
60
|
+
workspaceService={this.workspaceService}
|
|
61
|
+
labelProvider={this.labelProvider}
|
|
62
|
+
editorManager={this.editorManager}
|
|
63
|
+
/>;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.warn('Failed to parse Read tool input:', error);
|
|
66
|
+
return <div className="claude-code-tool error">Failed to parse Read tool data</div>;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const ReadToolComponent: React.FC<{
|
|
72
|
+
input: ReadToolInput;
|
|
73
|
+
workspaceService: WorkspaceService;
|
|
74
|
+
labelProvider: LabelProvider;
|
|
75
|
+
editorManager: EditorManager;
|
|
76
|
+
}> = ({ input, workspaceService, labelProvider, editorManager }) => {
|
|
77
|
+
const getFileName = (filePath: string): string => filePath.split('/').pop() || filePath;
|
|
78
|
+
const getWorkspaceRelativePath = async (filePath: string): Promise<string> => {
|
|
79
|
+
try {
|
|
80
|
+
const absoluteUri = new URI(filePath).parent;
|
|
81
|
+
const workspaceRelativePath = await workspaceService.getWorkspaceRelativePath(absoluteUri);
|
|
82
|
+
return workspaceRelativePath || '';
|
|
83
|
+
} catch {
|
|
84
|
+
return '';
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const getIcon = (filePath: string): string => {
|
|
89
|
+
try {
|
|
90
|
+
const uri = new URI(filePath);
|
|
91
|
+
return labelProvider.getIcon(uri) || 'codicon-file';
|
|
92
|
+
} catch {
|
|
93
|
+
return 'codicon-file';
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const handleOpenFile = async () => {
|
|
98
|
+
try {
|
|
99
|
+
const uri = new URI(input.file_path);
|
|
100
|
+
await editorManager.open(uri);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error('Failed to open file:', error);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const [relativePath, setRelativePath] = React.useState<string>('');
|
|
107
|
+
|
|
108
|
+
React.useEffect(() => {
|
|
109
|
+
getWorkspaceRelativePath(input.file_path).then(setRelativePath);
|
|
110
|
+
}, [input.file_path]);
|
|
111
|
+
|
|
112
|
+
const isEntireFile = !input.limit && !input.offset;
|
|
113
|
+
|
|
114
|
+
const compactHeader = (
|
|
115
|
+
<>
|
|
116
|
+
<div className="claude-code-tool header-left">
|
|
117
|
+
<span className="claude-code-tool title">Reading</span>
|
|
118
|
+
<span className={`${getIcon(input.file_path)} claude-code-tool icon`} />
|
|
119
|
+
<span
|
|
120
|
+
className="claude-code-tool file-name clickable-element"
|
|
121
|
+
onClick={handleOpenFile}
|
|
122
|
+
title="Click to open file in editor"
|
|
123
|
+
>
|
|
124
|
+
{getFileName(input.file_path)}
|
|
125
|
+
</span>
|
|
126
|
+
{relativePath && <span className="claude-code-tool relative-path">{relativePath}</span>}
|
|
127
|
+
</div>
|
|
128
|
+
<div className="claude-code-tool header-right">
|
|
129
|
+
{isEntireFile && (
|
|
130
|
+
<span className="claude-code-tool badge">Entire File</span>
|
|
131
|
+
)}
|
|
132
|
+
{!isEntireFile && (
|
|
133
|
+
<span className="claude-code-tool badge">Partial</span>
|
|
134
|
+
)}
|
|
135
|
+
</div>
|
|
136
|
+
</>
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const expandedContent = (
|
|
140
|
+
<div className="claude-code-tool details">
|
|
141
|
+
<div className="claude-code-tool detail-row">
|
|
142
|
+
<span className="claude-code-tool detail-label">File Path</span>
|
|
143
|
+
<code className="claude-code-tool detail-value">{input.file_path}</code>
|
|
144
|
+
</div>
|
|
145
|
+
{input.offset && (
|
|
146
|
+
<div className="claude-code-tool detail-row">
|
|
147
|
+
<span className="claude-code-tool detail-label">Starting Line</span>
|
|
148
|
+
<span className="claude-code-tool detail-value">{input.offset}</span>
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
{input.limit && (
|
|
152
|
+
<div className="claude-code-tool detail-row">
|
|
153
|
+
<span className="claude-code-tool detail-label">Line Limit</span>
|
|
154
|
+
<span className="claude-code-tool detail-value">{input.limit}</span>
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
157
|
+
<div className="claude-code-tool detail-row">
|
|
158
|
+
<span className="claude-code-tool detail-label">Read Mode</span>
|
|
159
|
+
<span className="claude-code-tool detail-value">{isEntireFile ? 'Entire file' : 'Partial read'}</span>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<CollapsibleToolRenderer
|
|
166
|
+
compactHeader={compactHeader}
|
|
167
|
+
expandedContent={expandedContent}
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
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 TodoItem {
|
|
28
|
+
id: string;
|
|
29
|
+
content: string;
|
|
30
|
+
status: 'pending' | 'in_progress' | 'completed';
|
|
31
|
+
priority: 'high' | 'medium' | 'low';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface TodoWriteInput {
|
|
35
|
+
todos: TodoItem[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Session-scoped registry to track TodoWrite renderer instances per session
|
|
39
|
+
class TodoWriteRegistry {
|
|
40
|
+
private static sessionInstances: Map<string, Set<() => void>> = new Map();
|
|
41
|
+
|
|
42
|
+
static register(sessionId: string, hideFn: () => void): void {
|
|
43
|
+
// Get or create instances set for this session
|
|
44
|
+
let sessionSet = this.sessionInstances.get(sessionId);
|
|
45
|
+
if (!sessionSet) {
|
|
46
|
+
sessionSet = new Set();
|
|
47
|
+
this.sessionInstances.set(sessionId, sessionSet);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Hide all previous instances in this session
|
|
51
|
+
sessionSet.forEach(fn => fn());
|
|
52
|
+
// Clear the session registry
|
|
53
|
+
sessionSet.clear();
|
|
54
|
+
// Add the new instance
|
|
55
|
+
sessionSet.add(hideFn);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static unregister(sessionId: string, hideFn: () => void): void {
|
|
59
|
+
const sessionSet = this.sessionInstances.get(sessionId);
|
|
60
|
+
if (sessionSet) {
|
|
61
|
+
sessionSet.delete(hideFn);
|
|
62
|
+
// Clean up empty session entries
|
|
63
|
+
if (sessionSet.size === 0) {
|
|
64
|
+
this.sessionInstances.delete(sessionId);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@injectable()
|
|
71
|
+
export class TodoWriteRenderer implements ChatResponsePartRenderer<ToolCallChatResponseContent> {
|
|
72
|
+
|
|
73
|
+
canHandle(response: ChatResponseContent): number {
|
|
74
|
+
if (ClaudeCodeToolCallChatResponseContent.is(response) && response.name === 'TodoWrite') {
|
|
75
|
+
return 15; // Higher than default ToolCallPartRenderer (10)
|
|
76
|
+
}
|
|
77
|
+
return -1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
render(response: ToolCallChatResponseContent, parentNode: ResponseNode): ReactNode {
|
|
81
|
+
try {
|
|
82
|
+
const input = JSON.parse(response.arguments || '{}') as TodoWriteInput;
|
|
83
|
+
return <TodoListComponent todos={input.todos || []} sessionId={parentNode.sessionId} />;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.warn('Failed to parse TodoWrite input:', error);
|
|
86
|
+
return <div className="claude-code-tool todo-list-error">Failed to parse todo list data</div>;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const TodoListComponent: React.FC<{ todos: TodoItem[]; sessionId: string }> = ({ todos, sessionId }) => {
|
|
92
|
+
const [isHidden, setIsHidden] = React.useState(false);
|
|
93
|
+
|
|
94
|
+
React.useEffect(() => {
|
|
95
|
+
const hideFn = () => setIsHidden(true);
|
|
96
|
+
TodoWriteRegistry.register(sessionId, hideFn);
|
|
97
|
+
|
|
98
|
+
return () => {
|
|
99
|
+
TodoWriteRegistry.unregister(sessionId, hideFn);
|
|
100
|
+
};
|
|
101
|
+
}, [sessionId]);
|
|
102
|
+
|
|
103
|
+
if (isHidden) {
|
|
104
|
+
// eslint-disable-next-line no-null/no-null
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const getStatusIcon = (status: TodoItem['status']) => {
|
|
108
|
+
switch (status) {
|
|
109
|
+
case 'completed':
|
|
110
|
+
return <span className={`${codicon('check')} claude-code-tool todo-status-icon completed`} />;
|
|
111
|
+
case 'in_progress':
|
|
112
|
+
return <span className={`${codicon('loading')} claude-code-tool todo-status-icon in-progress theia-animation-spin`} />;
|
|
113
|
+
case 'pending':
|
|
114
|
+
default:
|
|
115
|
+
return <span className={`${codicon('circle-outline')} claude-code-tool todo-status-icon pending`} />;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const getPriorityBadge = (priority: TodoItem['priority']) => (
|
|
120
|
+
<span className={`claude-code-tool todo-priority priority-${priority}`}>{priority}</span>
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (!todos || todos.length === 0) {
|
|
124
|
+
return (
|
|
125
|
+
<div className="claude-code-tool todo-list-container">
|
|
126
|
+
<div className="claude-code-tool todo-list-header">
|
|
127
|
+
<span className={`${codicon('checklist')} claude-code-tool todo-list-icon`} />
|
|
128
|
+
<span className="claude-code-tool todo-list-title">Todo List</span>
|
|
129
|
+
</div>
|
|
130
|
+
<div className="claude-code-tool todo-list-empty">No todos available</div>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const completedCount = todos.filter(todo => todo.status === 'completed').length;
|
|
136
|
+
const totalCount = todos.length;
|
|
137
|
+
|
|
138
|
+
const compactHeader = (
|
|
139
|
+
<>
|
|
140
|
+
<div className="claude-code-tool header-left">
|
|
141
|
+
<span className="claude-code-tool title">Todo List</span>
|
|
142
|
+
<span className={`${codicon('checklist')} claude-code-tool icon`} />
|
|
143
|
+
<span className="claude-code-tool progress-text">{completedCount}/{totalCount} completed</span>
|
|
144
|
+
</div>
|
|
145
|
+
<div className="claude-code-tool header-right">
|
|
146
|
+
<span className="claude-code-tool badge">{totalCount} item{totalCount !== 1 ? 's' : ''}</span>
|
|
147
|
+
</div>
|
|
148
|
+
</>
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const expandedContent = (
|
|
152
|
+
<div className="claude-code-tool details">
|
|
153
|
+
<div className="claude-code-tool todo-list-items">
|
|
154
|
+
{todos.map(todo => (
|
|
155
|
+
<div key={todo.id || todo.content} className={`claude-code-tool todo-item status-${todo.status}`}>
|
|
156
|
+
<div className="claude-code-tool todo-item-main">
|
|
157
|
+
<div className="claude-code-tool todo-item-status">
|
|
158
|
+
{getStatusIcon(todo.status)}
|
|
159
|
+
</div>
|
|
160
|
+
<div className="claude-code-tool todo-item-content">
|
|
161
|
+
<span className="claude-code-tool todo-item-text">{todo.content}</span>
|
|
162
|
+
{getPriorityBadge(todo.priority)}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
))}
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<CollapsibleToolRenderer
|
|
173
|
+
compactHeader={compactHeader}
|
|
174
|
+
expandedContent={expandedContent}
|
|
175
|
+
defaultExpanded={true}
|
|
176
|
+
/>
|
|
177
|
+
);
|
|
178
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
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 WebFetchToolInput {
|
|
28
|
+
url: string;
|
|
29
|
+
prompt: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@injectable()
|
|
33
|
+
export class WebFetchToolRenderer implements ChatResponsePartRenderer<ToolCallChatResponseContent> {
|
|
34
|
+
|
|
35
|
+
canHandle(response: ChatResponseContent): number {
|
|
36
|
+
if (ClaudeCodeToolCallChatResponseContent.is(response) && response.name === 'WebFetch') {
|
|
37
|
+
return 15; // Higher than default ToolCallPartRenderer (10)
|
|
38
|
+
}
|
|
39
|
+
return -1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
render(response: ToolCallChatResponseContent, parentNode: ResponseNode): ReactNode {
|
|
43
|
+
try {
|
|
44
|
+
const input = JSON.parse(response.arguments || '{}') as WebFetchToolInput;
|
|
45
|
+
return <WebFetchToolComponent input={input} />;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.warn('Failed to parse WebFetch tool input:', error);
|
|
48
|
+
return <div className="claude-code-tool error">Failed to parse WebFetch tool data</div>;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const WebFetchToolComponent: React.FC<{
|
|
54
|
+
input: WebFetchToolInput;
|
|
55
|
+
}> = ({ input }) => {
|
|
56
|
+
const getDomain = (url: string): string => {
|
|
57
|
+
try {
|
|
58
|
+
return new URL(url).hostname;
|
|
59
|
+
} catch {
|
|
60
|
+
return url;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const truncatePrompt = (prompt: string, maxLength: number = 100): string => {
|
|
65
|
+
if (prompt.length <= maxLength) { return prompt; }
|
|
66
|
+
return prompt.substring(0, maxLength) + '...';
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const compactHeader = (
|
|
70
|
+
<>
|
|
71
|
+
<div className="claude-code-tool header-left">
|
|
72
|
+
<span className="claude-code-tool title">Fetching</span>
|
|
73
|
+
<span className={`${codicon('globe')} claude-code-tool icon`} />
|
|
74
|
+
<span className="claude-code-tool command">{getDomain(input.url)}</span>
|
|
75
|
+
<span className="claude-code-tool description" title={input.prompt}>
|
|
76
|
+
{truncatePrompt(input.prompt)}
|
|
77
|
+
</span>
|
|
78
|
+
</div>
|
|
79
|
+
<div className="claude-code-tool header-right">
|
|
80
|
+
<span className="claude-code-tool badge">Web Fetch</span>
|
|
81
|
+
</div>
|
|
82
|
+
</>
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const expandedContent = (
|
|
86
|
+
<div className="claude-code-tool details">
|
|
87
|
+
<div className="claude-code-tool detail-row">
|
|
88
|
+
<span className="claude-code-tool detail-label">URL</span>
|
|
89
|
+
<code className="claude-code-tool detail-value">{input.url}</code>
|
|
90
|
+
</div>
|
|
91
|
+
<div className="claude-code-tool detail-row">
|
|
92
|
+
<span className="claude-code-tool detail-label">Domain</span>
|
|
93
|
+
<span className="claude-code-tool detail-value">{getDomain(input.url)}</span>
|
|
94
|
+
</div>
|
|
95
|
+
<div className="claude-code-tool detail-row">
|
|
96
|
+
<span className="claude-code-tool detail-label">Prompt</span>
|
|
97
|
+
<span className="claude-code-tool detail-value">{input.prompt}</span>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<CollapsibleToolRenderer
|
|
104
|
+
compactHeader={compactHeader}
|
|
105
|
+
expandedContent={expandedContent}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
};
|