mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
feat(hooks): implement granular stop and block behavior for agent hooks (#15824)
This commit is contained in:
@@ -1797,4 +1797,63 @@ describe('runNonInteractive', () => {
|
|||||||
// The key assertion: sendMessageStream should have been called ONLY ONCE (initial user input).
|
// The key assertion: sendMessageStream should have been called ONLY ONCE (initial user input).
|
||||||
expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledTimes(1);
|
expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Agent Execution Events', () => {
|
||||||
|
it('should handle AgentExecutionStopped event', async () => {
|
||||||
|
const events: ServerGeminiStreamEvent[] = [
|
||||||
|
{
|
||||||
|
type: GeminiEventType.AgentExecutionStopped,
|
||||||
|
value: { reason: 'Stopped by hook' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
mockGeminiClient.sendMessageStream.mockReturnValue(
|
||||||
|
createStreamFromEvents(events),
|
||||||
|
);
|
||||||
|
|
||||||
|
await runNonInteractive({
|
||||||
|
config: mockConfig,
|
||||||
|
settings: mockSettings,
|
||||||
|
input: 'test stop',
|
||||||
|
prompt_id: 'prompt-id-stop',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(processStderrSpy).toHaveBeenCalledWith(
|
||||||
|
'Agent execution stopped: Stopped by hook\n',
|
||||||
|
);
|
||||||
|
// Should exit without calling sendMessageStream again
|
||||||
|
expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle AgentExecutionBlocked event', async () => {
|
||||||
|
const allEvents: ServerGeminiStreamEvent[] = [
|
||||||
|
{
|
||||||
|
type: GeminiEventType.AgentExecutionBlocked,
|
||||||
|
value: { reason: 'Blocked by hook' },
|
||||||
|
},
|
||||||
|
{ type: GeminiEventType.Content, value: 'Final answer' },
|
||||||
|
{
|
||||||
|
type: GeminiEventType.Finished,
|
||||||
|
value: { reason: undefined, usageMetadata: { totalTokenCount: 10 } },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
mockGeminiClient.sendMessageStream.mockReturnValue(
|
||||||
|
createStreamFromEvents(allEvents),
|
||||||
|
);
|
||||||
|
|
||||||
|
await runNonInteractive({
|
||||||
|
config: mockConfig,
|
||||||
|
settings: mockSettings,
|
||||||
|
input: 'test block',
|
||||||
|
prompt_id: 'prompt-id-block',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(processStderrSpy).toHaveBeenCalledWith(
|
||||||
|
'[WARNING] Agent execution blocked: Blocked by hook\n',
|
||||||
|
);
|
||||||
|
// sendMessageStream is called once, recursion is internal to it and transparent to the caller
|
||||||
|
expect(mockGeminiClient.sendMessageStream).toHaveBeenCalledTimes(1);
|
||||||
|
expect(getWrittenOutput()).toBe('Final answer\n');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -348,6 +348,31 @@ export async function runNonInteractive({
|
|||||||
}
|
}
|
||||||
} else if (event.type === GeminiEventType.Error) {
|
} else if (event.type === GeminiEventType.Error) {
|
||||||
throw event.value.error;
|
throw event.value.error;
|
||||||
|
} else if (event.type === GeminiEventType.AgentExecutionStopped) {
|
||||||
|
const stopMessage = `Agent execution stopped: ${event.value.reason}`;
|
||||||
|
if (config.getOutputFormat() === OutputFormat.TEXT) {
|
||||||
|
process.stderr.write(`${stopMessage}\n`);
|
||||||
|
}
|
||||||
|
// Emit final result event for streaming JSON if needed
|
||||||
|
if (streamFormatter) {
|
||||||
|
const metrics = uiTelemetryService.getMetrics();
|
||||||
|
const durationMs = Date.now() - startTime;
|
||||||
|
streamFormatter.emitEvent({
|
||||||
|
type: JsonStreamEventType.RESULT,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
status: 'success',
|
||||||
|
stats: streamFormatter.convertToStreamStats(
|
||||||
|
metrics,
|
||||||
|
durationMs,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (event.type === GeminiEventType.AgentExecutionBlocked) {
|
||||||
|
const blockMessage = `Agent execution blocked: ${event.value.reason}`;
|
||||||
|
if (config.getOutputFormat() === OutputFormat.TEXT) {
|
||||||
|
process.stderr.write(`[WARNING] ${blockMessage}\n`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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],
|
[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(
|
const processGeminiStreamEvents = useCallback(
|
||||||
async (
|
async (
|
||||||
stream: AsyncIterable<GeminiEvent>,
|
stream: AsyncIterable<GeminiEvent>,
|
||||||
@@ -822,6 +857,18 @@ export const useGeminiStream = (
|
|||||||
case ServerGeminiEventType.Error:
|
case ServerGeminiEventType.Error:
|
||||||
handleErrorEvent(event.value, userMessageTimestamp);
|
handleErrorEvent(event.value, userMessageTimestamp);
|
||||||
break;
|
break;
|
||||||
|
case ServerGeminiEventType.AgentExecutionStopped:
|
||||||
|
handleAgentExecutionStoppedEvent(
|
||||||
|
event.value.reason,
|
||||||
|
userMessageTimestamp,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ServerGeminiEventType.AgentExecutionBlocked:
|
||||||
|
handleAgentExecutionBlockedEvent(
|
||||||
|
event.value.reason,
|
||||||
|
userMessageTimestamp,
|
||||||
|
);
|
||||||
|
break;
|
||||||
case ServerGeminiEventType.ChatCompressed:
|
case ServerGeminiEventType.ChatCompressed:
|
||||||
handleChatCompressionEvent(event.value, userMessageTimestamp);
|
handleChatCompressionEvent(event.value, userMessageTimestamp);
|
||||||
break;
|
break;
|
||||||
@@ -879,6 +926,8 @@ export const useGeminiStream = (
|
|||||||
handleContextWindowWillOverflowEvent,
|
handleContextWindowWillOverflowEvent,
|
||||||
handleCitationEvent,
|
handleCitationEvent,
|
||||||
handleChatModelEvent,
|
handleChatModelEvent,
|
||||||
|
handleAgentExecutionStoppedEvent,
|
||||||
|
handleAgentExecutionBlockedEvent,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
const submitQuery = useCallback(
|
const submitQuery = useCallback(
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import type {
|
|||||||
} from '../services/modelConfigService.js';
|
} from '../services/modelConfigService.js';
|
||||||
import { ClearcutLogger } from '../telemetry/clearcut-logger/clearcut-logger.js';
|
import { ClearcutLogger } from '../telemetry/clearcut-logger/clearcut-logger.js';
|
||||||
import { HookSystem } from '../hooks/hookSystem.js';
|
import { HookSystem } from '../hooks/hookSystem.js';
|
||||||
|
import type { DefaultHookOutput } from '../hooks/types.js';
|
||||||
import * as policyCatalog from '../availability/policyCatalog.js';
|
import * as policyCatalog from '../availability/policyCatalog.js';
|
||||||
|
|
||||||
vi.mock('../services/chatCompressionService.js');
|
vi.mock('../services/chatCompressionService.js');
|
||||||
@@ -2781,6 +2782,136 @@ ${JSON.stringify(
|
|||||||
expect(client['hookStateMap'].has('old-id')).toBe(false);
|
expect(client['hookStateMap'].has('old-id')).toBe(false);
|
||||||
expect(client['hookStateMap'].has('new-id')).toBe(true);
|
expect(client['hookStateMap'].has('new-id')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should stop execution in BeforeAgent when hook returns continue: false', async () => {
|
||||||
|
const { fireBeforeAgentHook } = await import('./clientHookTriggers.js');
|
||||||
|
vi.mocked(fireBeforeAgentHook).mockResolvedValue({
|
||||||
|
shouldStopExecution: () => true,
|
||||||
|
getEffectiveReason: () => 'Stopped by hook',
|
||||||
|
} as DefaultHookOutput);
|
||||||
|
|
||||||
|
const mockChat: Partial<GeminiChat> = {
|
||||||
|
addHistory: vi.fn(),
|
||||||
|
getHistory: vi.fn().mockReturnValue([]),
|
||||||
|
getLastPromptTokenCount: vi.fn(),
|
||||||
|
};
|
||||||
|
client['chat'] = mockChat as GeminiChat;
|
||||||
|
|
||||||
|
const request = [{ text: 'Hello' }];
|
||||||
|
const stream = client.sendMessageStream(
|
||||||
|
request,
|
||||||
|
new AbortController().signal,
|
||||||
|
'test-prompt',
|
||||||
|
);
|
||||||
|
const events = await fromAsync(stream);
|
||||||
|
|
||||||
|
expect(events).toContainEqual({
|
||||||
|
type: GeminiEventType.AgentExecutionStopped,
|
||||||
|
value: { reason: 'Stopped by hook' },
|
||||||
|
});
|
||||||
|
expect(mockChat.addHistory).toHaveBeenCalledWith({
|
||||||
|
role: 'user',
|
||||||
|
parts: request,
|
||||||
|
});
|
||||||
|
expect(mockTurnRunFn).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should block execution in BeforeAgent when hook returns decision: block', async () => {
|
||||||
|
const { fireBeforeAgentHook } = await import('./clientHookTriggers.js');
|
||||||
|
vi.mocked(fireBeforeAgentHook).mockResolvedValue({
|
||||||
|
shouldStopExecution: () => false,
|
||||||
|
isBlockingDecision: () => true,
|
||||||
|
getEffectiveReason: () => 'Blocked by hook',
|
||||||
|
} as DefaultHookOutput);
|
||||||
|
|
||||||
|
const mockChat: Partial<GeminiChat> = {
|
||||||
|
addHistory: vi.fn(),
|
||||||
|
getHistory: vi.fn().mockReturnValue([]),
|
||||||
|
getLastPromptTokenCount: vi.fn(),
|
||||||
|
};
|
||||||
|
client['chat'] = mockChat as GeminiChat;
|
||||||
|
|
||||||
|
const request = [{ text: 'Hello' }];
|
||||||
|
const stream = client.sendMessageStream(
|
||||||
|
request,
|
||||||
|
new AbortController().signal,
|
||||||
|
'test-prompt',
|
||||||
|
);
|
||||||
|
const events = await fromAsync(stream);
|
||||||
|
|
||||||
|
expect(events).toContainEqual({
|
||||||
|
type: GeminiEventType.AgentExecutionBlocked,
|
||||||
|
value: {
|
||||||
|
reason: 'Blocked by hook',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(mockChat.addHistory).not.toHaveBeenCalled();
|
||||||
|
expect(mockTurnRunFn).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should stop execution in AfterAgent when hook returns continue: false', async () => {
|
||||||
|
const { fireAfterAgentHook } = await import('./clientHookTriggers.js');
|
||||||
|
vi.mocked(fireAfterAgentHook).mockResolvedValue({
|
||||||
|
shouldStopExecution: () => true,
|
||||||
|
getEffectiveReason: () => 'Stopped after agent',
|
||||||
|
} as DefaultHookOutput);
|
||||||
|
|
||||||
|
mockTurnRunFn.mockImplementation(async function* () {
|
||||||
|
yield { type: GeminiEventType.Content, value: 'Hello' };
|
||||||
|
});
|
||||||
|
|
||||||
|
const stream = client.sendMessageStream(
|
||||||
|
{ text: 'Hi' },
|
||||||
|
new AbortController().signal,
|
||||||
|
'test-prompt',
|
||||||
|
);
|
||||||
|
const events = await fromAsync(stream);
|
||||||
|
|
||||||
|
expect(events).toContainEqual({
|
||||||
|
type: GeminiEventType.AgentExecutionStopped,
|
||||||
|
value: { reason: 'Stopped after agent' },
|
||||||
|
});
|
||||||
|
// sendMessageStream should not recurse
|
||||||
|
expect(mockTurnRunFn).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should yield AgentExecutionBlocked and recurse in AfterAgent when hook returns decision: block', async () => {
|
||||||
|
const { fireAfterAgentHook } = await import('./clientHookTriggers.js');
|
||||||
|
vi.mocked(fireAfterAgentHook)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
shouldStopExecution: () => false,
|
||||||
|
isBlockingDecision: () => true,
|
||||||
|
getEffectiveReason: () => 'Please explain',
|
||||||
|
} as DefaultHookOutput)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
shouldStopExecution: () => false,
|
||||||
|
isBlockingDecision: () => false,
|
||||||
|
} as DefaultHookOutput);
|
||||||
|
|
||||||
|
mockTurnRunFn.mockImplementation(async function* () {
|
||||||
|
yield { type: GeminiEventType.Content, value: 'Response' };
|
||||||
|
});
|
||||||
|
|
||||||
|
const stream = client.sendMessageStream(
|
||||||
|
{ text: 'Hi' },
|
||||||
|
new AbortController().signal,
|
||||||
|
'test-prompt',
|
||||||
|
);
|
||||||
|
const events = await fromAsync(stream);
|
||||||
|
|
||||||
|
expect(events).toContainEqual({
|
||||||
|
type: GeminiEventType.AgentExecutionBlocked,
|
||||||
|
value: { reason: 'Please explain' },
|
||||||
|
});
|
||||||
|
// Should have called turn run twice (original + re-prompt)
|
||||||
|
expect(mockTurnRunFn).toHaveBeenCalledTimes(2);
|
||||||
|
expect(mockTurnRunFn).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
expect.anything(),
|
||||||
|
[{ text: 'Please explain' }],
|
||||||
|
expect.anything(),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
Tool,
|
Tool,
|
||||||
GenerateContentResponse,
|
GenerateContentResponse,
|
||||||
} from '@google/genai';
|
} from '@google/genai';
|
||||||
|
import { createUserContent } from '@google/genai';
|
||||||
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||||
import {
|
import {
|
||||||
getDirectoryContextString,
|
getDirectoryContextString,
|
||||||
@@ -65,8 +66,12 @@ const MAX_TURNS = 100;
|
|||||||
|
|
||||||
type BeforeAgentHookReturn =
|
type BeforeAgentHookReturn =
|
||||||
| {
|
| {
|
||||||
type: GeminiEventType.Error;
|
type: GeminiEventType.AgentExecutionStopped;
|
||||||
value: { error: Error };
|
value: { reason: string };
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: GeminiEventType.AgentExecutionBlocked;
|
||||||
|
value: { reason: string };
|
||||||
}
|
}
|
||||||
| { additionalContext: string | undefined }
|
| { additionalContext: string | undefined }
|
||||||
| undefined;
|
| undefined;
|
||||||
@@ -135,13 +140,20 @@ export class GeminiClient {
|
|||||||
const hookOutput = await fireBeforeAgentHook(messageBus, request);
|
const hookOutput = await fireBeforeAgentHook(messageBus, request);
|
||||||
hookState.hasFiredBeforeAgent = true;
|
hookState.hasFiredBeforeAgent = true;
|
||||||
|
|
||||||
if (hookOutput?.isBlockingDecision() || hookOutput?.shouldStopExecution()) {
|
if (hookOutput?.shouldStopExecution()) {
|
||||||
return {
|
return {
|
||||||
type: GeminiEventType.Error,
|
type: GeminiEventType.AgentExecutionStopped,
|
||||||
value: {
|
value: {
|
||||||
error: new Error(
|
reason: hookOutput.getEffectiveReason(),
|
||||||
`BeforeAgent hook blocked processing: ${hookOutput.getEffectiveReason()}`,
|
},
|
||||||
),
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hookOutput?.isBlockingDecision()) {
|
||||||
|
return {
|
||||||
|
type: GeminiEventType.AgentExecutionBlocked,
|
||||||
|
value: {
|
||||||
|
reason: hookOutput.getEffectiveReason(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -747,7 +759,18 @@ export class GeminiClient {
|
|||||||
prompt_id,
|
prompt_id,
|
||||||
);
|
);
|
||||||
if (hookResult) {
|
if (hookResult) {
|
||||||
if ('type' in hookResult && hookResult.type === GeminiEventType.Error) {
|
if (
|
||||||
|
'type' in hookResult &&
|
||||||
|
hookResult.type === GeminiEventType.AgentExecutionStopped
|
||||||
|
) {
|
||||||
|
// Add user message to history before returning so it's kept in the transcript
|
||||||
|
this.getChat().addHistory(createUserContent(request));
|
||||||
|
yield hookResult;
|
||||||
|
return new Turn(this.getChat(), prompt_id);
|
||||||
|
} else if (
|
||||||
|
'type' in hookResult &&
|
||||||
|
hookResult.type === GeminiEventType.AgentExecutionBlocked
|
||||||
|
) {
|
||||||
yield hookResult;
|
yield hookResult;
|
||||||
return new Turn(this.getChat(), prompt_id);
|
return new Turn(this.getChat(), prompt_id);
|
||||||
} else if ('additionalContext' in hookResult) {
|
} else if ('additionalContext' in hookResult) {
|
||||||
@@ -781,11 +804,24 @@ export class GeminiClient {
|
|||||||
turn,
|
turn,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (hookOutput?.shouldStopExecution()) {
|
||||||
hookOutput?.isBlockingDecision() ||
|
yield {
|
||||||
hookOutput?.shouldStopExecution()
|
type: GeminiEventType.AgentExecutionStopped,
|
||||||
) {
|
value: {
|
||||||
|
reason: hookOutput.getEffectiveReason(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return turn;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hookOutput?.isBlockingDecision()) {
|
||||||
const continueReason = hookOutput.getEffectiveReason();
|
const continueReason = hookOutput.getEffectiveReason();
|
||||||
|
yield {
|
||||||
|
type: GeminiEventType.AgentExecutionBlocked,
|
||||||
|
value: {
|
||||||
|
reason: continueReason,
|
||||||
|
},
|
||||||
|
};
|
||||||
const continueRequest = [{ text: continueReason }];
|
const continueRequest = [{ text: continueReason }];
|
||||||
yield* this.sendMessageStream(
|
yield* this.sendMessageStream(
|
||||||
continueRequest,
|
continueRequest,
|
||||||
|
|||||||
@@ -66,12 +66,28 @@ export enum GeminiEventType {
|
|||||||
ContextWindowWillOverflow = 'context_window_will_overflow',
|
ContextWindowWillOverflow = 'context_window_will_overflow',
|
||||||
InvalidStream = 'invalid_stream',
|
InvalidStream = 'invalid_stream',
|
||||||
ModelInfo = 'model_info',
|
ModelInfo = 'model_info',
|
||||||
|
AgentExecutionStopped = 'agent_execution_stopped',
|
||||||
|
AgentExecutionBlocked = 'agent_execution_blocked',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ServerGeminiRetryEvent = {
|
export type ServerGeminiRetryEvent = {
|
||||||
type: GeminiEventType.Retry;
|
type: GeminiEventType.Retry;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ServerGeminiAgentExecutionStoppedEvent = {
|
||||||
|
type: GeminiEventType.AgentExecutionStopped;
|
||||||
|
value: {
|
||||||
|
reason: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ServerGeminiAgentExecutionBlockedEvent = {
|
||||||
|
type: GeminiEventType.AgentExecutionBlocked;
|
||||||
|
value: {
|
||||||
|
reason: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type ServerGeminiContextWindowWillOverflowEvent = {
|
export type ServerGeminiContextWindowWillOverflowEvent = {
|
||||||
type: GeminiEventType.ContextWindowWillOverflow;
|
type: GeminiEventType.ContextWindowWillOverflow;
|
||||||
value: {
|
value: {
|
||||||
@@ -204,7 +220,9 @@ export type ServerGeminiStreamEvent =
|
|||||||
| ServerGeminiRetryEvent
|
| ServerGeminiRetryEvent
|
||||||
| ServerGeminiContextWindowWillOverflowEvent
|
| ServerGeminiContextWindowWillOverflowEvent
|
||||||
| ServerGeminiInvalidStreamEvent
|
| ServerGeminiInvalidStreamEvent
|
||||||
| ServerGeminiModelInfoEvent;
|
| ServerGeminiModelInfoEvent
|
||||||
|
| ServerGeminiAgentExecutionStoppedEvent
|
||||||
|
| ServerGeminiAgentExecutionBlockedEvent;
|
||||||
|
|
||||||
// A turn manages the agentic loop turn within the server context.
|
// A turn manages the agentic loop turn within the server context.
|
||||||
export class Turn {
|
export class Turn {
|
||||||
|
|||||||
Reference in New Issue
Block a user