feat(hooks): implement granular stop and block behavior for agent hooks (#15824)

This commit is contained in:
Sandy Tao
2026-01-04 18:58:34 -08:00
committed by GitHub
parent bdb349e7f6
commit dd84c2fb83
7 changed files with 388 additions and 13 deletions
@@ -2798,4 +2798,61 @@ describe('useGeminiStream', () => {
});
});
});
describe('Agent Execution Events', () => {
it('should handle AgentExecutionStopped event', async () => {
mockSendMessageStream.mockReturnValue(
(async function* () {
yield {
type: ServerGeminiEventType.AgentExecutionStopped,
value: { reason: 'Stopped by hook' },
};
})(),
);
const { result } = renderTestHook();
await act(async () => {
await result.current.submitQuery('test stop');
});
await waitFor(() => {
expect(mockAddItem).toHaveBeenCalledWith(
{
type: MessageType.INFO,
text: 'Agent execution stopped: Stopped by hook',
},
expect.any(Number),
);
expect(result.current.streamingState).toBe(StreamingState.Idle);
});
});
it('should handle AgentExecutionBlocked event', async () => {
mockSendMessageStream.mockReturnValue(
(async function* () {
yield {
type: ServerGeminiEventType.AgentExecutionBlocked,
value: { reason: 'Blocked by hook' },
};
})(),
);
const { result } = renderTestHook();
await act(async () => {
await result.current.submitQuery('test block');
});
await waitFor(() => {
expect(mockAddItem).toHaveBeenCalledWith(
{
type: MessageType.WARNING,
text: 'Agent execution blocked: Blocked by hook',
},
expect.any(Number),
);
});
});
});
});
@@ -793,6 +793,41 @@ export const useGeminiStream = (
[addItem, pendingHistoryItemRef, setPendingHistoryItem, settings],
);
const handleAgentExecutionStoppedEvent = useCallback(
(reason: string, userMessageTimestamp: number) => {
if (pendingHistoryItemRef.current) {
addItem(pendingHistoryItemRef.current, userMessageTimestamp);
setPendingHistoryItem(null);
}
addItem(
{
type: MessageType.INFO,
text: `Agent execution stopped: ${reason}`,
},
userMessageTimestamp,
);
setIsResponding(false);
},
[addItem, pendingHistoryItemRef, setPendingHistoryItem, setIsResponding],
);
const handleAgentExecutionBlockedEvent = useCallback(
(reason: string, userMessageTimestamp: number) => {
if (pendingHistoryItemRef.current) {
addItem(pendingHistoryItemRef.current, userMessageTimestamp);
setPendingHistoryItem(null);
}
addItem(
{
type: MessageType.WARNING,
text: `Agent execution blocked: ${reason}`,
},
userMessageTimestamp,
);
},
[addItem, pendingHistoryItemRef, setPendingHistoryItem],
);
const processGeminiStreamEvents = useCallback(
async (
stream: AsyncIterable<GeminiEvent>,
@@ -822,6 +857,18 @@ export const useGeminiStream = (
case ServerGeminiEventType.Error:
handleErrorEvent(event.value, userMessageTimestamp);
break;
case ServerGeminiEventType.AgentExecutionStopped:
handleAgentExecutionStoppedEvent(
event.value.reason,
userMessageTimestamp,
);
break;
case ServerGeminiEventType.AgentExecutionBlocked:
handleAgentExecutionBlockedEvent(
event.value.reason,
userMessageTimestamp,
);
break;
case ServerGeminiEventType.ChatCompressed:
handleChatCompressionEvent(event.value, userMessageTimestamp);
break;
@@ -879,6 +926,8 @@ export const useGeminiStream = (
handleContextWindowWillOverflowEvent,
handleCitationEvent,
handleChatModelEvent,
handleAgentExecutionStoppedEvent,
handleAgentExecutionBlockedEvent,
],
);
const submitQuery = useCallback(