mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-14 13:53:02 -07:00
refactor(core): introduce SubagentState enum for progress
This commit is contained in:
@@ -21,6 +21,7 @@ import {
|
||||
type SubagentProgress,
|
||||
SubagentActivityErrorType,
|
||||
SUBAGENT_REJECTED_ERROR_PREFIX,
|
||||
SubagentState,
|
||||
} from './types.js';
|
||||
import { LocalSubagentInvocation } from './local-invocation.js';
|
||||
import { LocalAgentExecutor } from './local-executor.js';
|
||||
@@ -215,7 +216,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
]);
|
||||
const display = result.returnDisplay as SubagentProgress;
|
||||
expect(display.isSubagentProgress).toBe(true);
|
||||
expect(display.state).toBe('completed');
|
||||
expect(display.state).toBe(SubagentState.COMPLETED);
|
||||
expect(display.result).toBe('Analysis complete.');
|
||||
expect(display.terminateReason).toBe(AgentTerminateMode.GOAL);
|
||||
});
|
||||
@@ -234,7 +235,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
|
||||
const display = result.returnDisplay as SubagentProgress;
|
||||
expect(display.isSubagentProgress).toBe(true);
|
||||
expect(display.state).toBe('completed');
|
||||
expect(display.state).toBe(SubagentState.COMPLETED);
|
||||
expect(display.result).toBe('Partial progress...');
|
||||
expect(display.terminateReason).toBe(AgentTerminateMode.TIMEOUT);
|
||||
});
|
||||
@@ -340,7 +341,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
expect.objectContaining({
|
||||
type: 'thought',
|
||||
content: 'Error: Failed',
|
||||
status: 'error',
|
||||
status: SubagentState.ERROR,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -376,7 +377,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
expect.objectContaining({
|
||||
type: 'tool_call',
|
||||
content: 'ls',
|
||||
status: 'error',
|
||||
status: SubagentState.ERROR,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -418,7 +419,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
expect.objectContaining({
|
||||
type: 'tool_call',
|
||||
content: 'ls',
|
||||
status: 'cancelled',
|
||||
status: SubagentState.CANCELLED,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -443,7 +444,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
expect(result.error).toBeUndefined();
|
||||
const display = result.returnDisplay as SubagentProgress;
|
||||
expect(display.isSubagentProgress).toBe(true);
|
||||
expect(display.state).toBe('completed');
|
||||
expect(display.state).toBe(SubagentState.COMPLETED);
|
||||
expect(display.result).toBe('Done');
|
||||
});
|
||||
|
||||
@@ -466,7 +467,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
expect.objectContaining({
|
||||
type: 'thought',
|
||||
content: `Error: ${error.message}`,
|
||||
status: 'error',
|
||||
status: SubagentState.ERROR,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -488,7 +489,7 @@ describe('LocalSubagentInvocation', () => {
|
||||
expect(display.recentActivity).toContainEqual(
|
||||
expect.objectContaining({
|
||||
content: `Error: ${creationError.message}`,
|
||||
status: 'error',
|
||||
status: SubagentState.ERROR,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
SUBAGENT_REJECTED_ERROR_PREFIX,
|
||||
SUBAGENT_CANCELLED_ERROR_MESSAGE,
|
||||
isToolActivityError,
|
||||
SubagentState,
|
||||
} from './types.js';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import type { z } from 'zod';
|
||||
@@ -117,7 +118,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
isSubagentProgress: true,
|
||||
agentName: this.definition.name,
|
||||
recentActivity: [],
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
};
|
||||
updateOutput(initialProgress);
|
||||
}
|
||||
@@ -137,7 +138,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
if (
|
||||
lastItem &&
|
||||
lastItem.type === 'thought' &&
|
||||
lastItem.status === 'running'
|
||||
lastItem.status === SubagentState.RUNNING
|
||||
) {
|
||||
lastItem.content = sanitizeThoughtContent(text);
|
||||
} else {
|
||||
@@ -145,7 +146,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
id: randomUUID(),
|
||||
type: 'thought',
|
||||
content: sanitizeThoughtContent(text),
|
||||
status: 'running',
|
||||
status: SubagentState.RUNNING,
|
||||
});
|
||||
}
|
||||
updated = true;
|
||||
@@ -174,7 +175,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
displayName,
|
||||
description,
|
||||
args,
|
||||
status: 'running',
|
||||
status: SubagentState.RUNNING,
|
||||
});
|
||||
updated = true;
|
||||
|
||||
@@ -193,9 +194,11 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
if (
|
||||
recentActivity[i].type === 'tool_call' &&
|
||||
recentActivity[i].content === name &&
|
||||
recentActivity[i].status === 'running'
|
||||
recentActivity[i].status === SubagentState.RUNNING
|
||||
) {
|
||||
recentActivity[i].status = isError ? 'error' : 'completed';
|
||||
recentActivity[i].status = isError
|
||||
? SubagentState.ERROR
|
||||
: SubagentState.COMPLETED;
|
||||
updated = true;
|
||||
|
||||
this.publishActivity(recentActivity[i]);
|
||||
@@ -224,9 +227,9 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
if (
|
||||
recentActivity[i].type === 'tool_call' &&
|
||||
recentActivity[i].content === toolName &&
|
||||
recentActivity[i].status === 'running'
|
||||
recentActivity[i].status === SubagentState.RUNNING
|
||||
) {
|
||||
recentActivity[i].status = 'cancelled';
|
||||
recentActivity[i].status = SubagentState.CANCELLED;
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
@@ -237,9 +240,9 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
if (
|
||||
recentActivity[i].type === 'tool_call' &&
|
||||
recentActivity[i].content === toolName &&
|
||||
recentActivity[i].status === 'running'
|
||||
recentActivity[i].status === SubagentState.RUNNING
|
||||
) {
|
||||
recentActivity[i].status = 'error';
|
||||
recentActivity[i].status = SubagentState.ERROR;
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
@@ -253,7 +256,10 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
isCancellation || isRejection
|
||||
? sanitizedError
|
||||
: `Error: ${sanitizedError}`,
|
||||
status: isCancellation || isRejection ? 'cancelled' : 'error',
|
||||
status:
|
||||
isCancellation || isRejection
|
||||
? SubagentState.CANCELLED
|
||||
: SubagentState.ERROR,
|
||||
});
|
||||
updated = true;
|
||||
break;
|
||||
@@ -267,7 +273,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
isSubagentProgress: true,
|
||||
agentName: this.definition.name,
|
||||
recentActivity: [...recentActivity], // Copy to avoid mutation issues
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
};
|
||||
|
||||
updateOutput(progress);
|
||||
@@ -287,7 +293,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
isSubagentProgress: true,
|
||||
agentName: this.definition.name,
|
||||
recentActivity: [...recentActivity],
|
||||
state: 'cancelled',
|
||||
state: SubagentState.CANCELLED,
|
||||
};
|
||||
|
||||
if (updateOutput) {
|
||||
@@ -303,7 +309,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
|
||||
isSubagentProgress: true,
|
||||
agentName: this.definition.name,
|
||||
recentActivity: [...recentActivity],
|
||||
state: 'completed',
|
||||
state: SubagentState.COMPLETED,
|
||||
result: output.result,
|
||||
terminateReason: output.terminate_reason,
|
||||
};
|
||||
@@ -334,8 +340,8 @@ ${output.result}`;
|
||||
|
||||
// Mark any running items as error/cancelled
|
||||
for (const item of recentActivity) {
|
||||
if (item.status === 'running') {
|
||||
item.status = isAbort ? 'cancelled' : 'error';
|
||||
if (item.status === SubagentState.RUNNING) {
|
||||
item.status = isAbort ? SubagentState.CANCELLED : SubagentState.ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,12 +349,12 @@ ${output.result}`;
|
||||
// But only if it's NOT an abort, or if we want to show "Cancelled" as a thought
|
||||
if (!isAbort) {
|
||||
const lastActivity = recentActivity[recentActivity.length - 1];
|
||||
if (!lastActivity || lastActivity.status !== 'error') {
|
||||
if (!lastActivity || lastActivity.status !== SubagentState.ERROR) {
|
||||
recentActivity.push({
|
||||
id: randomUUID(),
|
||||
type: 'thought',
|
||||
content: `Error: ${errorMessage}`,
|
||||
status: 'error',
|
||||
status: SubagentState.ERROR,
|
||||
});
|
||||
// Maintain size limit
|
||||
// No limit on UI events sent via bus
|
||||
@@ -359,7 +365,7 @@ ${output.result}`;
|
||||
isSubagentProgress: true,
|
||||
agentName: this.definition.name,
|
||||
recentActivity: [...recentActivity],
|
||||
state: isAbort ? 'cancelled' : 'error',
|
||||
state: isAbort ? SubagentState.CANCELLED : SubagentState.ERROR,
|
||||
};
|
||||
|
||||
if (updateOutput) {
|
||||
|
||||
@@ -20,7 +20,11 @@ import {
|
||||
type A2AClientManager,
|
||||
} from './a2a-client-manager.js';
|
||||
|
||||
import type { RemoteAgentDefinition, SubagentProgress } from './types.js';
|
||||
import {
|
||||
type RemoteAgentDefinition,
|
||||
type SubagentProgress,
|
||||
SubagentState,
|
||||
} from './types.js';
|
||||
import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
|
||||
import { A2AAuthProviderFactory } from './auth-provider/factory.js';
|
||||
import type { A2AAuthProvider } from './auth-provider/types.js';
|
||||
@@ -268,7 +272,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({ state: 'error' });
|
||||
expect(result.returnDisplay).toMatchObject({
|
||||
state: SubagentState.ERROR,
|
||||
});
|
||||
expect((result.returnDisplay as SubagentProgress).result).toContain(
|
||||
"Failed to create auth provider for agent 'test-agent'",
|
||||
);
|
||||
@@ -461,7 +467,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
expect(updateOutput).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
isSubagentProgress: true,
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
recentActivity: expect.arrayContaining([
|
||||
expect.objectContaining({ content: 'Working...' }),
|
||||
]),
|
||||
@@ -470,7 +476,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
expect(updateOutput).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
isSubagentProgress: true,
|
||||
state: 'completed',
|
||||
state: SubagentState.COMPLETED,
|
||||
result: 'HelloHello World',
|
||||
}),
|
||||
);
|
||||
@@ -508,7 +514,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
abortSignal: controller.signal,
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({ state: 'error' });
|
||||
expect(result.returnDisplay).toMatchObject({
|
||||
state: SubagentState.ERROR,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
@@ -533,7 +541,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({
|
||||
state: 'error',
|
||||
state: SubagentState.ERROR,
|
||||
result: expect.stringContaining('Network error'),
|
||||
});
|
||||
});
|
||||
@@ -616,7 +624,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
expect(updateOutput).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
isSubagentProgress: true,
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
recentActivity: expect.arrayContaining([
|
||||
expect.objectContaining({ content: 'Working...' }),
|
||||
]),
|
||||
@@ -625,7 +633,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
expect(updateOutput).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
isSubagentProgress: true,
|
||||
state: 'completed',
|
||||
state: SubagentState.COMPLETED,
|
||||
result: 'Thinking...Final Answer',
|
||||
}),
|
||||
);
|
||||
@@ -693,7 +701,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
expect(updateOutput).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
isSubagentProgress: true,
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
recentActivity: expect.arrayContaining([
|
||||
expect.objectContaining({ content: 'Working...' }),
|
||||
]),
|
||||
@@ -702,7 +710,7 @@ describe('RemoteAgentInvocation', () => {
|
||||
expect(updateOutput).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
isSubagentProgress: true,
|
||||
state: 'completed',
|
||||
state: SubagentState.COMPLETED,
|
||||
result: 'Generating...\n\nArtifact (Result):\nPart 1 Part 2',
|
||||
}),
|
||||
);
|
||||
@@ -760,7 +768,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({ state: 'error' });
|
||||
expect(result.returnDisplay).toMatchObject({
|
||||
state: SubagentState.ERROR,
|
||||
});
|
||||
expect((result.returnDisplay as SubagentProgress).result).toContain(
|
||||
a2aError.userMessage,
|
||||
);
|
||||
@@ -782,7 +792,9 @@ describe('RemoteAgentInvocation', () => {
|
||||
abortSignal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(result.returnDisplay).toMatchObject({ state: 'error' });
|
||||
expect(result.returnDisplay).toMatchObject({
|
||||
state: SubagentState.ERROR,
|
||||
});
|
||||
expect((result.returnDisplay as SubagentProgress).result).toContain(
|
||||
'Error calling remote agent: something unexpected',
|
||||
);
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
type RemoteAgentDefinition,
|
||||
type AgentInputs,
|
||||
type SubagentProgress,
|
||||
SubagentState,
|
||||
getAgentCardLoadOptions,
|
||||
getRemoteAgentTargetUrl,
|
||||
} from './types.js';
|
||||
@@ -138,13 +139,13 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
|
||||
updateOutput({
|
||||
isSubagentProgress: true,
|
||||
agentName,
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
recentActivity: [
|
||||
{
|
||||
id: 'pending',
|
||||
type: 'thought',
|
||||
content: 'Working...',
|
||||
status: 'running',
|
||||
status: SubagentState.RUNNING,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -193,7 +194,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
|
||||
updateOutput({
|
||||
isSubagentProgress: true,
|
||||
agentName,
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
recentActivity: reassembler.toActivityItems(),
|
||||
result: reassembler.toString(),
|
||||
});
|
||||
@@ -225,7 +226,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
|
||||
const finalProgress: SubagentProgress = {
|
||||
isSubagentProgress: true,
|
||||
agentName,
|
||||
state: 'completed',
|
||||
state: SubagentState.COMPLETED,
|
||||
result: finalOutput,
|
||||
recentActivity: reassembler.toActivityItems(),
|
||||
};
|
||||
@@ -249,7 +250,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation<
|
||||
const errorProgress: SubagentProgress = {
|
||||
isSubagentProgress: true,
|
||||
agentName,
|
||||
state: 'error',
|
||||
state: SubagentState.ERROR,
|
||||
result: fullDisplay,
|
||||
recentActivity: reassembler.toActivityItems(),
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
DEFAULT_QUERY_STRING,
|
||||
type RemoteAgentDefinition,
|
||||
type SubagentProgress,
|
||||
SubagentState,
|
||||
getRemoteAgentTargetUrl,
|
||||
getAgentCardLoadOptions,
|
||||
} from './types.js';
|
||||
@@ -233,7 +234,7 @@ class RemoteSubagentProtocol implements AgentProtocol {
|
||||
this._latestProgress = {
|
||||
isSubagentProgress: true,
|
||||
agentName: this._agentName,
|
||||
state: 'running',
|
||||
state: SubagentState.RUNNING,
|
||||
recentActivity: reassembler.toActivityItems(),
|
||||
result: currentText,
|
||||
};
|
||||
@@ -259,7 +260,7 @@ class RemoteSubagentProtocol implements AgentProtocol {
|
||||
const finalProgress: SubagentProgress = {
|
||||
isSubagentProgress: true,
|
||||
agentName: this._agentName,
|
||||
state: 'completed',
|
||||
state: SubagentState.COMPLETED,
|
||||
result: finalOutput,
|
||||
recentActivity: reassembler.toActivityItems(),
|
||||
};
|
||||
|
||||
@@ -88,6 +88,13 @@ export interface SubagentActivityEvent {
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export enum SubagentState {
|
||||
RUNNING = 'running',
|
||||
COMPLETED = 'completed',
|
||||
ERROR = 'error',
|
||||
CANCELLED = 'cancelled',
|
||||
}
|
||||
|
||||
export interface SubagentActivityItem {
|
||||
id: string;
|
||||
type: 'thought' | 'tool_call';
|
||||
@@ -95,14 +102,14 @@ export interface SubagentActivityItem {
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
args?: string;
|
||||
status: 'running' | 'completed' | 'error' | 'cancelled';
|
||||
status: SubagentState;
|
||||
}
|
||||
|
||||
export interface SubagentProgress {
|
||||
isSubagentProgress: true;
|
||||
agentName: string;
|
||||
recentActivity: SubagentActivityItem[];
|
||||
state?: 'running' | 'completed' | 'error' | 'cancelled';
|
||||
state?: SubagentState;
|
||||
result?: string;
|
||||
terminateReason?: AgentTerminateMode;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user