@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.
Files changed (128) hide show
  1. package/README.md +30 -0
  2. package/lib/browser/claude-code-chat-agent.d.ts +82 -0
  3. package/lib/browser/claude-code-chat-agent.d.ts.map +1 -0
  4. package/lib/browser/claude-code-chat-agent.js +518 -0
  5. package/lib/browser/claude-code-chat-agent.js.map +1 -0
  6. package/lib/browser/claude-code-command-contribution.d.ts +16 -0
  7. package/lib/browser/claude-code-command-contribution.d.ts.map +1 -0
  8. package/lib/browser/claude-code-command-contribution.js +86 -0
  9. package/lib/browser/claude-code-command-contribution.js.map +1 -0
  10. package/lib/browser/claude-code-edit-tool-service.d.ts +61 -0
  11. package/lib/browser/claude-code-edit-tool-service.d.ts.map +1 -0
  12. package/lib/browser/claude-code-edit-tool-service.js +219 -0
  13. package/lib/browser/claude-code-edit-tool-service.js.map +1 -0
  14. package/lib/browser/claude-code-file-edit-backup-service.d.ts +57 -0
  15. package/lib/browser/claude-code-file-edit-backup-service.d.ts.map +1 -0
  16. package/lib/browser/claude-code-file-edit-backup-service.js +92 -0
  17. package/lib/browser/claude-code-file-edit-backup-service.js.map +1 -0
  18. package/lib/browser/claude-code-frontend-module.d.ts +5 -0
  19. package/lib/browser/claude-code-frontend-module.d.ts.map +1 -0
  20. package/lib/browser/claude-code-frontend-module.js +83 -0
  21. package/lib/browser/claude-code-frontend-module.js.map +1 -0
  22. package/lib/browser/claude-code-frontend-service.d.ts +40 -0
  23. package/lib/browser/claude-code-frontend-service.d.ts.map +1 -0
  24. package/lib/browser/claude-code-frontend-service.js +190 -0
  25. package/lib/browser/claude-code-frontend-service.js.map +1 -0
  26. package/lib/browser/claude-code-slash-commands-contribution.d.ts +17 -0
  27. package/lib/browser/claude-code-slash-commands-contribution.d.ts.map +1 -0
  28. package/lib/browser/claude-code-slash-commands-contribution.js +154 -0
  29. package/lib/browser/claude-code-slash-commands-contribution.js.map +1 -0
  30. package/lib/browser/claude-code-tool-call-content.d.ts +8 -0
  31. package/lib/browser/claude-code-tool-call-content.d.ts.map +1 -0
  32. package/lib/browser/claude-code-tool-call-content.js +30 -0
  33. package/lib/browser/claude-code-tool-call-content.js.map +1 -0
  34. package/lib/browser/renderers/bash-tool-renderer.d.ts +10 -0
  35. package/lib/browser/renderers/bash-tool-renderer.d.ts.map +1 -0
  36. package/lib/browser/renderers/bash-tool-renderer.js +71 -0
  37. package/lib/browser/renderers/bash-tool-renderer.js.map +1 -0
  38. package/lib/browser/renderers/collapsible-tool-renderer.d.ts +13 -0
  39. package/lib/browser/renderers/collapsible-tool-renderer.d.ts.map +1 -0
  40. package/lib/browser/renderers/collapsible-tool-renderer.js +48 -0
  41. package/lib/browser/renderers/collapsible-tool-renderer.js.map +1 -0
  42. package/lib/browser/renderers/edit-tool-renderer.d.ts +16 -0
  43. package/lib/browser/renderers/edit-tool-renderer.d.ts.map +1 -0
  44. package/lib/browser/renderers/edit-tool-renderer.js +134 -0
  45. package/lib/browser/renderers/edit-tool-renderer.js.map +1 -0
  46. package/lib/browser/renderers/glob-tool-renderer.d.ts +14 -0
  47. package/lib/browser/renderers/glob-tool-renderer.d.ts.map +1 -0
  48. package/lib/browser/renderers/glob-tool-renderer.js +107 -0
  49. package/lib/browser/renderers/glob-tool-renderer.js.map +1 -0
  50. package/lib/browser/renderers/grep-tool-renderer.d.ts +14 -0
  51. package/lib/browser/renderers/grep-tool-renderer.d.ts.map +1 -0
  52. package/lib/browser/renderers/grep-tool-renderer.js +157 -0
  53. package/lib/browser/renderers/grep-tool-renderer.js.map +1 -0
  54. package/lib/browser/renderers/ls-tool-renderer.d.ts +16 -0
  55. package/lib/browser/renderers/ls-tool-renderer.d.ts.map +1 -0
  56. package/lib/browser/renderers/ls-tool-renderer.js +116 -0
  57. package/lib/browser/renderers/ls-tool-renderer.js.map +1 -0
  58. package/lib/browser/renderers/multiedit-tool-renderer.d.ts +16 -0
  59. package/lib/browser/renderers/multiedit-tool-renderer.d.ts.map +1 -0
  60. package/lib/browser/renderers/multiedit-tool-renderer.js +152 -0
  61. package/lib/browser/renderers/multiedit-tool-renderer.js.map +1 -0
  62. package/lib/browser/renderers/read-tool-renderer.d.ts +16 -0
  63. package/lib/browser/renderers/read-tool-renderer.d.ts.map +1 -0
  64. package/lib/browser/renderers/read-tool-renderer.js +121 -0
  65. package/lib/browser/renderers/read-tool-renderer.js.map +1 -0
  66. package/lib/browser/renderers/todo-write-renderer.d.ts +10 -0
  67. package/lib/browser/renderers/todo-write-renderer.d.ts.map +1 -0
  68. package/lib/browser/renderers/todo-write-renderer.js +132 -0
  69. package/lib/browser/renderers/todo-write-renderer.js.map +1 -0
  70. package/lib/browser/renderers/web-fetch-tool-renderer.d.ts +10 -0
  71. package/lib/browser/renderers/web-fetch-tool-renderer.d.ts.map +1 -0
  72. package/lib/browser/renderers/web-fetch-tool-renderer.js +82 -0
  73. package/lib/browser/renderers/web-fetch-tool-renderer.js.map +1 -0
  74. package/lib/browser/renderers/write-tool-renderer.d.ts +16 -0
  75. package/lib/browser/renderers/write-tool-renderer.d.ts.map +1 -0
  76. package/lib/browser/renderers/write-tool-renderer.js +113 -0
  77. package/lib/browser/renderers/write-tool-renderer.js.map +1 -0
  78. package/lib/common/claude-code-preferences.d.ts +4 -0
  79. package/lib/common/claude-code-preferences.d.ts.map +1 -0
  80. package/lib/common/claude-code-preferences.js +33 -0
  81. package/lib/common/claude-code-preferences.js.map +1 -0
  82. package/lib/common/claude-code-service.d.ts +231 -0
  83. package/lib/common/claude-code-service.d.ts.map +1 -0
  84. package/lib/common/claude-code-service.js +82 -0
  85. package/lib/common/claude-code-service.js.map +1 -0
  86. package/lib/common/index.d.ts +2 -0
  87. package/lib/common/index.d.ts.map +1 -0
  88. package/lib/common/index.js +20 -0
  89. package/lib/common/index.js.map +1 -0
  90. package/lib/node/claude-code-backend-module.d.ts +4 -0
  91. package/lib/node/claude-code-backend-module.d.ts.map +1 -0
  92. package/lib/node/claude-code-backend-module.js +35 -0
  93. package/lib/node/claude-code-backend-module.js.map +1 -0
  94. package/lib/node/claude-code-service-impl.d.ts +32 -0
  95. package/lib/node/claude-code-service-impl.d.ts.map +1 -0
  96. package/lib/node/claude-code-service-impl.js +426 -0
  97. package/lib/node/claude-code-service-impl.js.map +1 -0
  98. package/lib/package.spec.d.ts +1 -0
  99. package/lib/package.spec.d.ts.map +1 -0
  100. package/lib/package.spec.js +26 -0
  101. package/lib/package.spec.js.map +1 -0
  102. package/package.json +57 -0
  103. package/src/browser/claude-code-chat-agent.ts +591 -0
  104. package/src/browser/claude-code-command-contribution.ts +80 -0
  105. package/src/browser/claude-code-edit-tool-service.ts +313 -0
  106. package/src/browser/claude-code-file-edit-backup-service.ts +141 -0
  107. package/src/browser/claude-code-frontend-module.ts +100 -0
  108. package/src/browser/claude-code-frontend-service.ts +215 -0
  109. package/src/browser/claude-code-slash-commands-contribution.ts +175 -0
  110. package/src/browser/claude-code-tool-call-content.ts +30 -0
  111. package/src/browser/renderers/bash-tool-renderer.tsx +97 -0
  112. package/src/browser/renderers/collapsible-tool-renderer.tsx +78 -0
  113. package/src/browser/renderers/edit-tool-renderer.tsx +180 -0
  114. package/src/browser/renderers/glob-tool-renderer.tsx +136 -0
  115. package/src/browser/renderers/grep-tool-renderer.tsx +190 -0
  116. package/src/browser/renderers/ls-tool-renderer.tsx +160 -0
  117. package/src/browser/renderers/multiedit-tool-renderer.tsx +204 -0
  118. package/src/browser/renderers/read-tool-renderer.tsx +170 -0
  119. package/src/browser/renderers/todo-write-renderer.tsx +178 -0
  120. package/src/browser/renderers/web-fetch-tool-renderer.tsx +108 -0
  121. package/src/browser/renderers/write-tool-renderer.tsx +155 -0
  122. package/src/browser/style/claude-code-tool-renderers.css +487 -0
  123. package/src/common/claude-code-preferences.ts +33 -0
  124. package/src/common/claude-code-service.ts +303 -0
  125. package/src/common/index.ts +17 -0
  126. package/src/node/claude-code-backend-module.ts +42 -0
  127. package/src/node/claude-code-service-impl.ts +462 -0
  128. 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
+ };