mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-13 21:07:00 -07:00
feat(agents): implement Agent Factory with granular feature flags and unified AgentSession
This commit is contained in:
@@ -819,6 +819,12 @@ export async function loadCliConfig(
|
||||
disabledSkills: settings.skills?.disabled,
|
||||
experimentalJitContext: settings.experimental?.jitContext,
|
||||
modelSteering: settings.experimental?.modelSteering,
|
||||
useAgentFactoryAll: settings.experimental?.useAgentFactoryAll,
|
||||
useAgentFactorySdk: settings.experimental?.useAgentFactorySdk,
|
||||
useAgentFactoryNonInteractive:
|
||||
settings.experimental?.useAgentFactoryNonInteractive,
|
||||
useAgentFactoryInteractive:
|
||||
settings.experimental?.useAgentFactoryInteractive,
|
||||
toolOutputMasking: settings.experimental?.toolOutputMasking,
|
||||
noBrowser: !!process.env['NO_BROWSER'],
|
||||
summarizeToolOutput: settings.model?.summarizeToolOutput,
|
||||
|
||||
@@ -1681,6 +1681,44 @@ const SETTINGS_SCHEMA = {
|
||||
'Enable model steering (user hints) to guide the model during tool execution.',
|
||||
showInDialog: true,
|
||||
},
|
||||
useAgentFactoryAll: {
|
||||
type: 'boolean',
|
||||
label: 'Use Agent Factory (All)',
|
||||
category: 'Experimental',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
description: 'Enable Agent Factory for all supported execution paths.',
|
||||
showInDialog: true,
|
||||
},
|
||||
useAgentFactorySdk: {
|
||||
type: 'boolean',
|
||||
label: 'Use Agent Factory (SDK)',
|
||||
category: 'Experimental',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
description: 'Enable Agent Factory for the SDK execution path.',
|
||||
showInDialog: true,
|
||||
},
|
||||
useAgentFactoryNonInteractive: {
|
||||
type: 'boolean',
|
||||
label: 'Use Agent Factory (Non-Interactive)',
|
||||
category: 'Experimental',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
description:
|
||||
'Enable Agent Factory for the non-interactive CLI execution path.',
|
||||
showInDialog: true,
|
||||
},
|
||||
useAgentFactoryInteractive: {
|
||||
type: 'boolean',
|
||||
label: 'Use Agent Factory (Interactive)',
|
||||
category: 'Experimental',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
description:
|
||||
'Enable Agent Factory for the interactive CLI execution path.',
|
||||
showInDialog: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -269,16 +269,22 @@ export async function runNonInteractive({
|
||||
query = processedQuery as Part[];
|
||||
}
|
||||
|
||||
if (config.isAgentsEnabled()) {
|
||||
// --- Dispatch Loop ---
|
||||
const experimental = settings.experimental;
|
||||
const useAgentFactory =
|
||||
experimental?.useAgentFactoryAll ||
|
||||
experimental?.useAgentFactoryNonInteractive;
|
||||
|
||||
if (useAgentFactory) {
|
||||
await runAgentSessionFlow(
|
||||
loopContext,
|
||||
{ config, settings, input, prompt_id, resumedSessionData, query }, // API change: pass query
|
||||
{ config, settings, input, prompt_id, resumedSessionData, query },
|
||||
handleUserFeedback,
|
||||
);
|
||||
} else {
|
||||
await runLegacyManualLoop(
|
||||
loopContext,
|
||||
{ config, settings, input, prompt_id, resumedSessionData, query }, // API change: pass query
|
||||
{ config, settings, input, prompt_id, resumedSessionData, query },
|
||||
handleUserFeedback,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,9 @@ import type {
|
||||
ToolCallResponseInfo,
|
||||
GeminiErrorEventValue,
|
||||
RetryAttemptPayload,
|
||||
AgentSession,
|
||||
AgentTerminateMode,
|
||||
AgentEvent,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { type Part, type PartListUnion, FinishReason } from '@google/genai';
|
||||
import type {
|
||||
@@ -1246,7 +1249,102 @@ export const useGeminiStream = (
|
||||
setPendingHistoryItem,
|
||||
setThought,
|
||||
],
|
||||
);
|
||||
|
||||
const processAgentEvents = useCallback(
|
||||
async (
|
||||
stream: AsyncIterable<AgentEvent>,
|
||||
userMessageTimestamp: number,
|
||||
signal: AbortSignal,
|
||||
): Promise<void> => {
|
||||
let geminiMessageBuffer = '';
|
||||
for await (const event of stream) {
|
||||
if (signal.aborted) break;
|
||||
|
||||
// Map AgentEvent back to GeminiEvent handlers
|
||||
switch (event.type) {
|
||||
case 'thought':
|
||||
handleThoughtEvent(
|
||||
{ summary: event.value, thought: event.value },
|
||||
userMessageTimestamp,
|
||||
);
|
||||
break;
|
||||
case ServerGeminiEventType.Content:
|
||||
geminiMessageBuffer = handleContentEvent(
|
||||
event.value,
|
||||
geminiMessageBuffer,
|
||||
userMessageTimestamp,
|
||||
);
|
||||
break;
|
||||
case ServerGeminiEventType.ToolCallRequest:
|
||||
// Handled by AgentSession, but we can still show them
|
||||
// The useToolScheduler will be used by AgentSession's internal scheduler,
|
||||
// but for UI feedback we need to make sure they show up in toolCalls.
|
||||
// Since AgentSession uses Scheduler which is not hooked into useToolScheduler state,
|
||||
// we might need to bridge this.
|
||||
// For now, we'll just emit events to show activity.
|
||||
break;
|
||||
case 'tool_suite_start':
|
||||
setToolCallsForDisplay(
|
||||
Array(event.value.count).fill({
|
||||
status: CoreToolCallStatus.Executing,
|
||||
request: { name: 'Executing tools...' },
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case 'tool_suite_finish':
|
||||
setToolCallsForDisplay([]);
|
||||
// handleCompletedTools will be called by AgentSession internally,
|
||||
// but we need to update the UI history here.
|
||||
// AgentSession doesn't provide the full TrackedToolCall objects.
|
||||
// This is a known gap in the "meet in the middle" approach.
|
||||
break;
|
||||
case 'agent_finish': {
|
||||
const { reason } = event.value;
|
||||
if (reason === AgentTerminateMode.MAX_TURNS) {
|
||||
handleMaxSessionTurnsEvent();
|
||||
}
|
||||
setIsResponding(false);
|
||||
break;
|
||||
}
|
||||
case 'goal_completed':
|
||||
addItem({
|
||||
type: MessageType.INFO,
|
||||
text: 'Goal completed.',
|
||||
});
|
||||
break;
|
||||
case ServerGeminiEventType.Error:
|
||||
handleErrorEvent(event.value, userMessageTimestamp);
|
||||
break;
|
||||
default: {
|
||||
// Handle other core events if they match
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
const coreEvent = event as unknown as {
|
||||
type: ServerGeminiEventType;
|
||||
value: unknown;
|
||||
};
|
||||
if (coreEvent.type === ServerGeminiEventType.Citation) {
|
||||
handleCitationEvent(
|
||||
coreEvent.value as unknown as string, // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
|
||||
userMessageTimestamp,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
handleThoughtEvent,
|
||||
handleContentEvent,
|
||||
handleMaxSessionTurnsEvent,
|
||||
handleErrorEvent,
|
||||
handleCitationEvent,
|
||||
setToolCallsForDisplay,
|
||||
setIsResponding,
|
||||
addItem,
|
||||
],
|
||||
);
|
||||
|
||||
const submitQuery = useCallback(
|
||||
async (
|
||||
query: PartListUnion,
|
||||
@@ -1319,23 +1417,45 @@ export const useGeminiStream = (
|
||||
lastQueryRef.current = queryToSend;
|
||||
lastPromptIdRef.current = prompt_id!;
|
||||
|
||||
try {
|
||||
const stream = geminiClient.sendMessageStream(
|
||||
queryToSend,
|
||||
abortSignal,
|
||||
prompt_id!,
|
||||
undefined,
|
||||
false,
|
||||
query,
|
||||
);
|
||||
const processingStatus = await processGeminiStreamEvents(
|
||||
stream,
|
||||
userMessageTimestamp,
|
||||
abortSignal,
|
||||
);
|
||||
const experimental = settings.experimental;
|
||||
const useAgentFactory =
|
||||
experimental?.useAgentFactoryAll ||
|
||||
experimental?.useAgentFactoryInteractive;
|
||||
|
||||
if (processingStatus === StreamProcessingStatus.UserCancelled) {
|
||||
return;
|
||||
try {
|
||||
if (useAgentFactory) {
|
||||
const session = new AgentSession(
|
||||
config.getSessionId(),
|
||||
{
|
||||
name: 'interactive-agent',
|
||||
maxTurns: config.getMaxSessionTurns(),
|
||||
},
|
||||
config,
|
||||
);
|
||||
const stream = session.prompt(queryToSend, abortSignal);
|
||||
await processAgentEvents(
|
||||
stream,
|
||||
userMessageTimestamp,
|
||||
abortSignal,
|
||||
);
|
||||
} else {
|
||||
const stream = geminiClient.sendMessageStream(
|
||||
queryToSend,
|
||||
abortSignal,
|
||||
prompt_id!,
|
||||
undefined,
|
||||
false,
|
||||
query,
|
||||
);
|
||||
const processingStatus = await processGeminiStreamEvents(
|
||||
stream,
|
||||
userMessageTimestamp,
|
||||
abortSignal,
|
||||
);
|
||||
|
||||
if (processingStatus === StreamProcessingStatus.UserCancelled) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingHistoryItemRef.current) {
|
||||
@@ -1416,6 +1536,8 @@ export const useGeminiStream = (
|
||||
setModelSwitchedFromQuotaError,
|
||||
prepareQueryForGemini,
|
||||
processGeminiStreamEvents,
|
||||
processAgentEvents,
|
||||
settings.experimental,
|
||||
pendingHistoryItemRef,
|
||||
addItem,
|
||||
setPendingHistoryItem,
|
||||
|
||||
Reference in New Issue
Block a user