@vybestack/llxprt-code 0.1.21 → 0.1.22
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/dist/package.json +3 -3
- package/dist/src/commands/mcp/add.js +11 -0
- package/dist/src/commands/mcp/add.js.map +1 -1
- package/dist/src/config/keyBindings.js +1 -0
- package/dist/src/config/keyBindings.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +1 -1
- package/dist/src/generated/git-commit.js +1 -1
- package/dist/src/integration-tests/ephemeral-settings.integration.test.js +47 -19
- package/dist/src/integration-tests/ephemeral-settings.integration.test.js.map +1 -1
- package/dist/src/nonInteractiveCli.js +2 -5
- package/dist/src/nonInteractiveCli.js.map +1 -1
- package/dist/src/services/FileCommandLoader.js +10 -8
- package/dist/src/services/FileCommandLoader.js.map +1 -1
- package/dist/src/services/prompt-processors/argumentProcessor.d.ts +2 -7
- package/dist/src/services/prompt-processors/argumentProcessor.js +2 -10
- package/dist/src/services/prompt-processors/argumentProcessor.js.map +1 -1
- package/dist/src/services/prompt-processors/shellProcessor.d.ts +16 -13
- package/dist/src/services/prompt-processors/shellProcessor.js +133 -40
- package/dist/src/services/prompt-processors/shellProcessor.js.map +1 -1
- package/dist/src/services/prompt-processors/types.d.ts +2 -0
- package/dist/src/services/prompt-processors/types.js +2 -0
- package/dist/src/services/prompt-processors/types.js.map +1 -1
- package/dist/src/test-utils/render.d.ts +8 -0
- package/dist/src/test-utils/render.js +10 -0
- package/dist/src/test-utils/render.js.map +1 -0
- package/dist/src/ui/App.js +6 -5
- package/dist/src/ui/App.js.map +1 -1
- package/dist/src/ui/colors.js +25 -2
- package/dist/src/ui/colors.js.map +1 -1
- package/dist/src/ui/commands/aboutCommand.js +3 -0
- package/dist/src/ui/commands/aboutCommand.js.map +1 -1
- package/dist/src/ui/commands/ideCommand.js +2 -2
- package/dist/src/ui/commands/ideCommand.js.map +1 -1
- package/dist/src/ui/commands/setCommand.js +41 -0
- package/dist/src/ui/commands/setCommand.js.map +1 -1
- package/dist/src/ui/commands/setCommand.test.js +4 -4
- package/dist/src/ui/commands/setCommand.test.js.map +1 -1
- package/dist/src/ui/components/AboutBox.d.ts +1 -0
- package/dist/src/ui/components/AboutBox.js +1 -1
- package/dist/src/ui/components/AboutBox.js.map +1 -1
- package/dist/src/ui/components/AuthDialog.test.js +11 -11
- package/dist/src/ui/components/AuthDialog.test.js.map +1 -1
- package/dist/src/ui/components/FolderTrustDialog.test.js +3 -3
- package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.js +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.test.js +1 -0
- package/dist/src/ui/components/HistoryItemDisplay.test.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.js +0 -4
- package/dist/src/ui/components/InputPrompt.js.map +1 -1
- package/dist/src/ui/components/SettingsDialog.test.js +39 -39
- package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
- package/dist/src/ui/components/ShellConfirmationDialog.js +2 -1
- package/dist/src/ui/components/ShellConfirmationDialog.js.map +1 -1
- package/dist/src/ui/components/ShellConfirmationDialog.test.js +5 -5
- package/dist/src/ui/components/ShellConfirmationDialog.test.js.map +1 -1
- package/dist/src/ui/components/messages/DiffRenderer.js +7 -3
- package/dist/src/ui/components/messages/DiffRenderer.js.map +1 -1
- package/dist/src/ui/components/messages/DiffRenderer.test.js +27 -0
- package/dist/src/ui/components/messages/DiffRenderer.test.js.map +1 -1
- package/dist/src/ui/components/messages/InfoMessage.js +2 -1
- package/dist/src/ui/components/messages/InfoMessage.js.map +1 -1
- package/dist/src/ui/components/messages/ToolConfirmationMessage.js +2 -1
- package/dist/src/ui/components/messages/ToolConfirmationMessage.js.map +1 -1
- package/dist/src/ui/components/messages/ToolConfirmationMessage.responsive.test.js +15 -14
- package/dist/src/ui/components/messages/ToolConfirmationMessage.responsive.test.js.map +1 -1
- package/dist/src/ui/components/messages/ToolConfirmationMessage.test.js +3 -3
- package/dist/src/ui/components/messages/ToolConfirmationMessage.test.js.map +1 -1
- package/dist/src/ui/components/shared/RadioButtonSelect.test.js +14 -13
- package/dist/src/ui/components/shared/RadioButtonSelect.test.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.d.ts +30 -0
- package/dist/src/ui/contexts/KeypressContext.js +314 -0
- package/dist/src/ui/contexts/KeypressContext.js.map +1 -0
- package/dist/src/ui/contexts/KeypressContext.test.d.ts +6 -0
- package/dist/src/ui/contexts/KeypressContext.test.js +220 -0
- package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -0
- package/dist/src/ui/hooks/index.d.ts +1 -0
- package/dist/src/ui/hooks/index.js +1 -0
- package/dist/src/ui/hooks/index.js.map +1 -1
- package/dist/src/ui/hooks/slashCommandProcessor.js +13 -7
- package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
- package/dist/src/ui/hooks/useAtCompletion.js +3 -5
- package/dist/src/ui/hooks/useAtCompletion.js.map +1 -1
- package/dist/src/ui/hooks/useKeypress.d.ts +4 -24
- package/dist/src/ui/hooks/useKeypress.js +9 -330
- package/dist/src/ui/hooks/useKeypress.js.map +1 -1
- package/dist/src/ui/hooks/useKeypress.test.js +5 -0
- package/dist/src/ui/hooks/useKeypress.test.js.map +1 -1
- package/dist/src/ui/hooks/usePhraseCycler.js +1 -0
- package/dist/src/ui/hooks/usePhraseCycler.js.map +1 -1
- package/dist/src/ui/hooks/useToolScheduler.test.d.ts +6 -0
- package/dist/src/ui/hooks/useToolScheduler.test.js +841 -0
- package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -0
- package/dist/src/ui/themes/semantic-resolver.js +16 -7
- package/dist/src/ui/themes/semantic-resolver.js.map +1 -1
- package/dist/src/ui/themes/semantic-resolver.test.js +68 -32
- package/dist/src/ui/themes/semantic-resolver.test.js.map +1 -1
- package/dist/src/ui/themes/semantic-tokens.d.ts +18 -33
- package/dist/src/ui/themes/semantic-tokens.js +88 -1
- package/dist/src/ui/themes/semantic-tokens.js.map +1 -1
- package/dist/src/ui/themes/semantic-tokens.test.js +22 -5
- package/dist/src/ui/themes/semantic-tokens.test.js.map +1 -1
- package/dist/src/ui/themes/theme-compat.d.ts +1 -1
- package/dist/src/ui/themes/theme-compat.js +7 -2
- package/dist/src/ui/themes/theme-compat.js.map +1 -1
- package/dist/src/ui/themes/theme-manager.test.js +10 -1
- package/dist/src/ui/themes/theme-manager.test.js.map +1 -1
- package/dist/src/ui/types.d.ts +12 -1
- package/dist/src/ui/types.js +1 -0
- package/dist/src/ui/types.js.map +1 -1
- package/dist/src/ui/utils/InlineMarkdownRenderer.js +8 -1
- package/dist/src/ui/utils/InlineMarkdownRenderer.js.map +1 -1
- package/dist/src/ui/utils/platformConstants.d.ts +5 -0
- package/dist/src/ui/utils/platformConstants.js +5 -0
- package/dist/src/ui/utils/platformConstants.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
@@ -0,0 +1,841 @@
|
|
1
|
+
/**
|
2
|
+
* @license
|
3
|
+
* Copyright 2025 Google LLC
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
5
|
+
*/
|
6
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
7
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
8
|
+
import { renderHook, act } from '@testing-library/react';
|
9
|
+
import { useReactToolScheduler, mapToDisplay, } from './useReactToolScheduler.js';
|
10
|
+
import { ToolConfirmationOutcome, ApprovalMode, Kind, BaseDeclarativeTool, BaseToolInvocation, } from '@vybestack/llxprt-code-core';
|
11
|
+
import { ToolCallStatus, } from '../types.js';
|
12
|
+
// Mocks
|
13
|
+
vi.mock('@google/gemini-cli-core', async () => {
|
14
|
+
const actual = await vi.importActual('@google/gemini-cli-core');
|
15
|
+
return {
|
16
|
+
...actual,
|
17
|
+
ToolRegistry: vi.fn(),
|
18
|
+
Config: vi.fn(),
|
19
|
+
};
|
20
|
+
});
|
21
|
+
const mockToolRegistry = {
|
22
|
+
getTool: vi.fn(),
|
23
|
+
};
|
24
|
+
const mockConfig = {
|
25
|
+
getToolRegistry: vi.fn(() => mockToolRegistry),
|
26
|
+
getApprovalMode: vi.fn(() => ApprovalMode.DEFAULT),
|
27
|
+
getUsageStatisticsEnabled: () => true,
|
28
|
+
getDebugMode: () => false,
|
29
|
+
};
|
30
|
+
class MockToolInvocation extends BaseToolInvocation {
|
31
|
+
tool;
|
32
|
+
constructor(tool, params) {
|
33
|
+
super(params);
|
34
|
+
this.tool = tool;
|
35
|
+
}
|
36
|
+
getDescription() {
|
37
|
+
return JSON.stringify(this.params);
|
38
|
+
}
|
39
|
+
shouldConfirmExecute(abortSignal) {
|
40
|
+
return this.tool.shouldConfirmExecute(this.params, abortSignal);
|
41
|
+
}
|
42
|
+
execute(signal, updateOutput, terminalColumns, terminalRows) {
|
43
|
+
return this.tool.execute(this.params, signal, updateOutput, terminalColumns, terminalRows);
|
44
|
+
}
|
45
|
+
}
|
46
|
+
class MockTool extends BaseDeclarativeTool {
|
47
|
+
constructor(name, displayName, canUpdateOutput = false, shouldConfirm = false, isOutputMarkdown = false) {
|
48
|
+
super(name, displayName, 'A mock tool for testing', Kind.Other, {}, isOutputMarkdown, canUpdateOutput);
|
49
|
+
if (shouldConfirm) {
|
50
|
+
this.shouldConfirmExecute.mockImplementation(async () => ({
|
51
|
+
type: 'edit',
|
52
|
+
title: 'Mock Tool Requires Confirmation',
|
53
|
+
onConfirm: mockOnUserConfirmForToolConfirmation,
|
54
|
+
filePath: 'mock',
|
55
|
+
fileName: 'mockToolRequiresConfirmation.ts',
|
56
|
+
fileDiff: 'Mock tool requires confirmation',
|
57
|
+
originalContent: 'Original content',
|
58
|
+
newContent: 'New content',
|
59
|
+
}));
|
60
|
+
}
|
61
|
+
}
|
62
|
+
execute = vi.fn();
|
63
|
+
shouldConfirmExecute = vi.fn();
|
64
|
+
createInvocation(params) {
|
65
|
+
return new MockToolInvocation(this, params);
|
66
|
+
}
|
67
|
+
}
|
68
|
+
const mockTool = new MockTool('mockTool', 'Mock Tool');
|
69
|
+
const mockToolWithLiveOutput = new MockTool('mockToolWithLiveOutput', 'Mock Tool With Live Output', true);
|
70
|
+
let mockOnUserConfirmForToolConfirmation;
|
71
|
+
const mockToolRequiresConfirmation = new MockTool('mockToolRequiresConfirmation', 'Mock Tool Requires Confirmation', false, true);
|
72
|
+
describe('useReactToolScheduler in YOLO Mode', () => {
|
73
|
+
let onComplete;
|
74
|
+
let setPendingHistoryItem;
|
75
|
+
beforeEach(() => {
|
76
|
+
onComplete = vi.fn();
|
77
|
+
setPendingHistoryItem = vi.fn();
|
78
|
+
mockToolRegistry.getTool.mockClear();
|
79
|
+
mockToolRequiresConfirmation.execute.mockClear();
|
80
|
+
mockToolRequiresConfirmation.shouldConfirmExecute.mockClear();
|
81
|
+
// IMPORTANT: Enable YOLO mode for this test suite
|
82
|
+
mockConfig.getApprovalMode.mockReturnValue(ApprovalMode.YOLO);
|
83
|
+
vi.useFakeTimers();
|
84
|
+
});
|
85
|
+
afterEach(() => {
|
86
|
+
vi.clearAllTimers();
|
87
|
+
vi.useRealTimers();
|
88
|
+
// IMPORTANT: Disable YOLO mode after this test suite
|
89
|
+
mockConfig.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
|
90
|
+
});
|
91
|
+
const renderSchedulerInYoloMode = () => renderHook(() => useReactToolScheduler(onComplete, mockConfig, setPendingHistoryItem, () => undefined, () => { }));
|
92
|
+
it('should skip confirmation and execute tool directly when yoloMode is true', async () => {
|
93
|
+
mockToolRegistry.getTool.mockReturnValue(mockToolRequiresConfirmation);
|
94
|
+
const expectedOutput = 'YOLO Confirmed output';
|
95
|
+
mockToolRequiresConfirmation.execute.mockResolvedValue({
|
96
|
+
llmContent: expectedOutput,
|
97
|
+
returnDisplay: 'YOLO Formatted tool output',
|
98
|
+
summary: 'YOLO summary',
|
99
|
+
});
|
100
|
+
const { result } = renderSchedulerInYoloMode();
|
101
|
+
const schedule = result.current[1];
|
102
|
+
const request = {
|
103
|
+
callId: 'yoloCall',
|
104
|
+
name: 'mockToolRequiresConfirmation',
|
105
|
+
args: { data: 'any data' },
|
106
|
+
};
|
107
|
+
act(() => {
|
108
|
+
schedule(request, new AbortController().signal);
|
109
|
+
});
|
110
|
+
await act(async () => {
|
111
|
+
await vi.runAllTimersAsync(); // Process validation
|
112
|
+
});
|
113
|
+
await act(async () => {
|
114
|
+
await vi.runAllTimersAsync(); // Process scheduling
|
115
|
+
});
|
116
|
+
await act(async () => {
|
117
|
+
await vi.runAllTimersAsync(); // Process execution
|
118
|
+
});
|
119
|
+
// Check that shouldConfirmExecute was NOT called
|
120
|
+
expect(mockToolRequiresConfirmation.shouldConfirmExecute).not.toHaveBeenCalled();
|
121
|
+
// Check that execute WAS called
|
122
|
+
expect(mockToolRequiresConfirmation.execute).toHaveBeenCalledWith(request.args, expect.any(AbortSignal), undefined, undefined, undefined);
|
123
|
+
// Check that onComplete was called with success
|
124
|
+
expect(onComplete).toHaveBeenCalledWith([
|
125
|
+
expect.objectContaining({
|
126
|
+
status: 'success',
|
127
|
+
request,
|
128
|
+
response: expect.objectContaining({
|
129
|
+
resultDisplay: 'YOLO Formatted tool output',
|
130
|
+
responseParts: {
|
131
|
+
functionResponse: {
|
132
|
+
id: 'yoloCall',
|
133
|
+
name: 'mockToolRequiresConfirmation',
|
134
|
+
response: { output: expectedOutput },
|
135
|
+
},
|
136
|
+
},
|
137
|
+
}),
|
138
|
+
}),
|
139
|
+
]);
|
140
|
+
// Ensure no confirmation UI was triggered (setPendingHistoryItem should not have been called with confirmation details)
|
141
|
+
const setPendingHistoryItemCalls = setPendingHistoryItem.mock.calls;
|
142
|
+
const confirmationCall = setPendingHistoryItemCalls.find((call) => {
|
143
|
+
const item = typeof call[0] === 'function' ? call[0]({}) : call[0];
|
144
|
+
return item?.tools?.[0]?.confirmationDetails;
|
145
|
+
});
|
146
|
+
expect(confirmationCall).toBeUndefined();
|
147
|
+
});
|
148
|
+
});
|
149
|
+
describe('useReactToolScheduler', () => {
|
150
|
+
// TODO(ntaylormullen): The following tests are skipped due to difficulties in
|
151
|
+
// reliably testing the asynchronous state updates and interactions with timers.
|
152
|
+
// These tests involve complex sequences of events, including confirmations,
|
153
|
+
// live output updates, and cancellations, which are challenging to assert
|
154
|
+
// correctly with the current testing setup. Further investigation is needed
|
155
|
+
// to find a robust way to test these scenarios.
|
156
|
+
let onComplete;
|
157
|
+
let setPendingHistoryItem;
|
158
|
+
let capturedOnConfirmForTest;
|
159
|
+
beforeEach(() => {
|
160
|
+
onComplete = vi.fn();
|
161
|
+
capturedOnConfirmForTest = undefined;
|
162
|
+
setPendingHistoryItem = vi.fn((updaterOrValue) => {
|
163
|
+
let pendingItem = null;
|
164
|
+
if (typeof updaterOrValue === 'function') {
|
165
|
+
// Loosen the type for prevState to allow for more flexible updates in tests
|
166
|
+
const prevState = {
|
167
|
+
type: 'tool_group', // Still default to tool_group for most cases
|
168
|
+
tools: [],
|
169
|
+
};
|
170
|
+
pendingItem = updaterOrValue(prevState); // Allow any for more flexibility
|
171
|
+
}
|
172
|
+
else {
|
173
|
+
pendingItem = updaterOrValue;
|
174
|
+
}
|
175
|
+
// Capture onConfirm if it exists, regardless of the exact type of pendingItem
|
176
|
+
// This is a common pattern in these tests.
|
177
|
+
if (pendingItem?.tools?.[0]?.confirmationDetails
|
178
|
+
?.onConfirm) {
|
179
|
+
capturedOnConfirmForTest = pendingItem
|
180
|
+
.tools[0].confirmationDetails?.onConfirm;
|
181
|
+
}
|
182
|
+
});
|
183
|
+
mockToolRegistry.getTool.mockClear();
|
184
|
+
mockTool.execute.mockClear();
|
185
|
+
mockTool.shouldConfirmExecute.mockClear();
|
186
|
+
mockToolWithLiveOutput.execute.mockClear();
|
187
|
+
mockToolWithLiveOutput.shouldConfirmExecute.mockClear();
|
188
|
+
mockToolRequiresConfirmation.execute.mockClear();
|
189
|
+
mockToolRequiresConfirmation.shouldConfirmExecute.mockClear();
|
190
|
+
mockOnUserConfirmForToolConfirmation = vi.fn();
|
191
|
+
mockToolRequiresConfirmation.shouldConfirmExecute.mockImplementation(async () => ({
|
192
|
+
onConfirm: mockOnUserConfirmForToolConfirmation,
|
193
|
+
fileName: 'mockToolRequiresConfirmation.ts',
|
194
|
+
fileDiff: 'Mock tool requires confirmation',
|
195
|
+
type: 'edit',
|
196
|
+
title: 'Mock Tool Requires Confirmation',
|
197
|
+
}));
|
198
|
+
vi.useFakeTimers();
|
199
|
+
});
|
200
|
+
afterEach(() => {
|
201
|
+
vi.clearAllTimers();
|
202
|
+
vi.useRealTimers();
|
203
|
+
});
|
204
|
+
const renderScheduler = () => renderHook(() => useReactToolScheduler(onComplete, mockConfig, setPendingHistoryItem, () => undefined, () => { }));
|
205
|
+
it('initial state should be empty', () => {
|
206
|
+
const { result } = renderScheduler();
|
207
|
+
expect(result.current[0]).toEqual([]);
|
208
|
+
});
|
209
|
+
it('should schedule and execute a tool call successfully', async () => {
|
210
|
+
mockToolRegistry.getTool.mockReturnValue(mockTool);
|
211
|
+
mockTool.execute.mockResolvedValue({
|
212
|
+
llmContent: 'Tool output',
|
213
|
+
returnDisplay: 'Formatted tool output',
|
214
|
+
summary: 'Formatted summary',
|
215
|
+
});
|
216
|
+
mockTool.shouldConfirmExecute.mockResolvedValue(null);
|
217
|
+
const { result } = renderScheduler();
|
218
|
+
const schedule = result.current[1];
|
219
|
+
const request = {
|
220
|
+
callId: 'call1',
|
221
|
+
name: 'mockTool',
|
222
|
+
args: { param: 'value' },
|
223
|
+
};
|
224
|
+
act(() => {
|
225
|
+
schedule(request, new AbortController().signal);
|
226
|
+
});
|
227
|
+
await act(async () => {
|
228
|
+
await vi.runAllTimersAsync();
|
229
|
+
});
|
230
|
+
await act(async () => {
|
231
|
+
await vi.runAllTimersAsync();
|
232
|
+
});
|
233
|
+
await act(async () => {
|
234
|
+
await vi.runAllTimersAsync();
|
235
|
+
});
|
236
|
+
expect(mockTool.execute).toHaveBeenCalledWith(request.args, expect.any(AbortSignal), undefined, undefined, undefined);
|
237
|
+
expect(onComplete).toHaveBeenCalledWith([
|
238
|
+
expect.objectContaining({
|
239
|
+
status: 'success',
|
240
|
+
request,
|
241
|
+
response: expect.objectContaining({
|
242
|
+
resultDisplay: 'Formatted tool output',
|
243
|
+
responseParts: {
|
244
|
+
functionResponse: {
|
245
|
+
id: 'call1',
|
246
|
+
name: 'mockTool',
|
247
|
+
response: { output: 'Tool output' },
|
248
|
+
},
|
249
|
+
},
|
250
|
+
}),
|
251
|
+
}),
|
252
|
+
]);
|
253
|
+
expect(result.current[0]).toEqual([]);
|
254
|
+
});
|
255
|
+
it('should handle tool not found', async () => {
|
256
|
+
mockToolRegistry.getTool.mockReturnValue(undefined);
|
257
|
+
const { result } = renderScheduler();
|
258
|
+
const schedule = result.current[1];
|
259
|
+
const request = {
|
260
|
+
callId: 'call1',
|
261
|
+
name: 'nonexistentTool',
|
262
|
+
args: {},
|
263
|
+
};
|
264
|
+
act(() => {
|
265
|
+
schedule(request, new AbortController().signal);
|
266
|
+
});
|
267
|
+
await act(async () => {
|
268
|
+
await vi.runAllTimersAsync();
|
269
|
+
});
|
270
|
+
await act(async () => {
|
271
|
+
await vi.runAllTimersAsync();
|
272
|
+
});
|
273
|
+
expect(onComplete).toHaveBeenCalledWith([
|
274
|
+
expect.objectContaining({
|
275
|
+
status: 'error',
|
276
|
+
request,
|
277
|
+
response: expect.objectContaining({
|
278
|
+
error: expect.objectContaining({
|
279
|
+
message: 'Tool "nonexistentTool" not found in registry.',
|
280
|
+
}),
|
281
|
+
}),
|
282
|
+
}),
|
283
|
+
]);
|
284
|
+
expect(result.current[0]).toEqual([]);
|
285
|
+
});
|
286
|
+
it('should handle error during shouldConfirmExecute', async () => {
|
287
|
+
mockToolRegistry.getTool.mockReturnValue(mockTool);
|
288
|
+
const confirmError = new Error('Confirmation check failed');
|
289
|
+
mockTool.shouldConfirmExecute.mockRejectedValue(confirmError);
|
290
|
+
const { result } = renderScheduler();
|
291
|
+
const schedule = result.current[1];
|
292
|
+
const request = {
|
293
|
+
callId: 'call1',
|
294
|
+
name: 'mockTool',
|
295
|
+
args: {},
|
296
|
+
};
|
297
|
+
act(() => {
|
298
|
+
schedule(request, new AbortController().signal);
|
299
|
+
});
|
300
|
+
await act(async () => {
|
301
|
+
await vi.runAllTimersAsync();
|
302
|
+
});
|
303
|
+
await act(async () => {
|
304
|
+
await vi.runAllTimersAsync();
|
305
|
+
});
|
306
|
+
expect(onComplete).toHaveBeenCalledWith([
|
307
|
+
expect.objectContaining({
|
308
|
+
status: 'error',
|
309
|
+
request,
|
310
|
+
response: expect.objectContaining({
|
311
|
+
error: confirmError,
|
312
|
+
}),
|
313
|
+
}),
|
314
|
+
]);
|
315
|
+
expect(result.current[0]).toEqual([]);
|
316
|
+
});
|
317
|
+
it('should handle error during execute', async () => {
|
318
|
+
mockToolRegistry.getTool.mockReturnValue(mockTool);
|
319
|
+
mockTool.shouldConfirmExecute.mockResolvedValue(null);
|
320
|
+
const execError = new Error('Execution failed');
|
321
|
+
mockTool.execute.mockRejectedValue(execError);
|
322
|
+
const { result } = renderScheduler();
|
323
|
+
const schedule = result.current[1];
|
324
|
+
const request = {
|
325
|
+
callId: 'call1',
|
326
|
+
name: 'mockTool',
|
327
|
+
args: {},
|
328
|
+
};
|
329
|
+
act(() => {
|
330
|
+
schedule(request, new AbortController().signal);
|
331
|
+
});
|
332
|
+
await act(async () => {
|
333
|
+
await vi.runAllTimersAsync();
|
334
|
+
});
|
335
|
+
await act(async () => {
|
336
|
+
await vi.runAllTimersAsync();
|
337
|
+
});
|
338
|
+
await act(async () => {
|
339
|
+
await vi.runAllTimersAsync();
|
340
|
+
});
|
341
|
+
expect(onComplete).toHaveBeenCalledWith([
|
342
|
+
expect.objectContaining({
|
343
|
+
status: 'error',
|
344
|
+
request,
|
345
|
+
response: expect.objectContaining({
|
346
|
+
error: execError,
|
347
|
+
}),
|
348
|
+
}),
|
349
|
+
]);
|
350
|
+
expect(result.current[0]).toEqual([]);
|
351
|
+
});
|
352
|
+
it.skip('should handle tool requiring confirmation - approved', async () => {
|
353
|
+
mockToolRegistry.getTool.mockReturnValue(mockToolRequiresConfirmation);
|
354
|
+
const expectedOutput = 'Confirmed output';
|
355
|
+
mockToolRequiresConfirmation.execute.mockResolvedValue({
|
356
|
+
llmContent: expectedOutput,
|
357
|
+
returnDisplay: 'Confirmed display',
|
358
|
+
summary: 'Confirmed summary',
|
359
|
+
});
|
360
|
+
const { result } = renderScheduler();
|
361
|
+
const schedule = result.current[1];
|
362
|
+
const request = {
|
363
|
+
callId: 'callConfirm',
|
364
|
+
name: 'mockToolRequiresConfirmation',
|
365
|
+
args: { data: 'sensitive' },
|
366
|
+
};
|
367
|
+
act(() => {
|
368
|
+
schedule(request, new AbortController().signal);
|
369
|
+
});
|
370
|
+
await act(async () => {
|
371
|
+
await vi.runAllTimersAsync();
|
372
|
+
});
|
373
|
+
expect(setPendingHistoryItem).toHaveBeenCalled();
|
374
|
+
expect(capturedOnConfirmForTest).toBeDefined();
|
375
|
+
await act(async () => {
|
376
|
+
await capturedOnConfirmForTest?.(ToolConfirmationOutcome.ProceedOnce);
|
377
|
+
});
|
378
|
+
await act(async () => {
|
379
|
+
await vi.runAllTimersAsync();
|
380
|
+
});
|
381
|
+
await act(async () => {
|
382
|
+
await vi.runAllTimersAsync();
|
383
|
+
});
|
384
|
+
await act(async () => {
|
385
|
+
await vi.runAllTimersAsync();
|
386
|
+
});
|
387
|
+
expect(mockOnUserConfirmForToolConfirmation).toHaveBeenCalledWith(ToolConfirmationOutcome.ProceedOnce);
|
388
|
+
expect(mockToolRequiresConfirmation.execute).toHaveBeenCalled();
|
389
|
+
expect(onComplete).toHaveBeenCalledWith([
|
390
|
+
expect.objectContaining({
|
391
|
+
status: 'success',
|
392
|
+
request,
|
393
|
+
response: expect.objectContaining({
|
394
|
+
resultDisplay: 'Confirmed display',
|
395
|
+
responseParts: expect.arrayContaining([
|
396
|
+
expect.objectContaining({
|
397
|
+
functionResponse: expect.objectContaining({
|
398
|
+
response: { output: expectedOutput },
|
399
|
+
}),
|
400
|
+
}),
|
401
|
+
]),
|
402
|
+
}),
|
403
|
+
}),
|
404
|
+
]);
|
405
|
+
});
|
406
|
+
it.skip('should handle tool requiring confirmation - cancelled by user', async () => {
|
407
|
+
mockToolRegistry.getTool.mockReturnValue(mockToolRequiresConfirmation);
|
408
|
+
const { result } = renderScheduler();
|
409
|
+
const schedule = result.current[1];
|
410
|
+
const request = {
|
411
|
+
callId: 'callConfirmCancel',
|
412
|
+
name: 'mockToolRequiresConfirmation',
|
413
|
+
args: {},
|
414
|
+
};
|
415
|
+
act(() => {
|
416
|
+
schedule(request, new AbortController().signal);
|
417
|
+
});
|
418
|
+
await act(async () => {
|
419
|
+
await vi.runAllTimersAsync();
|
420
|
+
});
|
421
|
+
expect(setPendingHistoryItem).toHaveBeenCalled();
|
422
|
+
expect(capturedOnConfirmForTest).toBeDefined();
|
423
|
+
await act(async () => {
|
424
|
+
await capturedOnConfirmForTest?.(ToolConfirmationOutcome.Cancel);
|
425
|
+
});
|
426
|
+
await act(async () => {
|
427
|
+
await vi.runAllTimersAsync();
|
428
|
+
});
|
429
|
+
await act(async () => {
|
430
|
+
await vi.runAllTimersAsync();
|
431
|
+
});
|
432
|
+
expect(mockOnUserConfirmForToolConfirmation).toHaveBeenCalledWith(ToolConfirmationOutcome.Cancel);
|
433
|
+
expect(onComplete).toHaveBeenCalledWith([
|
434
|
+
expect.objectContaining({
|
435
|
+
status: 'cancelled',
|
436
|
+
request,
|
437
|
+
response: expect.objectContaining({
|
438
|
+
responseParts: expect.arrayContaining([
|
439
|
+
expect.objectContaining({
|
440
|
+
functionResponse: expect.objectContaining({
|
441
|
+
response: expect.objectContaining({
|
442
|
+
error: `User did not allow tool call ${request.name}. Reason: User cancelled.`,
|
443
|
+
}),
|
444
|
+
}),
|
445
|
+
}),
|
446
|
+
]),
|
447
|
+
}),
|
448
|
+
}),
|
449
|
+
]);
|
450
|
+
});
|
451
|
+
it.skip('should handle live output updates', async () => {
|
452
|
+
mockToolRegistry.getTool.mockReturnValue(mockToolWithLiveOutput);
|
453
|
+
let liveUpdateFn;
|
454
|
+
let resolveExecutePromise;
|
455
|
+
const executePromise = new Promise((resolve) => {
|
456
|
+
resolveExecutePromise = resolve;
|
457
|
+
});
|
458
|
+
mockToolWithLiveOutput.execute.mockImplementation(async (_args, _signal, updateFn) => {
|
459
|
+
liveUpdateFn = updateFn;
|
460
|
+
return executePromise;
|
461
|
+
});
|
462
|
+
mockToolWithLiveOutput.shouldConfirmExecute.mockResolvedValue(null);
|
463
|
+
const { result } = renderScheduler();
|
464
|
+
const schedule = result.current[1];
|
465
|
+
const request = {
|
466
|
+
callId: 'liveCall',
|
467
|
+
name: 'mockToolWithLiveOutput',
|
468
|
+
args: {},
|
469
|
+
};
|
470
|
+
act(() => {
|
471
|
+
schedule(request, new AbortController().signal);
|
472
|
+
});
|
473
|
+
await act(async () => {
|
474
|
+
await vi.runAllTimersAsync();
|
475
|
+
});
|
476
|
+
expect(liveUpdateFn).toBeDefined();
|
477
|
+
expect(setPendingHistoryItem).toHaveBeenCalled();
|
478
|
+
await act(async () => {
|
479
|
+
liveUpdateFn?.('Live output 1');
|
480
|
+
});
|
481
|
+
await act(async () => {
|
482
|
+
await vi.runAllTimersAsync();
|
483
|
+
});
|
484
|
+
await act(async () => {
|
485
|
+
liveUpdateFn?.('Live output 2');
|
486
|
+
});
|
487
|
+
await act(async () => {
|
488
|
+
await vi.runAllTimersAsync();
|
489
|
+
});
|
490
|
+
act(() => {
|
491
|
+
resolveExecutePromise({
|
492
|
+
llmContent: 'Final output',
|
493
|
+
returnDisplay: 'Final display',
|
494
|
+
summary: 'Final summary',
|
495
|
+
});
|
496
|
+
});
|
497
|
+
await act(async () => {
|
498
|
+
await vi.runAllTimersAsync();
|
499
|
+
});
|
500
|
+
await act(async () => {
|
501
|
+
await vi.runAllTimersAsync();
|
502
|
+
});
|
503
|
+
expect(onComplete).toHaveBeenCalledWith([
|
504
|
+
expect.objectContaining({
|
505
|
+
status: 'success',
|
506
|
+
request,
|
507
|
+
response: expect.objectContaining({
|
508
|
+
resultDisplay: 'Final display',
|
509
|
+
responseParts: expect.arrayContaining([
|
510
|
+
expect.objectContaining({
|
511
|
+
functionResponse: expect.objectContaining({
|
512
|
+
response: { output: 'Final output' },
|
513
|
+
}),
|
514
|
+
}),
|
515
|
+
]),
|
516
|
+
}),
|
517
|
+
}),
|
518
|
+
]);
|
519
|
+
expect(result.current[0]).toEqual([]);
|
520
|
+
});
|
521
|
+
it('should schedule and execute multiple tool calls', async () => {
|
522
|
+
const tool1 = new MockTool('tool1', 'Tool 1');
|
523
|
+
tool1.execute.mockResolvedValue({
|
524
|
+
llmContent: 'Output 1',
|
525
|
+
returnDisplay: 'Display 1',
|
526
|
+
summary: 'Summary 1',
|
527
|
+
});
|
528
|
+
tool1.shouldConfirmExecute.mockResolvedValue(null);
|
529
|
+
const tool2 = new MockTool('tool2', 'Tool 2');
|
530
|
+
tool2.execute.mockResolvedValue({
|
531
|
+
llmContent: 'Output 2',
|
532
|
+
returnDisplay: 'Display 2',
|
533
|
+
summary: 'Summary 2',
|
534
|
+
});
|
535
|
+
tool2.shouldConfirmExecute.mockResolvedValue(null);
|
536
|
+
mockToolRegistry.getTool.mockImplementation((name) => {
|
537
|
+
if (name === 'tool1')
|
538
|
+
return tool1;
|
539
|
+
if (name === 'tool2')
|
540
|
+
return tool2;
|
541
|
+
return undefined;
|
542
|
+
});
|
543
|
+
const { result } = renderScheduler();
|
544
|
+
const schedule = result.current[1];
|
545
|
+
const requests = [
|
546
|
+
{ callId: 'multi1', name: 'tool1', args: { p: 1 } },
|
547
|
+
{ callId: 'multi2', name: 'tool2', args: { p: 2 } },
|
548
|
+
];
|
549
|
+
act(() => {
|
550
|
+
schedule(requests, new AbortController().signal);
|
551
|
+
});
|
552
|
+
await act(async () => {
|
553
|
+
await vi.runAllTimersAsync();
|
554
|
+
});
|
555
|
+
await act(async () => {
|
556
|
+
await vi.runAllTimersAsync();
|
557
|
+
});
|
558
|
+
await act(async () => {
|
559
|
+
await vi.runAllTimersAsync();
|
560
|
+
});
|
561
|
+
await act(async () => {
|
562
|
+
await vi.runAllTimersAsync();
|
563
|
+
});
|
564
|
+
expect(onComplete).toHaveBeenCalledTimes(1);
|
565
|
+
const completedCalls = onComplete.mock.calls[0][0];
|
566
|
+
expect(completedCalls.length).toBe(2);
|
567
|
+
const call1Result = completedCalls.find((c) => c.request.callId === 'multi1');
|
568
|
+
const call2Result = completedCalls.find((c) => c.request.callId === 'multi2');
|
569
|
+
expect(call1Result).toMatchObject({
|
570
|
+
status: 'success',
|
571
|
+
request: requests[0],
|
572
|
+
response: expect.objectContaining({
|
573
|
+
resultDisplay: 'Display 1',
|
574
|
+
responseParts: {
|
575
|
+
functionResponse: {
|
576
|
+
id: 'multi1',
|
577
|
+
name: 'tool1',
|
578
|
+
response: { output: 'Output 1' },
|
579
|
+
},
|
580
|
+
},
|
581
|
+
}),
|
582
|
+
});
|
583
|
+
expect(call2Result).toMatchObject({
|
584
|
+
status: 'success',
|
585
|
+
request: requests[1],
|
586
|
+
response: expect.objectContaining({
|
587
|
+
resultDisplay: 'Display 2',
|
588
|
+
responseParts: {
|
589
|
+
functionResponse: {
|
590
|
+
id: 'multi2',
|
591
|
+
name: 'tool2',
|
592
|
+
response: { output: 'Output 2' },
|
593
|
+
},
|
594
|
+
},
|
595
|
+
}),
|
596
|
+
});
|
597
|
+
expect(result.current[0]).toEqual([]);
|
598
|
+
});
|
599
|
+
it.skip('should throw error if scheduling while already running', async () => {
|
600
|
+
mockToolRegistry.getTool.mockReturnValue(mockTool);
|
601
|
+
const longExecutePromise = new Promise((resolve) => setTimeout(() => resolve({
|
602
|
+
llmContent: 'done',
|
603
|
+
returnDisplay: 'done display',
|
604
|
+
summary: 'done summary',
|
605
|
+
}), 50));
|
606
|
+
mockTool.execute.mockReturnValue(longExecutePromise);
|
607
|
+
mockTool.shouldConfirmExecute.mockResolvedValue(null);
|
608
|
+
const { result } = renderScheduler();
|
609
|
+
const schedule = result.current[1];
|
610
|
+
const request1 = {
|
611
|
+
callId: 'run1',
|
612
|
+
name: 'mockTool',
|
613
|
+
args: {},
|
614
|
+
};
|
615
|
+
const request2 = {
|
616
|
+
callId: 'run2',
|
617
|
+
name: 'mockTool',
|
618
|
+
args: {},
|
619
|
+
};
|
620
|
+
act(() => {
|
621
|
+
schedule(request1, new AbortController().signal);
|
622
|
+
});
|
623
|
+
await act(async () => {
|
624
|
+
await vi.runAllTimersAsync();
|
625
|
+
});
|
626
|
+
expect(() => schedule(request2, new AbortController().signal)).toThrow('Cannot schedule tool calls while other tool calls are running');
|
627
|
+
await act(async () => {
|
628
|
+
await vi.advanceTimersByTimeAsync(50);
|
629
|
+
await vi.runAllTimersAsync();
|
630
|
+
await act(async () => {
|
631
|
+
await vi.runAllTimersAsync();
|
632
|
+
});
|
633
|
+
});
|
634
|
+
expect(onComplete).toHaveBeenCalledWith([
|
635
|
+
expect.objectContaining({
|
636
|
+
status: 'success',
|
637
|
+
request: request1,
|
638
|
+
response: expect.objectContaining({ resultDisplay: 'done display' }),
|
639
|
+
}),
|
640
|
+
]);
|
641
|
+
expect(result.current[0]).toEqual([]);
|
642
|
+
});
|
643
|
+
});
|
644
|
+
describe('mapToDisplay', () => {
|
645
|
+
const baseRequest = {
|
646
|
+
callId: 'testCallId',
|
647
|
+
name: 'testTool',
|
648
|
+
args: { foo: 'bar' },
|
649
|
+
};
|
650
|
+
const baseTool = new MockTool('testTool', 'Test Tool Display');
|
651
|
+
const baseResponse = {
|
652
|
+
callId: 'testCallId',
|
653
|
+
responseParts: [
|
654
|
+
{
|
655
|
+
functionResponse: {
|
656
|
+
name: 'testTool',
|
657
|
+
id: 'testCallId',
|
658
|
+
response: { output: 'Test output' },
|
659
|
+
},
|
660
|
+
},
|
661
|
+
],
|
662
|
+
resultDisplay: 'Test display output',
|
663
|
+
error: undefined,
|
664
|
+
};
|
665
|
+
const baseInvocation = baseTool.build(baseRequest.args);
|
666
|
+
const testCases = [
|
667
|
+
{
|
668
|
+
name: 'validating',
|
669
|
+
status: 'validating',
|
670
|
+
extraProps: { tool: baseTool, invocation: baseInvocation },
|
671
|
+
expectedStatus: ToolCallStatus.Executing,
|
672
|
+
expectedName: baseTool.displayName,
|
673
|
+
expectedDescription: baseInvocation.getDescription(),
|
674
|
+
},
|
675
|
+
{
|
676
|
+
name: 'awaiting_approval',
|
677
|
+
status: 'awaiting_approval',
|
678
|
+
extraProps: {
|
679
|
+
tool: baseTool,
|
680
|
+
invocation: baseInvocation,
|
681
|
+
confirmationDetails: {
|
682
|
+
onConfirm: vi.fn(),
|
683
|
+
type: 'edit',
|
684
|
+
title: 'Test Tool Display',
|
685
|
+
serverName: 'testTool',
|
686
|
+
toolName: 'testTool',
|
687
|
+
toolDisplayName: 'Test Tool Display',
|
688
|
+
filePath: 'mock',
|
689
|
+
fileName: 'test.ts',
|
690
|
+
fileDiff: 'Test diff',
|
691
|
+
originalContent: 'Original content',
|
692
|
+
newContent: 'New content',
|
693
|
+
},
|
694
|
+
},
|
695
|
+
expectedStatus: ToolCallStatus.Confirming,
|
696
|
+
expectedName: baseTool.displayName,
|
697
|
+
expectedDescription: baseInvocation.getDescription(),
|
698
|
+
},
|
699
|
+
{
|
700
|
+
name: 'scheduled',
|
701
|
+
status: 'scheduled',
|
702
|
+
extraProps: { tool: baseTool, invocation: baseInvocation },
|
703
|
+
expectedStatus: ToolCallStatus.Pending,
|
704
|
+
expectedName: baseTool.displayName,
|
705
|
+
expectedDescription: baseInvocation.getDescription(),
|
706
|
+
},
|
707
|
+
{
|
708
|
+
name: 'executing no live output',
|
709
|
+
status: 'executing',
|
710
|
+
extraProps: { tool: baseTool, invocation: baseInvocation },
|
711
|
+
expectedStatus: ToolCallStatus.Executing,
|
712
|
+
expectedName: baseTool.displayName,
|
713
|
+
expectedDescription: baseInvocation.getDescription(),
|
714
|
+
},
|
715
|
+
{
|
716
|
+
name: 'executing with live output',
|
717
|
+
status: 'executing',
|
718
|
+
extraProps: {
|
719
|
+
tool: baseTool,
|
720
|
+
invocation: baseInvocation,
|
721
|
+
liveOutput: 'Live test output',
|
722
|
+
},
|
723
|
+
expectedStatus: ToolCallStatus.Executing,
|
724
|
+
expectedResultDisplay: 'Live test output',
|
725
|
+
expectedName: baseTool.displayName,
|
726
|
+
expectedDescription: baseInvocation.getDescription(),
|
727
|
+
},
|
728
|
+
{
|
729
|
+
name: 'success',
|
730
|
+
status: 'success',
|
731
|
+
extraProps: {
|
732
|
+
tool: baseTool,
|
733
|
+
invocation: baseInvocation,
|
734
|
+
response: baseResponse,
|
735
|
+
},
|
736
|
+
expectedStatus: ToolCallStatus.Success,
|
737
|
+
expectedResultDisplay: baseResponse.resultDisplay,
|
738
|
+
expectedName: baseTool.displayName,
|
739
|
+
expectedDescription: baseInvocation.getDescription(),
|
740
|
+
},
|
741
|
+
{
|
742
|
+
name: 'error tool not found',
|
743
|
+
status: 'error',
|
744
|
+
extraProps: {
|
745
|
+
response: {
|
746
|
+
...baseResponse,
|
747
|
+
error: new Error('Test error tool not found'),
|
748
|
+
resultDisplay: 'Error display tool not found',
|
749
|
+
},
|
750
|
+
},
|
751
|
+
expectedStatus: ToolCallStatus.Error,
|
752
|
+
expectedResultDisplay: 'Error display tool not found',
|
753
|
+
expectedName: baseRequest.name,
|
754
|
+
expectedDescription: JSON.stringify(baseRequest.args),
|
755
|
+
},
|
756
|
+
{
|
757
|
+
name: 'error tool execution failed',
|
758
|
+
status: 'error',
|
759
|
+
extraProps: {
|
760
|
+
tool: baseTool,
|
761
|
+
response: {
|
762
|
+
...baseResponse,
|
763
|
+
error: new Error('Tool execution failed'),
|
764
|
+
resultDisplay: 'Execution failed display',
|
765
|
+
},
|
766
|
+
},
|
767
|
+
expectedStatus: ToolCallStatus.Error,
|
768
|
+
expectedResultDisplay: 'Execution failed display',
|
769
|
+
expectedName: baseTool.displayName, // Changed from baseTool.name
|
770
|
+
expectedDescription: baseInvocation.getDescription(),
|
771
|
+
},
|
772
|
+
{
|
773
|
+
name: 'cancelled',
|
774
|
+
status: 'cancelled',
|
775
|
+
extraProps: {
|
776
|
+
tool: baseTool,
|
777
|
+
invocation: baseInvocation,
|
778
|
+
response: {
|
779
|
+
...baseResponse,
|
780
|
+
resultDisplay: 'Cancelled display',
|
781
|
+
},
|
782
|
+
},
|
783
|
+
expectedStatus: ToolCallStatus.Canceled,
|
784
|
+
expectedResultDisplay: 'Cancelled display',
|
785
|
+
expectedName: baseTool.displayName,
|
786
|
+
expectedDescription: baseInvocation.getDescription(),
|
787
|
+
},
|
788
|
+
];
|
789
|
+
testCases.forEach(({ name: testName, status, extraProps, expectedStatus, expectedResultDisplay, expectedName, expectedDescription, }) => {
|
790
|
+
it(`should map ToolCall with status '${status}' (${testName}) correctly`, () => {
|
791
|
+
const toolCall = {
|
792
|
+
request: baseRequest,
|
793
|
+
status,
|
794
|
+
...(extraProps || {}),
|
795
|
+
};
|
796
|
+
const display = mapToDisplay(toolCall);
|
797
|
+
expect(display.type).toBe('tool_group');
|
798
|
+
expect(display.tools.length).toBe(1);
|
799
|
+
const toolDisplay = display.tools[0];
|
800
|
+
expect(toolDisplay.callId).toBe(baseRequest.callId);
|
801
|
+
expect(toolDisplay.status).toBe(expectedStatus);
|
802
|
+
expect(toolDisplay.resultDisplay).toBe(expectedResultDisplay);
|
803
|
+
expect(toolDisplay.name).toBe(expectedName);
|
804
|
+
expect(toolDisplay.description).toBe(expectedDescription);
|
805
|
+
expect(toolDisplay.renderOutputAsMarkdown).toBe(extraProps?.tool?.isOutputMarkdown ?? false);
|
806
|
+
if (status === 'awaiting_approval') {
|
807
|
+
expect(toolDisplay.confirmationDetails).toBe(extraProps.confirmationDetails);
|
808
|
+
}
|
809
|
+
else {
|
810
|
+
expect(toolDisplay.confirmationDetails).toBeUndefined();
|
811
|
+
}
|
812
|
+
});
|
813
|
+
});
|
814
|
+
it('should map an array of ToolCalls correctly', () => {
|
815
|
+
const toolCall1 = {
|
816
|
+
request: { ...baseRequest, callId: 'call1' },
|
817
|
+
status: 'success',
|
818
|
+
tool: baseTool,
|
819
|
+
invocation: baseTool.build(baseRequest.args),
|
820
|
+
response: { ...baseResponse, callId: 'call1' },
|
821
|
+
};
|
822
|
+
const toolForCall2 = new MockTool(baseTool.name, baseTool.displayName, false, false, true);
|
823
|
+
const toolCall2 = {
|
824
|
+
request: { ...baseRequest, callId: 'call2' },
|
825
|
+
status: 'executing',
|
826
|
+
tool: toolForCall2,
|
827
|
+
invocation: toolForCall2.build(baseRequest.args),
|
828
|
+
liveOutput: 'markdown output',
|
829
|
+
};
|
830
|
+
const display = mapToDisplay([toolCall1, toolCall2]);
|
831
|
+
expect(display.tools.length).toBe(2);
|
832
|
+
expect(display.tools[0].callId).toBe('call1');
|
833
|
+
expect(display.tools[0].status).toBe(ToolCallStatus.Success);
|
834
|
+
expect(display.tools[0].renderOutputAsMarkdown).toBe(false);
|
835
|
+
expect(display.tools[1].callId).toBe('call2');
|
836
|
+
expect(display.tools[1].status).toBe(ToolCallStatus.Executing);
|
837
|
+
expect(display.tools[1].resultDisplay).toBe('markdown output');
|
838
|
+
expect(display.tools[1].renderOutputAsMarkdown).toBe(true);
|
839
|
+
});
|
840
|
+
});
|
841
|
+
//# sourceMappingURL=useToolScheduler.test.js.map
|