@theia/ai-ide 1.71.0-next.72 → 1.71.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 (41) hide show
  1. package/lib/browser/frontend-module.d.ts.map +1 -1
  2. package/lib/browser/frontend-module.js +8 -0
  3. package/lib/browser/frontend-module.js.map +1 -1
  4. package/lib/browser/review/pr-review-agent.d.ts +19 -0
  5. package/lib/browser/review/pr-review-agent.d.ts.map +1 -0
  6. package/lib/browser/review/pr-review-agent.js +47 -0
  7. package/lib/browser/review/pr-review-agent.js.map +1 -0
  8. package/lib/browser/review/pr-review-prompt-template.d.ts +4 -0
  9. package/lib/browser/review/pr-review-prompt-template.d.ts.map +1 -0
  10. package/lib/browser/review/pr-review-prompt-template.js +437 -0
  11. package/lib/browser/review/pr-review-prompt-template.js.map +1 -0
  12. package/lib/browser/user-interaction-tool-renderer.d.ts +18 -0
  13. package/lib/browser/user-interaction-tool-renderer.d.ts.map +1 -0
  14. package/lib/browser/user-interaction-tool-renderer.js +330 -0
  15. package/lib/browser/user-interaction-tool-renderer.js.map +1 -0
  16. package/lib/browser/user-interaction-tool.d.ts +47 -0
  17. package/lib/browser/user-interaction-tool.d.ts.map +1 -0
  18. package/lib/browser/user-interaction-tool.js +397 -0
  19. package/lib/browser/user-interaction-tool.js.map +1 -0
  20. package/lib/browser/user-interaction-tool.spec.d.ts +2 -0
  21. package/lib/browser/user-interaction-tool.spec.d.ts.map +1 -0
  22. package/lib/browser/user-interaction-tool.spec.js +336 -0
  23. package/lib/browser/user-interaction-tool.spec.js.map +1 -0
  24. package/lib/common/user-interaction-tool.d.ts +53 -0
  25. package/lib/common/user-interaction-tool.d.ts.map +1 -0
  26. package/lib/common/user-interaction-tool.js +176 -0
  27. package/lib/common/user-interaction-tool.js.map +1 -0
  28. package/lib/common/user-interaction-tool.spec.d.ts +2 -0
  29. package/lib/common/user-interaction-tool.spec.d.ts.map +1 -0
  30. package/lib/common/user-interaction-tool.spec.js +216 -0
  31. package/lib/common/user-interaction-tool.spec.js.map +1 -0
  32. package/package.json +22 -22
  33. package/src/browser/frontend-module.ts +9 -0
  34. package/src/browser/review/pr-review-agent.ts +42 -0
  35. package/src/browser/review/pr-review-prompt-template.ts +449 -0
  36. package/src/browser/style/index.css +299 -0
  37. package/src/browser/user-interaction-tool-renderer.tsx +531 -0
  38. package/src/browser/user-interaction-tool.spec.ts +396 -0
  39. package/src/browser/user-interaction-tool.ts +423 -0
  40. package/src/common/user-interaction-tool.spec.ts +241 -0
  41. package/src/common/user-interaction-tool.ts +237 -0
