mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-14 22:02:59 -07:00
feat(agent): formalize first-class tool lifecycle states and status mapping (#24993)
This commit is contained in:
@@ -224,14 +224,17 @@ export const useAgentStream = ({
|
||||
if (tc.callId !== event.requestId) return tc;
|
||||
|
||||
const legacyState = event._meta?.legacyState;
|
||||
const evtStatus = legacyState?.status;
|
||||
|
||||
let status = tc.status;
|
||||
if (evtStatus === 'executing')
|
||||
if (event.status === 'executing')
|
||||
status = CoreToolCallStatus.Executing;
|
||||
else if (evtStatus === 'error') status = CoreToolCallStatus.Error;
|
||||
else if (evtStatus === 'success')
|
||||
else if (event.status === 'pending_input')
|
||||
status = CoreToolCallStatus.AwaitingApproval;
|
||||
else if (event.status === 'errored')
|
||||
status = CoreToolCallStatus.Error;
|
||||
else if (event.status === 'succeeded')
|
||||
status = CoreToolCallStatus.Success;
|
||||
else if (event.status === 'aborted')
|
||||
status = CoreToolCallStatus.Cancelled;
|
||||
|
||||
const display = event.display?.result;
|
||||
const liveOutput =
|
||||
@@ -272,11 +275,16 @@ export const useAgentStream = ({
|
||||
const resultDisplay =
|
||||
displayContentToString(display) ?? tc.resultDisplay;
|
||||
|
||||
let status = CoreToolCallStatus.Success;
|
||||
if (event.status === 'errored') status = CoreToolCallStatus.Error;
|
||||
else if (event.status === 'aborted')
|
||||
status = CoreToolCallStatus.Cancelled;
|
||||
else if (event.status === 'succeeded')
|
||||
status = CoreToolCallStatus.Success;
|
||||
|
||||
return {
|
||||
...tc,
|
||||
status: event.isError
|
||||
? CoreToolCallStatus.Error
|
||||
: CoreToolCallStatus.Success,
|
||||
status,
|
||||
display: event.display
|
||||
? { ...tc.display, ...event.display }
|
||||
: tc.display,
|
||||
|
||||
@@ -235,6 +235,7 @@ export function translateEvent(
|
||||
makeEvent('tool_request', state, {
|
||||
requestId: event.value.callId,
|
||||
name: event.value.name,
|
||||
status: 'pending',
|
||||
args: event.value.args,
|
||||
display: event.value.display,
|
||||
}),
|
||||
@@ -257,6 +258,7 @@ export function translateEvent(
|
||||
makeEvent('tool_response', state, {
|
||||
requestId: event.value.callId,
|
||||
name: state.pendingToolNames.get(event.value.callId) ?? 'unknown',
|
||||
status: event.value.error ? 'errored' : 'succeeded',
|
||||
content: event.value.error
|
||||
? [{ type: 'text', text: event.value.error.message }]
|
||||
: geminiPartsToContentParts(event.value.responseParts),
|
||||
|
||||
@@ -39,7 +39,12 @@ import type {
|
||||
ContentPart,
|
||||
StreamEndReason,
|
||||
Unsubscribe,
|
||||
ToolEventStatus,
|
||||
} from './types.js';
|
||||
import {
|
||||
MessageBusType,
|
||||
type ToolCallsUpdateMessage,
|
||||
} from '../confirmation-bus/types.js';
|
||||
|
||||
function isAbortLikeError(err: unknown): boolean {
|
||||
return err instanceof Error && err.name === 'AbortError';
|
||||
@@ -64,6 +69,7 @@ export class LegacyAgentProtocol implements AgentProtocol {
|
||||
private _activeStreamId?: string;
|
||||
private _abortController = new AbortController();
|
||||
private _nextStreamIdOverride?: string;
|
||||
private _lastToolStatuses = new Map<string, ToolEventStatus>();
|
||||
|
||||
private readonly _client: GeminiClient;
|
||||
private readonly _scheduler: Scheduler;
|
||||
@@ -92,6 +98,11 @@ export class LegacyAgentProtocol implements AgentProtocol {
|
||||
}
|
||||
this._scheduler = scheduler;
|
||||
}
|
||||
|
||||
this._config.messageBus.subscribe(
|
||||
MessageBusType.TOOL_CALLS_UPDATE,
|
||||
this._handleToolCallsUpdate.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
get events(): readonly AgentEvent[] {
|
||||
@@ -274,6 +285,11 @@ export class LegacyAgentProtocol implements AgentProtocol {
|
||||
this._makeToolResponseEvent({
|
||||
requestId: request.callId,
|
||||
name: request.name,
|
||||
status: response.error
|
||||
? 'errored'
|
||||
: tc.status === 'cancelled'
|
||||
? 'aborted'
|
||||
: 'succeeded',
|
||||
content,
|
||||
isError: response.error !== undefined,
|
||||
...(display ? { display } : {}),
|
||||
@@ -487,6 +503,71 @@ export class LegacyAgentProtocol implements AgentProtocol {
|
||||
} satisfies AgentEvent<'error'>;
|
||||
return event;
|
||||
}
|
||||
|
||||
private _handleToolCallsUpdate(msg: ToolCallsUpdateMessage): void {
|
||||
if (!this._activeStreamId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventsToEmit: AgentEvent[] = [];
|
||||
|
||||
for (const tc of msg.toolCalls) {
|
||||
const callId = tc.request.callId;
|
||||
let status: ToolEventStatus = 'pending';
|
||||
if (tc.status === 'validating' || tc.status === 'scheduled') {
|
||||
status = 'pending';
|
||||
} else if (tc.status === 'awaiting_approval') {
|
||||
status = 'pending_input';
|
||||
} else if (tc.status === 'executing') {
|
||||
status = 'executing';
|
||||
} else if (tc.status === 'success') {
|
||||
status = 'succeeded';
|
||||
} else if (tc.status === 'error') {
|
||||
status = 'errored';
|
||||
} else if (tc.status === 'cancelled') {
|
||||
status = 'aborted';
|
||||
}
|
||||
|
||||
const lastStatus = this._lastToolStatuses.get(callId);
|
||||
|
||||
if (lastStatus !== status) {
|
||||
this._lastToolStatuses.set(callId, status);
|
||||
|
||||
const display = populateToolDisplay({
|
||||
name: tc.request.name,
|
||||
invocation: 'invocation' in tc ? tc.invocation : undefined,
|
||||
displayName: 'tool' in tc ? tc.tool?.displayName : undefined,
|
||||
display: 'response' in tc ? tc.response?.display : undefined,
|
||||
});
|
||||
|
||||
eventsToEmit.push(
|
||||
this._makeToolUpdateEvent({
|
||||
requestId: callId,
|
||||
status,
|
||||
...(display ? { display } : {}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (eventsToEmit.length > 0) {
|
||||
this._emit(eventsToEmit);
|
||||
}
|
||||
}
|
||||
|
||||
private _makeToolUpdateEvent(
|
||||
payload: Omit<
|
||||
AgentEvent<'tool_update'>,
|
||||
'id' | 'timestamp' | 'streamId' | 'type'
|
||||
>,
|
||||
): AgentEvent<'tool_update'> {
|
||||
const event = {
|
||||
...this._nextEventFields(),
|
||||
type: 'tool_update',
|
||||
...payload,
|
||||
} satisfies AgentEvent<'tool_update'>;
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
||||
export class LegacyAgentSession extends AgentSession {
|
||||
|
||||
@@ -227,11 +227,21 @@ export interface ToolDisplay {
|
||||
format?: ToolDisplayFormat;
|
||||
}
|
||||
|
||||
export type ToolEventStatus =
|
||||
| 'pending'
|
||||
| 'pending_input'
|
||||
| 'executing'
|
||||
| 'succeeded'
|
||||
| 'errored'
|
||||
| 'aborted';
|
||||
|
||||
export interface ToolRequest {
|
||||
/** A unique identifier for this tool request to be correlated by the response. */
|
||||
requestId: string;
|
||||
/** The name of the tool being requested. */
|
||||
name: string;
|
||||
/** The status of the tool execution. */
|
||||
status: ToolEventStatus;
|
||||
/** The arguments for the tool. */
|
||||
/** Tool-controlled display information. */
|
||||
display?: ToolDisplay;
|
||||
@@ -255,6 +265,8 @@ export interface ToolRequest {
|
||||
*/
|
||||
export interface ToolUpdate {
|
||||
requestId: string;
|
||||
/** The status of the tool execution. */
|
||||
status: ToolEventStatus;
|
||||
/** Tool-controlled display information. */
|
||||
display?: ToolDisplay;
|
||||
content?: ContentPart[];
|
||||
@@ -276,6 +288,8 @@ export interface ToolUpdate {
|
||||
export interface ToolResponse {
|
||||
requestId: string;
|
||||
name: string;
|
||||
/** The status of the tool execution. */
|
||||
status: ToolEventStatus;
|
||||
/** Tool-controlled display information. */
|
||||
display?: ToolDisplay;
|
||||
/** Multi-part content to be sent to the model. */
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
import { writeFileSync, existsSync, cpSync } from 'node:fs';
|
||||
import { writeFileSync, existsSync, cpSync, rmSync } from 'node:fs';
|
||||
import { join, basename } from 'node:path';
|
||||
|
||||
if (!process.cwd().includes('packages')) {
|
||||
@@ -48,7 +48,14 @@ if (packageName === 'core') {
|
||||
const docsSource = join(process.cwd(), '..', '..', 'docs');
|
||||
const docsTarget = join(process.cwd(), 'dist', 'docs');
|
||||
if (existsSync(docsSource)) {
|
||||
cpSync(docsSource, docsTarget, { recursive: true, dereference: true });
|
||||
if (existsSync(docsTarget)) {
|
||||
rmSync(docsTarget, { recursive: true, force: true });
|
||||
}
|
||||
cpSync(docsSource, docsTarget, {
|
||||
recursive: true,
|
||||
dereference: true,
|
||||
force: true,
|
||||
});
|
||||
console.log('Copied documentation to dist/docs');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user