mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-06 08:07:42 -07:00
fix: resolve typescript lint errors and test failures
- Remove unnecessary `any` casts and unsafe type assertions in `useAgentStream.ts`. - Introduce `MinimalTrackedToolCall` to safely type mock tool calls for inactivity monitors. - Fix arrow-body-style lint errors in `AppContainer.tsx` and `useAgentStream.ts`. - Update `nonInteractiveCli.test.ts` to include a required `build` method in mock tools to prevent TypeErrors during stream initialization. - Remove redundant non-null assertion in `legacy-agent-session.ts`.
This commit is contained in:
@@ -1519,6 +1519,9 @@ describe('runNonInteractive', () => {
|
||||
name: 'ShellTool',
|
||||
description: 'A shell tool',
|
||||
run: vi.fn(),
|
||||
build: vi.fn().mockReturnValue({
|
||||
getDescription: () => 'A shell tool',
|
||||
}),
|
||||
}),
|
||||
getFunctionDeclarations: vi.fn().mockReturnValue([{ name: 'ShellTool' }]),
|
||||
} as unknown as ToolRegistry);
|
||||
|
||||
@@ -82,6 +82,7 @@ import {
|
||||
buildUserSteeringHintPrompt,
|
||||
logBillingEvent,
|
||||
ApiKeyUpdatedEvent,
|
||||
LegacyAgentProtocol,
|
||||
type InjectionSource,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { validateAuthMethod } from '../config/auth.js';
|
||||
@@ -1092,8 +1093,44 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
};
|
||||
}, [config]);
|
||||
|
||||
const useAgentProtocol = config?.getExperimentalUseAgentProtocol() || false;
|
||||
const useActiveStream = useAgentProtocol ? useAgentStream : useGeminiStream;
|
||||
const streamAgent = useMemo(
|
||||
() =>
|
||||
config?.getExperimentalUseAgentProtocol()
|
||||
? new LegacyAgentProtocol({ config, getPreferredEditor })
|
||||
: undefined,
|
||||
[config, getPreferredEditor],
|
||||
);
|
||||
|
||||
const activeStream = streamAgent
|
||||
? // eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useAgentStream({
|
||||
agent: streamAgent,
|
||||
addItem: historyManager.addItem,
|
||||
onCancelSubmit,
|
||||
isShellFocused: embeddedShellFocused,
|
||||
})
|
||||
: // eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useGeminiStream(
|
||||
config.getGeminiClient(),
|
||||
historyManager.history,
|
||||
historyManager.addItem,
|
||||
config,
|
||||
settings,
|
||||
setDebugMessage,
|
||||
handleSlashCommand,
|
||||
shellModeActive,
|
||||
getPreferredEditor,
|
||||
onAuthError,
|
||||
performMemoryRefresh,
|
||||
modelSwitchedFromQuotaError,
|
||||
setModelSwitchedFromQuotaError,
|
||||
onCancelSubmit,
|
||||
setEmbeddedShellFocused,
|
||||
terminalWidth,
|
||||
terminalHeight,
|
||||
embeddedShellFocused,
|
||||
consumePendingHints,
|
||||
);
|
||||
|
||||
const {
|
||||
streamingState,
|
||||
@@ -1114,27 +1151,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
backgroundShells,
|
||||
dismissBackgroundShell,
|
||||
retryStatus,
|
||||
} = useActiveStream(
|
||||
config.getGeminiClient(),
|
||||
historyManager.history,
|
||||
historyManager.addItem,
|
||||
config,
|
||||
settings,
|
||||
setDebugMessage,
|
||||
handleSlashCommand,
|
||||
shellModeActive,
|
||||
getPreferredEditor,
|
||||
onAuthError,
|
||||
performMemoryRefresh,
|
||||
modelSwitchedFromQuotaError,
|
||||
setModelSwitchedFromQuotaError,
|
||||
onCancelSubmit,
|
||||
setEmbeddedShellFocused,
|
||||
terminalWidth,
|
||||
terminalHeight,
|
||||
embeddedShellFocused,
|
||||
consumePendingHints,
|
||||
);
|
||||
} = activeStream;
|
||||
|
||||
const pendingHistoryItems = useMemo(
|
||||
() => [...pendingSlashCommandHistoryItems, ...pendingGeminiHistoryItems],
|
||||
|
||||
@@ -6,40 +6,17 @@
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { act } from 'react';
|
||||
import {
|
||||
type Config,
|
||||
type GeminiClient,
|
||||
LegacyAgentSession as MockLegacyAgentSession,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { type LoadedSettings } from '../../config/settings.js';
|
||||
import type { LegacyAgentProtocol } from '@google/gemini-cli-core';
|
||||
import { renderHookWithProviders } from '../../test-utils/render.js';
|
||||
|
||||
// --- MOCKS ---
|
||||
|
||||
const mockScheduler = vi.hoisted(() => ({
|
||||
schedule: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
cancelAll: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockLegacyAgentSession = vi.hoisted(() => ({
|
||||
const mockLegacyAgentProtocol = vi.hoisted(() => ({
|
||||
send: vi.fn().mockResolvedValue({ streamId: 'test-stream-id' }),
|
||||
subscribe: vi.fn().mockReturnValue(() => {}),
|
||||
abort: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
vi.mock('./useToolScheduler.js', () => ({
|
||||
useToolScheduler: vi.fn().mockReturnValue([
|
||||
[], // toolCalls
|
||||
vi.fn(), // schedule
|
||||
vi.fn(), // markToolsAsSubmitted
|
||||
vi.fn(), // setToolCallsForDisplay
|
||||
vi.fn(), // cancelAll
|
||||
0, // lastToolOutputTime
|
||||
mockScheduler, // scheduler
|
||||
]),
|
||||
}));
|
||||
|
||||
vi.mock('./useLogger.js', () => ({
|
||||
useLogger: vi.fn().mockReturnValue({
|
||||
logMessage: vi.fn().mockResolvedValue(undefined),
|
||||
@@ -56,17 +33,6 @@ vi.mock('../contexts/SessionContext.js', async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
// Mock core classes properly
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual = await importOriginal<Record<string, unknown>>();
|
||||
return {
|
||||
...actual,
|
||||
LegacyAgentSession: vi
|
||||
.fn()
|
||||
.mockImplementation(() => mockLegacyAgentSession),
|
||||
};
|
||||
});
|
||||
|
||||
// --- END MOCKS ---
|
||||
|
||||
import { useAgentStream } from './useAgentStream.js';
|
||||
@@ -74,88 +40,40 @@ import { MessageType, StreamingState } from '../types.js';
|
||||
|
||||
describe('useAgentStream', () => {
|
||||
const mockAddItem = vi.fn();
|
||||
const mockOnDebugMessage = vi.fn();
|
||||
const mockHandleSlashCommand = vi.fn().mockResolvedValue(false);
|
||||
const mockOnAuthError = vi.fn();
|
||||
const mockPerformMemoryRefresh = vi.fn(() => Promise.resolve());
|
||||
const mockSetModelSwitchedFromQuotaError = vi.fn();
|
||||
const mockOnCancelSubmit = vi.fn();
|
||||
const mockSetShellInputFocused = vi.fn();
|
||||
|
||||
const mockConfig = {
|
||||
storage: {},
|
||||
getSessionId: () => 'test-session',
|
||||
getExperimentalUseAgentProtocol: () => true,
|
||||
getApprovalMode: () => 'default',
|
||||
getMessageBus: () => ({}),
|
||||
} as Config;
|
||||
|
||||
const mockSettings = {
|
||||
merged: {
|
||||
billing: { overageStrategy: 'stop' },
|
||||
ui: { errorVerbosity: 'full' },
|
||||
},
|
||||
} as unknown as LoadedSettings;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should initialize LegacyAgentSession on mount', async () => {
|
||||
it('should initialize on mount', async () => {
|
||||
await renderHookWithProviders(() =>
|
||||
useAgentStream(
|
||||
{} as GeminiClient,
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockSettings,
|
||||
mockOnDebugMessage,
|
||||
mockHandleSlashCommand,
|
||||
false,
|
||||
() => undefined,
|
||||
mockOnAuthError,
|
||||
mockPerformMemoryRefresh,
|
||||
false,
|
||||
mockSetModelSwitchedFromQuotaError,
|
||||
mockOnCancelSubmit,
|
||||
mockSetShellInputFocused,
|
||||
80,
|
||||
24,
|
||||
),
|
||||
useAgentStream({
|
||||
agent: mockLegacyAgentProtocol as unknown as LegacyAgentProtocol,
|
||||
addItem: mockAddItem,
|
||||
onCancelSubmit: mockOnCancelSubmit,
|
||||
isShellFocused: false,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(MockLegacyAgentSession).toHaveBeenCalled();
|
||||
expect(mockLegacyAgentSession.subscribe).toHaveBeenCalled();
|
||||
expect(mockLegacyAgentProtocol.subscribe).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call session.send when submitQuery is called', async () => {
|
||||
it('should call agent.send when submitQuery is called', async () => {
|
||||
const { result } = await renderHookWithProviders(() =>
|
||||
useAgentStream(
|
||||
{} as GeminiClient,
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockSettings,
|
||||
mockOnDebugMessage,
|
||||
mockHandleSlashCommand,
|
||||
false,
|
||||
() => undefined,
|
||||
mockOnAuthError,
|
||||
mockPerformMemoryRefresh,
|
||||
false,
|
||||
mockSetModelSwitchedFromQuotaError,
|
||||
mockOnCancelSubmit,
|
||||
mockSetShellInputFocused,
|
||||
80,
|
||||
24,
|
||||
),
|
||||
useAgentStream({
|
||||
agent: mockLegacyAgentProtocol as unknown as LegacyAgentProtocol,
|
||||
addItem: mockAddItem,
|
||||
onCancelSubmit: mockOnCancelSubmit,
|
||||
isShellFocused: false,
|
||||
}),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitQuery('hello');
|
||||
});
|
||||
|
||||
expect(mockLegacyAgentSession.send).toHaveBeenCalledWith({
|
||||
expect(mockLegacyAgentProtocol.send).toHaveBeenCalledWith({
|
||||
message: [{ type: 'text', text: 'hello' }],
|
||||
});
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
@@ -166,67 +84,52 @@ describe('useAgentStream', () => {
|
||||
|
||||
it('should update streamingState based on agent_start and agent_end events', async () => {
|
||||
const { result } = await renderHookWithProviders(() =>
|
||||
useAgentStream(
|
||||
{} as GeminiClient,
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockSettings,
|
||||
mockOnDebugMessage,
|
||||
mockHandleSlashCommand,
|
||||
false,
|
||||
() => undefined,
|
||||
mockOnAuthError,
|
||||
mockPerformMemoryRefresh,
|
||||
false,
|
||||
mockSetModelSwitchedFromQuotaError,
|
||||
mockOnCancelSubmit,
|
||||
mockSetShellInputFocused,
|
||||
80,
|
||||
24,
|
||||
),
|
||||
useAgentStream({
|
||||
agent: mockLegacyAgentProtocol as unknown as LegacyAgentProtocol,
|
||||
addItem: mockAddItem,
|
||||
onCancelSubmit: mockOnCancelSubmit,
|
||||
isShellFocused: false,
|
||||
}),
|
||||
);
|
||||
|
||||
const eventHandler = vi.mocked(mockLegacyAgentSession.subscribe).mock
|
||||
const eventHandler = vi.mocked(mockLegacyAgentProtocol.subscribe).mock
|
||||
.calls[0][0];
|
||||
|
||||
expect(result.current.streamingState).toBe(StreamingState.Idle);
|
||||
|
||||
act(() => {
|
||||
eventHandler({ type: 'agent_start' });
|
||||
eventHandler({
|
||||
type: 'agent_start',
|
||||
id: '1',
|
||||
timestamp: '',
|
||||
streamId: '',
|
||||
});
|
||||
});
|
||||
expect(result.current.streamingState).toBe(StreamingState.Responding);
|
||||
|
||||
act(() => {
|
||||
eventHandler({ type: 'agent_end', reason: 'completed' });
|
||||
eventHandler({
|
||||
type: 'agent_end',
|
||||
reason: 'completed',
|
||||
id: '2',
|
||||
timestamp: '',
|
||||
streamId: '',
|
||||
});
|
||||
});
|
||||
expect(result.current.streamingState).toBe(StreamingState.Idle);
|
||||
});
|
||||
|
||||
it('should accumulate text content and update pendingHistoryItems', async () => {
|
||||
const { result } = await renderHookWithProviders(() =>
|
||||
useAgentStream(
|
||||
{} as GeminiClient,
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockSettings,
|
||||
mockOnDebugMessage,
|
||||
mockHandleSlashCommand,
|
||||
false,
|
||||
() => undefined,
|
||||
mockOnAuthError,
|
||||
mockPerformMemoryRefresh,
|
||||
false,
|
||||
mockSetModelSwitchedFromQuotaError,
|
||||
mockOnCancelSubmit,
|
||||
mockSetShellInputFocused,
|
||||
80,
|
||||
24,
|
||||
),
|
||||
useAgentStream({
|
||||
agent: mockLegacyAgentProtocol as unknown as LegacyAgentProtocol,
|
||||
addItem: mockAddItem,
|
||||
onCancelSubmit: mockOnCancelSubmit,
|
||||
isShellFocused: false,
|
||||
}),
|
||||
);
|
||||
|
||||
const eventHandler = vi.mocked(mockLegacyAgentSession.subscribe).mock
|
||||
const eventHandler = vi.mocked(mockLegacyAgentProtocol.subscribe).mock
|
||||
.calls[0][0];
|
||||
|
||||
act(() => {
|
||||
@@ -234,6 +137,9 @@ describe('useAgentStream', () => {
|
||||
type: 'message',
|
||||
role: 'agent',
|
||||
content: [{ type: 'text', text: 'Hello' }],
|
||||
id: '1',
|
||||
timestamp: '',
|
||||
streamId: '',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -248,6 +154,9 @@ describe('useAgentStream', () => {
|
||||
type: 'message',
|
||||
role: 'agent',
|
||||
content: [{ type: 'text', text: ' world' }],
|
||||
id: '2',
|
||||
timestamp: '',
|
||||
streamId: '',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -256,28 +165,15 @@ describe('useAgentStream', () => {
|
||||
|
||||
it('should process thought events and update thought state', async () => {
|
||||
const { result } = await renderHookWithProviders(() =>
|
||||
useAgentStream(
|
||||
{} as GeminiClient,
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockSettings,
|
||||
mockOnDebugMessage,
|
||||
mockHandleSlashCommand,
|
||||
false,
|
||||
() => undefined,
|
||||
mockOnAuthError,
|
||||
mockPerformMemoryRefresh,
|
||||
false,
|
||||
mockSetModelSwitchedFromQuotaError,
|
||||
mockOnCancelSubmit,
|
||||
mockSetShellInputFocused,
|
||||
80,
|
||||
24,
|
||||
),
|
||||
useAgentStream({
|
||||
agent: mockLegacyAgentProtocol as unknown as LegacyAgentProtocol,
|
||||
addItem: mockAddItem,
|
||||
onCancelSubmit: mockOnCancelSubmit,
|
||||
isShellFocused: false,
|
||||
}),
|
||||
);
|
||||
|
||||
const eventHandler = vi.mocked(mockLegacyAgentSession.subscribe).mock
|
||||
const eventHandler = vi.mocked(mockLegacyAgentProtocol.subscribe).mock
|
||||
.calls[0][0];
|
||||
|
||||
act(() => {
|
||||
@@ -285,6 +181,9 @@ describe('useAgentStream', () => {
|
||||
type: 'message',
|
||||
role: 'agent',
|
||||
content: [{ type: 'thought', thought: '**Thinking** about tests' }],
|
||||
id: '1',
|
||||
timestamp: '',
|
||||
streamId: '',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -294,84 +193,21 @@ describe('useAgentStream', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should display error message when a tool call requires approval', async () => {
|
||||
let eventHandler: (event: unknown) => void = () => {};
|
||||
vi.spyOn(mockLegacyAgentSession, 'subscribe').mockImplementation(
|
||||
(handler) => {
|
||||
eventHandler = handler;
|
||||
return () => {};
|
||||
},
|
||||
);
|
||||
|
||||
await renderHookWithProviders(() =>
|
||||
useAgentStream(
|
||||
{} as GeminiClient,
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockSettings,
|
||||
mockOnDebugMessage,
|
||||
mockHandleSlashCommand,
|
||||
false,
|
||||
() => undefined,
|
||||
mockOnAuthError,
|
||||
mockPerformMemoryRefresh,
|
||||
false,
|
||||
mockSetModelSwitchedFromQuotaError,
|
||||
mockOnCancelSubmit,
|
||||
mockSetShellInputFocused,
|
||||
80,
|
||||
24,
|
||||
),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
eventHandler({
|
||||
type: 'error',
|
||||
status: 'UNIMPLEMENTED',
|
||||
message:
|
||||
'TODO: Tool approvals not yet implemented, please switch to YOLO mode to test.',
|
||||
fatal: true,
|
||||
});
|
||||
});
|
||||
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: MessageType.ERROR,
|
||||
text: 'TODO: Tool approvals not yet implemented, please switch to YOLO mode to test.',
|
||||
}),
|
||||
expect.any(Number),
|
||||
);
|
||||
});
|
||||
|
||||
it('should call session.abort when cancelOngoingRequest is called', async () => {
|
||||
it('should call agent.abort when cancelOngoingRequest is called', async () => {
|
||||
const { result } = await renderHookWithProviders(() =>
|
||||
useAgentStream(
|
||||
{} as GeminiClient,
|
||||
[],
|
||||
mockAddItem,
|
||||
mockConfig,
|
||||
mockSettings,
|
||||
mockOnDebugMessage,
|
||||
mockHandleSlashCommand,
|
||||
false,
|
||||
() => undefined,
|
||||
mockOnAuthError,
|
||||
mockPerformMemoryRefresh,
|
||||
false,
|
||||
mockSetModelSwitchedFromQuotaError,
|
||||
mockOnCancelSubmit,
|
||||
mockSetShellInputFocused,
|
||||
80,
|
||||
24,
|
||||
),
|
||||
useAgentStream({
|
||||
agent: mockLegacyAgentProtocol as unknown as LegacyAgentProtocol,
|
||||
addItem: mockAddItem,
|
||||
onCancelSubmit: mockOnCancelSubmit,
|
||||
isShellFocused: false,
|
||||
}),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.cancelOngoingRequest();
|
||||
});
|
||||
|
||||
expect(mockLegacyAgentSession.abort).toHaveBeenCalled();
|
||||
expect(mockLegacyAgentProtocol.abort).toHaveBeenCalled();
|
||||
expect(mockOnCancelSubmit).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,27 +9,21 @@ import {
|
||||
getErrorMessage,
|
||||
MessageSenderType,
|
||||
debugLogger,
|
||||
LegacyAgentSession,
|
||||
geminiPartsToContentParts,
|
||||
parseThought,
|
||||
CoreToolCallStatus,
|
||||
} from '@google/gemini-cli-core';
|
||||
import {
|
||||
type Config,
|
||||
type GeminiClient,
|
||||
type ApprovalMode,
|
||||
Kind,
|
||||
type EditorType,
|
||||
type ThoughtSummary,
|
||||
type RetryAttemptPayload,
|
||||
type AgentEvent,
|
||||
type AgentProtocol,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { type PartListUnion } from '@google/genai';
|
||||
import type {
|
||||
HistoryItem,
|
||||
HistoryItemWithoutId,
|
||||
LoopDetectionConfirmationRequest,
|
||||
SlashCommandProcessorResult,
|
||||
IndividualToolCallDisplay,
|
||||
HistoryItemToolGroup,
|
||||
} from '../types.js';
|
||||
@@ -39,42 +33,29 @@ import { getToolGroupBorderAppearance } from '../utils/borderStyles.js';
|
||||
import { type BackgroundShell } from './shellCommandProcessor.js';
|
||||
import type { UseHistoryManagerReturn } from './useHistoryManager.js';
|
||||
import { useLogger } from './useLogger.js';
|
||||
import { useToolScheduler } from './useToolScheduler.js';
|
||||
|
||||
import { useSessionStats } from '../contexts/SessionContext.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import { useStateAndRef } from './useStateAndRef.js';
|
||||
import { useConfig } from '../contexts/ConfigContext.js';
|
||||
import { type MinimalTrackedToolCall } from './useTurnActivityMonitor.js';
|
||||
|
||||
export interface UseAgentStreamOptions {
|
||||
agent?: AgentProtocol;
|
||||
addItem: UseHistoryManagerReturn['addItem'];
|
||||
onCancelSubmit: (shouldRestorePrompt?: boolean) => void;
|
||||
isShellFocused?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* useAgentStream implements the interactive agent loop using the LegacyAgentSession (AgentProtocol).
|
||||
* It attempts to maintain parity with useGeminiStream while consolidating model/tool orchestration
|
||||
* into the unified core API.
|
||||
* useAgentStream implements the interactive agent loop using an AgentProtocol.
|
||||
* It is completely agnostic to the specific agent implementation.
|
||||
*/
|
||||
export const useAgentStream = (
|
||||
geminiClient: GeminiClient,
|
||||
_history: HistoryItem[],
|
||||
addItem: UseHistoryManagerReturn['addItem'],
|
||||
config: Config,
|
||||
_settings: LoadedSettings,
|
||||
_onDebugMessage: (message: string) => void,
|
||||
_handleSlashCommand: (
|
||||
cmd: PartListUnion,
|
||||
) => Promise<SlashCommandProcessorResult | false>,
|
||||
_shellModeActive: boolean,
|
||||
getPreferredEditor: () => EditorType | undefined,
|
||||
_onAuthError: (error: string) => void,
|
||||
_performMemoryRefresh: () => Promise<void>,
|
||||
_modelSwitchedFromQuotaError: boolean,
|
||||
_setModelSwitchedFromQuotaError: React.Dispatch<
|
||||
React.SetStateAction<boolean>
|
||||
>,
|
||||
onCancelSubmit: (shouldRestorePrompt?: boolean) => void,
|
||||
_setShellInputFocused: (value: boolean) => void,
|
||||
_terminalWidth: number,
|
||||
_terminalHeight: number,
|
||||
_isShellFocused?: boolean,
|
||||
_consumeUserHint?: () => string | null,
|
||||
) => {
|
||||
export const useAgentStream = ({
|
||||
agent,
|
||||
addItem,
|
||||
onCancelSubmit,
|
||||
isShellFocused,
|
||||
}: UseAgentStreamOptions) => {
|
||||
const config = useConfig();
|
||||
const [initError] = useState<string | null>(null);
|
||||
const [retryStatus] = useState<RetryAttemptPayload | null>(null);
|
||||
const [streamingState, setStreamingState] = useState<StreamingState>(
|
||||
@@ -82,8 +63,6 @@ export const useAgentStream = (
|
||||
);
|
||||
const [thought, setThought] = useState<ThoughtSummary | null>(null);
|
||||
|
||||
// Track the current session instance
|
||||
const sessionRef = useRef<LegacyAgentSession | null>(null);
|
||||
const currentStreamIdRef = useRef<string | null>(null);
|
||||
const userMessageTimestampRef = useRef<number>(0);
|
||||
const geminiMessageBufferRef = useRef<string>('');
|
||||
@@ -98,24 +77,8 @@ export const useAgentStream = (
|
||||
const [_isFirstToolInGroup, isFirstToolInGroupRef, setIsFirstToolInGroup] =
|
||||
useStateAndRef<boolean>(true);
|
||||
|
||||
const [
|
||||
toolCalls,
|
||||
_schedule,
|
||||
_markToolsAsSubmitted,
|
||||
_setToolCallsForDisplay,
|
||||
cancelAllToolCalls,
|
||||
lastOutputTime,
|
||||
scheduler,
|
||||
] = useToolScheduler(
|
||||
async (_completedTools) => {
|
||||
// LegacyAgentSession owns the loop, so we don't need to trigger next turns here.
|
||||
},
|
||||
config,
|
||||
getPreferredEditor,
|
||||
);
|
||||
|
||||
const { startNewPrompt } = useSessionStats();
|
||||
const logger = useLogger(config.storage);
|
||||
const logger = useLogger(config?.storage);
|
||||
|
||||
const activePtyId = undefined;
|
||||
const backgroundShellCount = 0;
|
||||
@@ -128,6 +91,24 @@ export const useAgentStream = (
|
||||
);
|
||||
const dismissBackgroundShell = useCallback(async (_pid: number) => {}, []);
|
||||
|
||||
// Use the trackedTools to mock pendingToolCalls for inactivity monitors
|
||||
const pendingToolCalls = useMemo(
|
||||
(): MinimalTrackedToolCall[] =>
|
||||
trackedTools.map((t) => ({
|
||||
request: {
|
||||
name: t.originalRequestName || t.name,
|
||||
args: { command: t.description },
|
||||
callId: t.callId,
|
||||
isClientInitiated: t.isClientInitiated ?? false,
|
||||
prompt_id: '',
|
||||
},
|
||||
status: t.status,
|
||||
})),
|
||||
[trackedTools],
|
||||
);
|
||||
|
||||
const lastOutputTime = Date.now(); // We could track actual time if needed, simplified for now
|
||||
|
||||
// TODO: Support LoopDetection confirmation requests
|
||||
const [loopDetectionConfirmationRequest] =
|
||||
useState<LoopDetectionConfirmationRequest | null>(null);
|
||||
@@ -141,13 +122,12 @@ export const useAgentStream = (
|
||||
}, [addItem, pendingHistoryItemRef, setPendingHistoryItem]);
|
||||
|
||||
const cancelOngoingRequest = useCallback(async () => {
|
||||
if (sessionRef.current) {
|
||||
await sessionRef.current.abort();
|
||||
cancelAllToolCalls(new AbortController().signal);
|
||||
if (agent) {
|
||||
await agent.abort();
|
||||
setStreamingState(StreamingState.Idle);
|
||||
onCancelSubmit(false);
|
||||
}
|
||||
}, [cancelAllToolCalls, onCancelSubmit]);
|
||||
}, [agent, onCancelSubmit]);
|
||||
|
||||
// TODO: Support native handleApprovalModeChange for Plan Mode
|
||||
const handleApprovalModeChange = useCallback(
|
||||
@@ -308,21 +288,11 @@ export const useAgentStream = (
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (sessionRef.current) {
|
||||
return sessionRef.current.subscribe(handleEvent);
|
||||
if (agent) {
|
||||
return agent.subscribe(handleEvent);
|
||||
}
|
||||
return undefined;
|
||||
}, [handleEvent]);
|
||||
|
||||
// Handle initialization of the session
|
||||
if (!sessionRef.current) {
|
||||
sessionRef.current = new LegacyAgentSession({
|
||||
client: geminiClient,
|
||||
scheduler,
|
||||
config,
|
||||
promptId: '',
|
||||
});
|
||||
}
|
||||
}, [agent, handleEvent]);
|
||||
|
||||
const submitQuery = useCallback(
|
||||
async (
|
||||
@@ -330,7 +300,7 @@ export const useAgentStream = (
|
||||
options?: { isContinuation: boolean },
|
||||
_prompt_id?: string,
|
||||
) => {
|
||||
if (!sessionRef.current) return;
|
||||
if (!agent) return;
|
||||
|
||||
const timestamp = Date.now();
|
||||
userMessageTimestampRef.current = timestamp;
|
||||
@@ -349,7 +319,7 @@ export const useAgentStream = (
|
||||
);
|
||||
|
||||
try {
|
||||
const { streamId } = await sessionRef.current.send({
|
||||
const { streamId } = await agent.send({
|
||||
message: parts,
|
||||
});
|
||||
currentStreamIdRef.current = streamId;
|
||||
@@ -360,7 +330,7 @@ export const useAgentStream = (
|
||||
);
|
||||
}
|
||||
},
|
||||
[addItem, logger, startNewPrompt],
|
||||
[agent, addItem, logger, startNewPrompt],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -415,7 +385,7 @@ export const useAgentStream = (
|
||||
const appearance = getToolGroupBorderAppearance(
|
||||
{ type: 'tool_group', tools: trackedTools },
|
||||
activePtyId,
|
||||
!!_isShellFocused,
|
||||
!!isShellFocused,
|
||||
[],
|
||||
backgroundShells,
|
||||
);
|
||||
@@ -440,7 +410,7 @@ export const useAgentStream = (
|
||||
setIsFirstToolInGroup,
|
||||
addItem,
|
||||
activePtyId,
|
||||
_isShellFocused,
|
||||
isShellFocused,
|
||||
backgroundShells,
|
||||
]);
|
||||
|
||||
@@ -454,7 +424,7 @@ export const useAgentStream = (
|
||||
const appearance = getToolGroupBorderAppearance(
|
||||
{ type: 'tool_group', tools: trackedTools },
|
||||
activePtyId,
|
||||
!!_isShellFocused,
|
||||
!!isShellFocused,
|
||||
[],
|
||||
backgroundShells,
|
||||
);
|
||||
@@ -504,7 +474,7 @@ export const useAgentStream = (
|
||||
trackedTools,
|
||||
pushedToolCallIds,
|
||||
activePtyId,
|
||||
_isShellFocused,
|
||||
isShellFocused,
|
||||
backgroundShells,
|
||||
]);
|
||||
|
||||
@@ -523,7 +493,7 @@ export const useAgentStream = (
|
||||
pendingHistoryItems,
|
||||
thought,
|
||||
cancelOngoingRequest,
|
||||
pendingToolCalls: toolCalls,
|
||||
pendingToolCalls,
|
||||
handleApprovalModeChange,
|
||||
activePtyId,
|
||||
loopDetectionConfirmationRequest,
|
||||
|
||||
@@ -5,20 +5,22 @@
|
||||
*/
|
||||
|
||||
import { useInactivityTimer } from './useInactivityTimer.js';
|
||||
import { useTurnActivityMonitor } from './useTurnActivityMonitor.js';
|
||||
import {
|
||||
useTurnActivityMonitor,
|
||||
type MinimalTrackedToolCall,
|
||||
} from './useTurnActivityMonitor.js';
|
||||
import {
|
||||
SHELL_FOCUS_HINT_DELAY_MS,
|
||||
SHELL_ACTION_REQUIRED_TITLE_DELAY_MS,
|
||||
SHELL_SILENT_WORKING_TITLE_DELAY_MS,
|
||||
} from '../constants.js';
|
||||
import type { StreamingState } from '../types.js';
|
||||
import { type TrackedToolCall } from './useToolScheduler.js';
|
||||
|
||||
interface ShellInactivityStatusProps {
|
||||
activePtyId: number | string | null | undefined;
|
||||
lastOutputTime: number;
|
||||
streamingState: StreamingState;
|
||||
pendingToolCalls: TrackedToolCall[];
|
||||
pendingToolCalls: MinimalTrackedToolCall[];
|
||||
embeddedShellFocused: boolean;
|
||||
isInteractiveShellEnabled: boolean;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,16 @@
|
||||
|
||||
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { StreamingState } from '../types.js';
|
||||
import { hasRedirection } from '@google/gemini-cli-core';
|
||||
import { type TrackedToolCall } from './useToolScheduler.js';
|
||||
import {
|
||||
hasRedirection,
|
||||
type CoreToolCallStatus,
|
||||
type ToolCallRequestInfo,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
export interface MinimalTrackedToolCall {
|
||||
status: CoreToolCallStatus;
|
||||
request: ToolCallRequestInfo;
|
||||
}
|
||||
|
||||
export interface TurnActivityStatus {
|
||||
operationStartTime: number;
|
||||
@@ -21,7 +29,7 @@ export interface TurnActivityStatus {
|
||||
export const useTurnActivityMonitor = (
|
||||
streamingState: StreamingState,
|
||||
activePtyId: number | string | null | undefined,
|
||||
pendingToolCalls: TrackedToolCall[] = [],
|
||||
pendingToolCalls: MinimalTrackedToolCall[] = [],
|
||||
): TurnActivityStatus => {
|
||||
const [operationStartTime, setOperationStartTime] = useState(0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user