@@ -0,0 +1,531 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2026 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 { inject, injectable } from '@theia/core/shared/inversify';
18
+ import { ChatResponsePartRenderer } from '@theia/ai-chat-ui/lib/browser/chat-response-part-renderer';
19
+ import { ResponseNode } from '@theia/ai-chat-ui/lib/browser/chat-tree-view';
20
+ import { ChatResponseContent, ToolCallChatResponseContent } from '@theia/ai-chat/lib/common';
21
+ import { ReactNode } from '@theia/core/shared/react';
22
+ import * as React from '@theia/core/shared/react';
23
+ import { codicon, ContextMenuRenderer, OpenerService } from '@theia/core/lib/browser';
24
+ import { nls } from '@theia/core/lib/common/nls';
25
+ import { useMarkdownRendering } from '@theia/ai-chat-ui/lib/browser/chat-response-renderer/markdown-part-renderer';
26
+ import { withToolCallConfirmation } from '@theia/ai-chat-ui/lib/browser/chat-response-renderer/tool-confirmation';
27
+ import { ToolConfirmationManager } from '@theia/ai-chat/lib/browser/chat-tool-preference-bindings';
28
+ import { ToolInvocationRegistry } from '@theia/ai-core';
29
+ import { UserInteractionTool } from './user-interaction-tool';
30
+ import {
31
+ USER_INTERACTION_FUNCTION_ID,
32
+ UserInteractionArgs,
33
+ UserInteractionLink,
34
+ UserInteractionResult,
35
+ UserInteractionStep,
36
+ buildDiffLabel,
37
+ isEmptyContentRef,
38
+ parseUserInteractionArgs,
39
+ parseUserInteractionInput,
40
+ parseUserInteractionResult,
41
+ resolveContentRef,
42
+ } from '../common/user-interaction-tool';
43
+
44
+ interface UserInteractionComponentProps {
45
+ args: UserInteractionArgs;
46
+ toolCallId: string;
47
+ tool: UserInteractionTool;
48
+ finished: boolean;
49
+ canceled: boolean;
50
+ /**
51
+ * Whether the parent response has completed (including restoration). When the
52
+ * response is complete but no `result` was persisted, the interaction is treated
53
+ * as canceled because there is no longer a live agent waiting for input.
54
+ */
55
+ responseComplete: boolean;
56
+ result: UserInteractionResult | undefined;
57
+ openerService: OpenerService;
58
+ }
59
+
60
+ interface StepState {
61
+ value?: string;
62
+ comments: string[];
63
+ }
64
+
65
+ const UserInteractionComponent: React.FC<UserInteractionComponentProps> = ({
66
+ args, toolCallId, tool, finished, canceled, responseComplete, result, openerService
67
+ }) => {
68
+ const steps = args.interactions;
69
+ const stepCount = steps.length;
70
+ const [currentStep, setCurrentStep] = React.useState(0);
71
+ const [stepStates, setStepStates] = React.useState<StepState[]>(() => steps.map(() => ({ comments: [] })));
72
+ const [pendingComment, setPendingComment] = React.useState('');
73
+
74
+ const activeStep: UserInteractionStep | undefined = steps[currentStep];
75
+ const isLastStep = currentStep === stepCount - 1;
76
+ const messageRef = useMarkdownRendering(activeStep?.message ?? '', openerService);
77
+
78
+ // A parent response that completed without delivering a tool result means the
79
+ // interaction was restored from a serialized "waiting for input" state. The
80
+ // agent that was waiting is no longer running, so it must be treated as
81
+ // canceled and all inputs locked.
82
+ const restoredWithoutResult = responseComplete && !result;
83
+ const isFinal = finished || !!result || canceled || restoredWithoutResult;
84
+
85
+ // Auto-open the active step's links the first time the user reaches it.
86
+ // Going Back and then Forward must not re-open them.
87
+ const visitedStepsRef = React.useRef<Set<number>>(new Set());
88
+ React.useEffect(() => {
89
+ if (isFinal || !activeStep || visitedStepsRef.current.has(currentStep)) {
90
+ return;
91
+ }
92
+ visitedStepsRef.current.add(currentStep);
93
+ const links = activeStep.links ?? [];
94
+ for (const link of links) {
95
+ if (link.autoOpen !== false) {
96
+ tool.openLink(link).catch(err => console.warn('Failed to auto-open user-interaction link:', err));
97
+ }
98
+ }
99
+ }, [currentStep, activeStep, isFinal, tool]);
100
+
101
+ const persistStepState = React.useCallback((stepIndex: number, state: StepState) => {
102
+ tool.setStepResult(toolCallId, stepIndex, {
103
+ value: state.value,
104
+ comments: state.comments.length > 0 ? state.comments : undefined
105
+ });
106
+ }, [tool, toolCallId]);
107
+
108
+ const updateStepState = React.useCallback((stepIndex: number, updater: (prev: StepState) => StepState) => {
109
+ setStepStates(prev => {
110
+ const next = prev.slice();
111
+ const updated = updater(prev[stepIndex]);
112
+ next[stepIndex] = updated;
113
+ persistStepState(stepIndex, updated);
114
+ return next;
115
+ });
116
+ }, [persistStepState]);
117
+
118
+ const isSingleStep = stepCount === 1;
119
+ const hasOptions = !!activeStep?.options && activeStep.options.length > 0;
120
+
121
+ const handleOptionClick = React.useCallback((value: string) => {
122
+ if (isFinal) {
123
+ return;
124
+ }
125
+ if (isSingleStep) {
126
+ setStepStates(prev => {
127
+ const next = prev.slice();
128
+ next[0] = { ...prev[0], value };
129
+ return next;
130
+ });
131
+ tool.completeInteractionWith(toolCallId, 0, { value });
132
+ return;
133
+ }
134
+ updateStepState(currentStep, prev => ({
135
+ ...prev,
136
+ value: prev.value === value ? undefined : value
137
+ }));
138
+ }, [currentStep, isFinal, isSingleStep, tool, toolCallId, updateStepState]);
139
+
140
+ const handleAddComment = React.useCallback(() => {
141
+ const trimmed = pendingComment.trim();
142
+ if (isFinal || !trimmed) {
143
+ return;
144
+ }
145
+ updateStepState(currentStep, prev => ({
146
+ ...prev,
147
+ comments: [...prev.comments, trimmed]
148
+ }));
149
+ setPendingComment('');
150
+ }, [currentStep, isFinal, pendingComment, updateStepState]);
151
+
152
+ const handleRemoveComment = React.useCallback((commentIndex: number) => {
153
+ if (isFinal) {
154
+ return;
155
+ }
156
+ updateStepState(currentStep, prev => ({
157
+ ...prev,
158
+ comments: prev.comments.filter((_, i) => i !== commentIndex)
159
+ }));
160
+ }, [currentStep, isFinal, updateStepState]);
161
+
162
+ const handleCommentKeyDown = React.useCallback((e: React.KeyboardEvent) => {
163
+ if (e.key === 'Enter') {
164
+ e.preventDefault();
165
+ handleAddComment();
166
+ }
167
+ }, [handleAddComment]);
168
+
169
+ const goToStep = React.useCallback((idx: number) => {
170
+ if (idx < 0 || idx >= stepCount) {
171
+ return;
172
+ }
173
+ setCurrentStep(idx);
174
+ setPendingComment('');
175
+ }, [stepCount]);
176
+
177
+ const handleAdvance = React.useCallback(() => {
178
+ if (isLastStep) {
179
+ if (!isFinal) {
180
+ tool.completeInteraction(toolCallId);
181
+ }
182
+ return;
183
+ }
184
+ setCurrentStep(idx => idx + 1);
185
+ setPendingComment('');
186
+ }, [isFinal, isLastStep, tool, toolCallId]);
187
+
188
+ const handleBack = React.useCallback(() => {
189
+ if (currentStep === 0) {
190
+ return;
191
+ }
192
+ setCurrentStep(idx => idx - 1);
193
+ setPendingComment('');
194
+ }, [currentStep]);
195
+
196
+ if (!activeStep) {
197
+ return undefined;
198
+ }
199
+
200
+ const activeState = stepStates[currentStep];
201
+ const stepLabel = nls.localize('theia/ai-ide/userInteractionStepLabel', 'Step {0} of {1}', currentStep + 1, stepCount);
202
+ const advanceLabel = isLastStep
203
+ ? nls.localize('theia/ai-ide/userInteractionFinishStep', 'Finish')
204
+ : nls.localizeByDefault('Next');
205
+
206
+ const showAdvanceRow = !isSingleStep;
207
+
208
+ return (
209
+ <div className='tool-call container user-interaction-wizard'>
210
+ <div className='tool-call header'>
211
+ <span className={codicon('comment-discussion')} />
212
+ <span className='user-interaction-tool title'>{activeStep.title}</span>
213
+ {(() => {
214
+ // The tool's own result is authoritative: a completed
215
+ // interaction must stay "Completed" even if the chat
216
+ // session is canceled later. A response that completed
217
+ // without a result indicates the interaction was restored
218
+ // from a "waiting" state and is treated as canceled.
219
+ const status: 'completed' | 'canceled' | 'waiting' =
220
+ result?.completed === true ? 'completed' :
221
+ result?.completed === false || canceled || restoredWithoutResult ? 'canceled' :
222
+ 'waiting';
223
+ if (status === 'completed') {
224
+ return (
225
+ <span className='user-interaction-tool status completed'>
226
+ <i className={codicon('check')} />
227
+ {nls.localizeByDefault('Completed')}
228
+ </span>
229
+ );
230
+ }
231
+ if (status === 'canceled') {
232
+ return (
233
+ <span className='user-interaction-tool status canceled'>
234
+ <i className={codicon('close')} />
235
+ {nls.localize('theia/ai-ide/userInteractionCanceled', 'Canceled')}
236
+ </span>
237
+ );
238
+ }
239
+ return (
240
+ <span className='user-interaction-tool status waiting' role='status' aria-live='polite'>
241
+ {nls.localize('theia/ai/chat-ui/chat-view-tree-widget/waitingForInput', 'Waiting for input')}
242
+ </span>
243
+ );
244
+ })()}
245
+ </div>
246
+ {!isSingleStep && <StepProgress current={currentStep} total={stepCount} onSelect={goToStep} steps={steps} />}
247
+ {activeStep.links && activeStep.links.length > 0 && (
248
+ <div className='user-interaction-tool links'>
249
+ {activeStep.links.map((link, i) => (
250
+ <LinkButton
251
+ key={i}
252
+ link={link}
253
+ onClick={() => tool.openLink(link).catch(err => console.warn('Failed to open user-interaction link:', err))}
254
+ />
255
+ ))}
256
+ </div>
257
+ )}
258
+ <div className='user-interaction-tool message' ref={messageRef} />
259
+ {hasOptions && (
260
+ <div className='user-interaction-tool options'>
261
+ {activeStep.options!.map((option, i) => {
262
+ const isSelected = activeState.value === option.value;
263
+ const className = 'user-interaction-tool option-button theia-button '
264
+ + (isSelected ? 'main selected' : 'secondary');
265
+ const label = option.buttonLabel || option.text;
266
+ return (
267
+ <button
268
+ key={i}
269
+ className={className}
270
+ onClick={() => handleOptionClick(option.value)}
271
+ disabled={isFinal}
272
+ title={option.description}
273
+ aria-pressed={isSelected}
274
+ >
275
+ {/* Hidden bold copy reserves the width needed for the bold "selected" state
276
+ so the button does not grow when the label switches to bold. */}
277
+ <span className='user-interaction-tool option-button-bold-spacer' aria-hidden='true'>{label}</span>
278
+ <span className='user-interaction-tool option-button-label'>{label}</span>
279
+ </button>
280
+ );
281
+ })}
282
+ </div>
283
+ )}
284
+ {!isSingleStep && (
285
+ <div className='user-interaction-tool comment-section'>
286
+ {!isFinal && (
287
+ <div className='user-interaction-tool comment-input-row'>
288
+ <input
289
+ type='text'
290
+ className='theia-input user-interaction-tool comment-input-field'
291
+ placeholder={nls.localize('theia/ai-ide/userInteractionCommentPlaceholder', 'Add a comment for this step...')}
292
+ value={pendingComment}
293
+ onChange={e => setPendingComment(e.target.value)}
294
+ onKeyDown={handleCommentKeyDown}
295
+ />
296
+ <button
297
+ className='theia-button secondary user-interaction-tool comment-submit'
298
+ onClick={handleAddComment}
299
+ disabled={!pendingComment.trim()}
300
+ >
301
+ {nls.localizeByDefault('Comment')}
302
+ </button>
303
+ </div>
304
+ )}
305
+ {activeState.comments.length > 0 && (
306
+ <ul className='user-interaction-tool comment-list'>
307
+ {activeState.comments.map((comment, i) => (
308
+ <li key={i} className='user-interaction-tool comment-item'>
309
+ <span className='user-interaction-tool comment-text'>{comment}</span>
310
+ {!isFinal && (
311
+ <button
312
+ className='user-interaction-tool comment-remove'
313
+ onClick={() => handleRemoveComment(i)}
314
+ title={nls.localizeByDefault('Remove')}
315
+ aria-label={nls.localizeByDefault('Remove')}
316
+ >
317
+ <i className={codicon('close')} />
318
+ </button>
319
+ )}
320
+ </li>
321
+ ))}
322
+ </ul>
323
+ )}
324
+ </div>
325
+ )}
326
+ {showAdvanceRow && (
327
+ <div className='user-interaction-tool advance-row'>
328
+ {!isSingleStep && (
329
+ <>
330
+ <button
331
+ className='theia-button secondary user-interaction-tool back-button'
332
+ onClick={handleBack}
333
+ disabled={currentStep === 0}
334
+ title={nls.localizeByDefault('Back')}
335
+ >
336
+ <i className={codicon('arrow-left')} />
337
+ {nls.localizeByDefault('Back')}
338
+ </button>
339
+ <span className='user-interaction-tool page-counter' aria-label={stepLabel}>
340
+ {currentStep + 1} / {stepCount}
341
+ </span>
342
+ </>
343
+ )}
344
+ <button
345
+ className='theia-button main user-interaction-tool advance-button'
346
+ onClick={handleAdvance}
347
+ disabled={isFinal && isLastStep}
348
+ >
349
+ {advanceLabel}
350
+ <i className={codicon(isLastStep ? 'check' : 'arrow-right')} />
351
+ </button>
352
+ </div>
353
+ )}
354
+ </div>
355
+ );
356
+ };
357
+
358
+ const StepProgress: React.FC<{
359
+ current: number;
360
+ total: number;
361
+ onSelect: (index: number) => void;
362
+ steps: UserInteractionStep[];
363
+ }> = ({ current, total, onSelect, steps }) => (
364
+ <div className='user-interaction-tool progress'>
365
+ {Array.from({ length: total }).map((_, i) => {
366
+ const label = nls.localize(
367
+ 'theia/ai-ide/userInteractionGoToStep',
368
+ 'Go to step {0}: {1}',
369
+ i + 1,
370
+ steps[i]?.title ?? ''
371
+ );
372
+ return (
373
+ <button
374
+ key={i}
375
+ type='button'
376
+ className={'user-interaction-tool progress-dot'
377
+ + (i < current ? ' done' : '')
378
+ + (i === current ? ' active' : '')}
379
+ onClick={() => onSelect(i)}
380
+ title={label}
381
+ aria-label={label}
382
+ aria-current={i === current ? 'step' : undefined}
383
+ />
384
+ );
385
+ })}
386
+ </div>
387
+ );
388
+
389
+ const LinkButton: React.FC<{ link: UserInteractionLink; onClick: () => void }> = ({ link, onClick }) => {
390
+ const isDiff = link.rightRef !== undefined;
391
+ const icon = isDiff ? codicon('diff') : codicon('go-to-file');
392
+ const left = resolveContentRef(link.ref);
393
+ let label: string;
394
+ if (link.label) {
395
+ label = link.label;
396
+ } else if (isDiff) {
397
+ label = buildDiffLabel(left, resolveContentRef(link.rightRef!));
398
+ } else {
399
+ label = isEmptyContentRef(left)
400
+ ? (left.label || nls.localize('theia/ai-ide/userInteractionEmpty', 'Empty'))
401
+ : left.path;
402
+ }
403
+
404
+ return (
405
+ <button className='user-interaction-tool link-button' onClick={onClick}>
406
+ <i className={icon} />
407
+ <span>{label}</span>
408
+ </button>
409
+ );
410
+ };
411
+
412
+ const UserInteractionWithConfirmation = withToolCallConfirmation(UserInteractionComponent);
413
+
414
+ const StreamingProgress: React.FC<{ title: string; stepCount: number }> = ({ title, stepCount }) => {
415
+ let label: string;
416
+ if (title && stepCount > 0) {
417
+ label = nls.localize('theia/ai-ide/userInteractionPreparingSteps', 'Preparing: {0} ({1} steps)', title, stepCount);
418
+ } else if (title) {
419
+ label = nls.localize('theia/ai-ide/userInteractionPreparingTitle', 'Preparing: {0}', title);
420
+ } else {
421
+ label = nls.localize('theia/ai-ide/userInteractionPreparing', 'Preparing user interaction...');
422
+ }
423
+ return (
424
+ <div className='tool-call container'>
425
+ <div className='tool-call header pending'>
426
+ <span className={codicon('comment-discussion')} />
427
+ <span className={`${codicon('loading')} theia-animation-spin`} />
428
+ <span className='user-interaction-tool pending-text'>{label}</span>
429
+ </div>
430
+ </div>
431
+ );
432
+ };
433
+
434
+ const MalformedInteraction: React.FC<{ message: string }> = ({ message }) => (
435
+ <div className='tool-call container'>
436
+ <div className='tool-call header error'>
437
+ <span className={codicon('comment-discussion')} />
438
+ <span className='user-interaction-tool title'>
439
+ {nls.localize('theia/ai-ide/userInteractionMalformed', 'User interaction could not be displayed')}
440
+ </span>
441
+ </div>
442
+ <div className='tool-call error-message'>{message}</div>
443
+ </div>
444
+ );
445
+
446
+ interface ToolErrorResult { error: string }
447
+ function parseToolErrorResult(raw: unknown): ToolErrorResult | undefined {
448
+ let candidate: unknown = raw;
449
+ if (typeof raw === 'string') {
450
+ try {
451
+ candidate = JSON.parse(raw);
452
+ } catch {
453
+ return undefined;
454
+ }
455
+ }
456
+ if (candidate && typeof candidate === 'object' && typeof (candidate as { error?: unknown }).error === 'string') {
457
+ return candidate as ToolErrorResult;
458
+ }
459
+ return undefined;
460
+ }
461
+
462
+ @injectable()
463
+ export class UserInteractionToolRenderer implements ChatResponsePartRenderer<ToolCallChatResponseContent> {
464
+
465
+ @inject(ToolConfirmationManager)
466
+ protected toolConfirmationManager: ToolConfirmationManager;
467
+
468
+ @inject(ContextMenuRenderer)
469
+ protected contextMenuRenderer: ContextMenuRenderer;
470
+
471
+ @inject(ToolInvocationRegistry)
472
+ protected toolInvocationRegistry: ToolInvocationRegistry;
473
+
474
+ @inject(UserInteractionTool)
475
+ protected userInteractionTool: UserInteractionTool;
476
+
477
+ @inject(OpenerService)
478
+ protected openerService: OpenerService;
479
+
480
+ canHandle(response: ChatResponseContent): number {
481
+ if (ToolCallChatResponseContent.is(response) && response.name === USER_INTERACTION_FUNCTION_ID) {
482
+ return 20;
483
+ }
484
+ return -1;
485
+ }
486
+
487
+ render(response: ToolCallChatResponseContent, parentNode: ResponseNode): ReactNode {
488
+ const args = parseUserInteractionArgs(response.arguments);
489
+
490
+ if (!args || !response.id) {
491
+ // The tool already returned a result but the args don't validate: this
492
+ // is a malformed invocation (e.g., the agent sent a step the tool
493
+ // rejected, or arguments that fail shared parsing). Show an error state
494
+ // instead of a perpetual loading spinner.
495
+ if (response.result !== undefined) {
496
+ const error = parseToolErrorResult(response.result);
497
+ const message = error?.error
498
+ ?? nls.localize('theia/ai-ide/userInteractionMalformedFallback', 'The arguments could not be parsed.');
499
+ return <MalformedInteraction message={message} />;
500
+ }
501
+ const input = parseUserInteractionInput(response.arguments);
502
+ return <StreamingProgress title={input.title} stepCount={input.stepCount} />;
503
+ }
504
+
505
+ const chatId = parentNode.sessionId;
506
+ const toolRequest = this.toolInvocationRegistry.getFunction(USER_INTERACTION_FUNCTION_ID);
507
+ const confirmationMode = this.toolConfirmationManager.getConfirmationMode(
508
+ USER_INTERACTION_FUNCTION_ID, chatId, toolRequest
509
+ );
510
+
511
+ return (
512
+ <UserInteractionWithConfirmation
513
+ args={args}
514
+ toolCallId={response.id}
515
+ tool={this.userInteractionTool}
516
+ finished={response.finished}
517
+ canceled={parentNode.response.isCanceled}
518
+ responseComplete={parentNode.response.isComplete}
519
+ result={parseUserInteractionResult(response.result)}
520
+ openerService={this.openerService}
521
+ response={response}
522
+ confirmationMode={confirmationMode}
523
+ toolConfirmationManager={this.toolConfirmationManager}
524
+ toolRequest={toolRequest}
525
+ chatId={chatId}
526
+ requestCanceled={parentNode.response.isCanceled}
527
+ contextMenuRenderer={this.contextMenuRenderer}
528
+ />
529
+ );
530
+ }
531
+ }