fix(patch): cherry-pick cfdc4cf to release/v0.24.0-pr-16759 to patch version v0.24.0 and create version 0.24.1 (#16865)

Co-authored-by: christine betts <chrstn@uw.edu>
Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
This commit is contained in:
gemini-cli-robot
2026-01-16 11:19:45 -08:00
committed by GitHub
parent b56a111594
commit 33f2b3a4e3
3 changed files with 86 additions and 52 deletions
+2 -2
View File
@@ -444,7 +444,7 @@ export const useGeminiStream = (
isClientInitiated: true, isClientInitiated: true,
prompt_id, prompt_id,
}; };
scheduleToolCalls([toolCallRequest], abortSignal); await scheduleToolCalls([toolCallRequest], abortSignal);
return { queryToSend: null, shouldProceed: false }; return { queryToSend: null, shouldProceed: false };
} }
case 'submit_prompt': { case 'submit_prompt': {
@@ -911,7 +911,7 @@ export const useGeminiStream = (
} }
} }
if (toolCallRequests.length > 0) { if (toolCallRequests.length > 0) {
scheduleToolCalls(toolCallRequests, signal); await scheduleToolCalls(toolCallRequests, signal);
} }
return StreamProcessingStatus.Completed; return StreamProcessingStatus.Completed;
}, },
@@ -31,7 +31,7 @@ import { ToolCallStatus } from '../types.js';
export type ScheduleFn = ( export type ScheduleFn = (
request: ToolCallRequestInfo | ToolCallRequestInfo[], request: ToolCallRequestInfo | ToolCallRequestInfo[],
signal: AbortSignal, signal: AbortSignal,
) => void; ) => Promise<void>;
export type MarkToolsAsSubmittedFn = (callIds: string[]) => void; export type MarkToolsAsSubmittedFn = (callIds: string[]) => void;
export type TrackedScheduledToolCall = ScheduledToolCall & { export type TrackedScheduledToolCall = ScheduledToolCall & {
@@ -180,7 +180,7 @@ export function useReactToolScheduler(
signal: AbortSignal, signal: AbortSignal,
) => { ) => {
setToolCallsForDisplay([]); setToolCallsForDisplay([]);
void scheduler.schedule(request, signal); return scheduler.schedule(request, signal);
}, },
[scheduler, setToolCallsForDisplay], [scheduler, setToolCallsForDisplay],
); );
@@ -163,8 +163,8 @@ describe('useReactToolScheduler in YOLO Mode', () => {
args: { data: 'any data' }, args: { data: 'any data' },
} as any; } as any;
act(() => { await act(async () => {
schedule(request, new AbortController().signal); await schedule(request, new AbortController().signal);
}); });
await act(async () => { await act(async () => {
@@ -220,11 +220,11 @@ describe('useReactToolScheduler', () => {
schedule: ( schedule: (
req: ToolCallRequestInfo | ToolCallRequestInfo[], req: ToolCallRequestInfo | ToolCallRequestInfo[],
signal: AbortSignal, signal: AbortSignal,
) => void, ) => Promise<void>,
request: ToolCallRequestInfo | ToolCallRequestInfo[], request: ToolCallRequestInfo | ToolCallRequestInfo[],
) => { ) => {
act(() => { await act(async () => {
schedule(request, new AbortController().signal); await schedule(request, new AbortController().signal);
}); });
await advanceAndSettle(); await advanceAndSettle();
@@ -313,10 +313,13 @@ describe('useReactToolScheduler', () => {
it('should clear previous tool calls when scheduling new ones', async () => { it('should clear previous tool calls when scheduling new ones', async () => {
mockToolRegistry.getTool.mockReturnValue(mockTool); mockToolRegistry.getTool.mockReturnValue(mockTool);
(mockTool.execute as Mock).mockResolvedValue({ (mockTool.execute as Mock).mockImplementation(async () => {
await new Promise((r) => setTimeout(r, 10));
return {
llmContent: 'Tool output', llmContent: 'Tool output',
returnDisplay: 'Formatted tool output', returnDisplay: 'Formatted tool output',
} as ToolResult); };
});
const { result } = renderScheduler(); const { result } = renderScheduler();
const schedule = result.current[1]; const schedule = result.current[1];
@@ -337,10 +340,13 @@ describe('useReactToolScheduler', () => {
name: 'mockTool', name: 'mockTool',
args: {}, args: {},
} as any; } as any;
act(() => { let schedulePromise: Promise<void>;
schedule(newRequest, new AbortController().signal); await act(async () => {
schedulePromise = schedule(newRequest, new AbortController().signal);
}); });
await advanceAndSettle();
// After scheduling, the old call should be gone, // After scheduling, the old call should be gone,
// and the new one should be in the display in its initial state. // and the new one should be in the display in its initial state.
expect(result.current[0].length).toBe(1); expect(result.current[0].length).toBe(1);
@@ -349,14 +355,13 @@ describe('useReactToolScheduler', () => {
// Let the new call finish. // Let the new call finish.
await act(async () => { await act(async () => {
await vi.advanceTimersByTimeAsync(0); await vi.advanceTimersByTimeAsync(20);
}); });
await act(async () => { await act(async () => {
await vi.advanceTimersByTimeAsync(0); await schedulePromise;
});
await act(async () => {
await vi.advanceTimersByTimeAsync(0);
}); });
expect(onComplete).toHaveBeenCalled(); expect(onComplete).toHaveBeenCalled();
}); });
@@ -379,16 +384,14 @@ describe('useReactToolScheduler', () => {
args: {}, args: {},
} as any; } as any;
act(() => { let schedulePromise: Promise<void>;
schedule(request, new AbortController().signal);
});
await act(async () => { await act(async () => {
await vi.advanceTimersByTimeAsync(0); schedulePromise = schedule(request, new AbortController().signal);
}); // validation
await act(async () => {
await vi.advanceTimersByTimeAsync(0); // Process scheduling
}); });
await advanceAndSettle(); // validation
await advanceAndSettle(); // Process scheduling
// At this point, the tool is 'executing' and waiting on the promise. // At this point, the tool is 'executing' and waiting on the promise.
expect(result.current[0][0].status).toBe('executing'); expect(result.current[0][0].status).toBe('executing');
@@ -397,9 +400,7 @@ describe('useReactToolScheduler', () => {
cancelAllToolCalls(cancelController.signal); cancelAllToolCalls(cancelController.signal);
}); });
await act(async () => { await advanceAndSettle();
await vi.advanceTimersByTimeAsync(0);
});
expect(onComplete).toHaveBeenCalledWith([ expect(onComplete).toHaveBeenCalledWith([
expect.objectContaining({ expect.objectContaining({
@@ -412,6 +413,11 @@ describe('useReactToolScheduler', () => {
await act(async () => { await act(async () => {
resolveExecute({ llmContent: 'output', returnDisplay: 'display' }); resolveExecute({ llmContent: 'output', returnDisplay: 'display' });
}); });
// Now await the schedule promise
await act(async () => {
await schedulePromise;
});
}); });
it.each([ it.each([
@@ -511,8 +517,9 @@ describe('useReactToolScheduler', () => {
args: { data: 'sensitive' }, args: { data: 'sensitive' },
} as any; } as any;
act(() => { let schedulePromise: Promise<void>;
schedule(request, new AbortController().signal); await act(async () => {
schedulePromise = schedule(request, new AbortController().signal);
}); });
await advanceAndSettle(); await advanceAndSettle();
@@ -526,8 +533,11 @@ describe('useReactToolScheduler', () => {
}); });
await advanceAndSettle(); await advanceAndSettle();
await advanceAndSettle();
await advanceAndSettle(); // Now await the schedule promise as it should complete
await act(async () => {
await schedulePromise;
});
expect(mockOnUserConfirmForToolConfirmation).toHaveBeenCalledWith( expect(mockOnUserConfirmForToolConfirmation).toHaveBeenCalledWith(
ToolConfirmationOutcome.ProceedOnce, ToolConfirmationOutcome.ProceedOnce,
@@ -558,8 +568,9 @@ describe('useReactToolScheduler', () => {
args: {}, args: {},
} as any; } as any;
act(() => { let schedulePromise: Promise<void>;
schedule(request, new AbortController().signal); await act(async () => {
schedulePromise = schedule(request, new AbortController().signal);
}); });
await advanceAndSettle(); await advanceAndSettle();
@@ -571,8 +582,13 @@ describe('useReactToolScheduler', () => {
await act(async () => { await act(async () => {
await capturedOnConfirmForTest?.(ToolConfirmationOutcome.Cancel); await capturedOnConfirmForTest?.(ToolConfirmationOutcome.Cancel);
}); });
await advanceAndSettle(); await advanceAndSettle();
await advanceAndSettle();
// Now await the schedule promise
await act(async () => {
await schedulePromise;
});
expect(mockOnUserConfirmForToolConfirmation).toHaveBeenCalledWith( expect(mockOnUserConfirmForToolConfirmation).toHaveBeenCalledWith(
ToolConfirmationOutcome.Cancel, ToolConfirmationOutcome.Cancel,
@@ -619,8 +635,12 @@ describe('useReactToolScheduler', () => {
args: {}, args: {},
} as any; } as any;
act(() => { let schedulePromise: Promise<void>;
result.current[1](request, new AbortController().signal); await act(async () => {
schedulePromise = result.current[1](
request,
new AbortController().signal,
);
}); });
await advanceAndSettle(); await advanceAndSettle();
@@ -644,7 +664,11 @@ describe('useReactToolScheduler', () => {
} as ToolResult); } as ToolResult);
}); });
await advanceAndSettle(); await advanceAndSettle();
await advanceAndSettle();
// Now await schedule
await act(async () => {
await schedulePromise;
});
const completedCalls = onComplete.mock.calls[0][0] as ToolCall[]; const completedCalls = onComplete.mock.calls[0][0] as ToolCall[];
expect(completedCalls[0].status).toBe('success'); expect(completedCalls[0].status).toBe('success');
@@ -690,8 +714,8 @@ describe('useReactToolScheduler', () => {
{ callId: 'multi2', name: 'tool2', args: { p: 2 } } as any, { callId: 'multi2', name: 'tool2', args: { p: 2 } } as any,
]; ];
act(() => { await act(async () => {
schedule(requests, new AbortController().signal); await schedule(requests, new AbortController().signal);
}); });
await act(async () => { await act(async () => {
await vi.advanceTimersByTimeAsync(0); await vi.advanceTimersByTimeAsync(0);
@@ -782,24 +806,30 @@ describe('useReactToolScheduler', () => {
args: {}, args: {},
} as any; } as any;
act(() => { let schedulePromise1: Promise<void>;
schedule(request1, new AbortController().signal); let schedulePromise2: Promise<void>;
await act(async () => {
schedulePromise1 = schedule(request1, new AbortController().signal);
}); });
await act(async () => { await act(async () => {
await vi.advanceTimersByTimeAsync(0); await vi.advanceTimersByTimeAsync(0);
}); });
act(() => { await act(async () => {
schedule(request2, new AbortController().signal); schedulePromise2 = schedule(request2, new AbortController().signal);
}); });
await act(async () => { await act(async () => {
await vi.advanceTimersByTimeAsync(50); await vi.advanceTimersByTimeAsync(50);
await vi.advanceTimersByTimeAsync(0); await vi.advanceTimersByTimeAsync(0);
});
// Wait for first to complete
await act(async () => { await act(async () => {
await vi.advanceTimersByTimeAsync(0); await schedulePromise1;
});
}); });
expect(onComplete).toHaveBeenCalledWith([ expect(onComplete).toHaveBeenCalledWith([
expect.objectContaining({ expect.objectContaining({
status: 'success', status: 'success',
@@ -807,13 +837,17 @@ describe('useReactToolScheduler', () => {
response: expect.objectContaining({ resultDisplay: 'done display' }), response: expect.objectContaining({ resultDisplay: 'done display' }),
}), }),
]); ]);
await act(async () => { await act(async () => {
await vi.advanceTimersByTimeAsync(50); await vi.advanceTimersByTimeAsync(50);
await vi.advanceTimersByTimeAsync(0); await vi.advanceTimersByTimeAsync(0);
});
// Wait for second to complete
await act(async () => { await act(async () => {
await vi.advanceTimersByTimeAsync(0); await schedulePromise2;
});
}); });
expect(onComplete).toHaveBeenCalledWith([ expect(onComplete).toHaveBeenCalledWith([
expect.objectContaining({ expect.objectContaining({
status: 'success', status: 'success',