Download packages/core/src/agents/local-invocation.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 12.4 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/core/src/agents/local-invocation.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/core/src/agents/local-invocation.ts
-
curl -L -o local-invocation.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/core/src/agents/local-invocation.ts
12.4 kB
| /** | |
| * @license | |
| * Copyright 2026 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import { type AgentLoopContext } from '../config/agent-loop-context.js'; | |
| import { MessageBusType } from '../confirmation-bus/types.js'; | |
| import { LocalAgentExecutor } from './local-executor.js'; | |
| import { | |
| BaseToolInvocation, | |
| type ToolResult, | |
| type ExecuteOptions, | |
| } from '../tools/tools.js'; | |
| import { | |
| type LocalAgentDefinition, | |
| type AgentInputs, | |
| type SubagentActivityEvent, | |
| type SubagentProgress, | |
| type SubagentActivityItem, | |
| AgentTerminateMode, | |
| SubagentActivityErrorType, | |
| SUBAGENT_REJECTED_ERROR_PREFIX, | |
| SUBAGENT_CANCELLED_ERROR_MESSAGE, | |
| isToolActivityError, | |
| SubagentState, | |
| } from './types.js'; | |
| import { randomUUID } from 'node:crypto'; | |
| import type { z } from 'zod'; | |
| import type { MessageBus } from '../confirmation-bus/message-bus.js'; | |
| import { | |
| sanitizeThoughtContent, | |
| sanitizeToolArgs, | |
| sanitizeErrorMessage, | |
| } from '../utils/agent-sanitization-utils.js'; | |
| import { debugLogger } from '../utils/debugLogger.js'; | |
| /** | |
| * Represents a validated, executable instance of a subagent tool. | |
| * | |
| * This class orchestrates the execution of a defined agent by: | |
| * 1. Initializing the {@link LocalAgentExecutor}. | |
| * 2. Running the agent's execution loop. | |
| * 3. Bridging the agent's streaming activity (e.g., thoughts) to the tool's | |
| * live output stream. | |
| * 4. Formatting the final result into a {@link ToolResult}. | |
| */ | |
| export class LocalSubagentInvocation extends BaseToolInvocation< | |
| AgentInputs, | |
| ToolResult | |
| > { | |
| /** | |
| * @param definition The definition object that configures the agent. | |
| * @param context The agent loop context. | |
| * @param params The validated input parameters for the agent. | |
| * @param messageBus Message bus for policy enforcement. | |
| */ | |
| constructor( | |
| private readonly definition: LocalAgentDefinition, | |
| private readonly context: AgentLoopContext, | |
| params: AgentInputs, | |
| messageBus: MessageBus, | |
| _toolName?: string, | |
| _toolDisplayName?: string, | |
| ) { | |
| super( | |
| params, | |
| messageBus, | |
| _toolName ?? definition.name, | |
| _toolDisplayName ?? definition.displayName, | |
| ); | |
| } | |
| /** | |
| * Returns a concise, human-readable description of the invocation. | |
| * Used for logging and display purposes. | |
| */ | |
| getDescription(): string { | |
| const inputSummary = Object.entries(this.params) | |
| .map(([key, value]) => `${key}: ${String(value)}`) | |
| .join(', '); | |
| return `Running subagent '${this.definition.name}' with inputs: { ${inputSummary} }`; | |
| } | |
| private publishActivity(activity: SubagentActivityItem): void { | |
| void this.messageBus.publish({ | |
| type: MessageBusType.SUBAGENT_ACTIVITY, | |
| subagentName: this.definition.displayName ?? this.definition.name, | |
| activity, | |
| }); | |
| } | |
| /** | |
| * Executes the subagent. | |
| * | |
| * @param signal An `AbortSignal` to cancel the agent's execution. | |
| * @param updateOutput A callback to stream intermediate output, such as the | |
| * agent's thoughts, to the user interface. | |
| * @returns A `Promise` that resolves with the final `ToolResult`. | |
| */ | |
| async execute(options: ExecuteOptions): Promise<ToolResult> { | |
| const { abortSignal: signal, updateOutput } = options; | |
| const recentActivity: SubagentActivityItem[] = []; | |
| let executor: LocalAgentExecutor<z.ZodUnknown> | undefined; | |
| try { | |
| if (updateOutput) { | |
| // Send initial state | |
| const initialProgress: SubagentProgress = { | |
| isSubagentProgress: true, | |
| agentName: this.definition.name, | |
| recentActivity: [], | |
| state: SubagentState.RUNNING, | |
| }; | |
| updateOutput(initialProgress); | |
| } | |
| // Create an activity callback to bridge the executor's events to the | |
| // tool's streaming output. | |
| const onActivity = (activity: SubagentActivityEvent): void => { | |
| if (!updateOutput) return; | |
| let updated = false; | |
| switch (activity.type) { | |
| case 'THOUGHT_CHUNK': { | |
| const text = String(activity.data['text']); | |
| const lastItem = recentActivity[recentActivity.length - 1]; | |
| if ( | |
| lastItem && | |
| lastItem.type === 'thought' && | |
| lastItem.status === SubagentState.RUNNING | |
| ) { | |
| lastItem.content = sanitizeThoughtContent(text); | |
| } else { | |
| recentActivity.push({ | |
| id: randomUUID(), | |
| type: 'thought', | |
| content: sanitizeThoughtContent(text), | |
| status: SubagentState.RUNNING, | |
| }); | |
| } | |
| updated = true; | |
| const latestThought = recentActivity[recentActivity.length - 1]; | |
| if (latestThought) { | |
| this.publishActivity(latestThought); | |
| } | |
| break; | |
| } | |
| case 'TOOL_CALL_START': { | |
| const name = String(activity.data['name']); | |
| const displayName = activity.data['displayName'] | |
| ? sanitizeErrorMessage(String(activity.data['displayName'])) | |
| : undefined; | |
| const description = activity.data['description'] | |
| ? sanitizeErrorMessage(String(activity.data['description'])) | |
| : undefined; | |
| const args = JSON.stringify( | |
| sanitizeToolArgs(activity.data['args']), | |
| ); | |
| const callId = activity.data['callId'] | |
| ? String(activity.data['callId']) | |
| : randomUUID(); | |
| recentActivity.push({ | |
| id: callId, | |
| type: 'tool_call', | |
| content: name, | |
| displayName, | |
| description, | |
| args, | |
| status: SubagentState.RUNNING, | |
| }); | |
| updated = true; | |
| const latestTool = recentActivity[recentActivity.length - 1]; | |
| if (latestTool) { | |
| this.publishActivity(latestTool); | |
| } | |
| break; | |
| } | |
| case 'TOOL_CALL_END': { | |
| const data = activity.data['data']; | |
| const isError = isToolActivityError(data); | |
| const callId = activity.data['id'] | |
| ? String(activity.data['id']) | |
| : undefined; | |
| if (callId) { | |
| for (let i = recentActivity.length - 1; i >= 0; i--) { | |
| if ( | |
| recentActivity[i].type === 'tool_call' && | |
| recentActivity[i].id === callId && | |
| recentActivity[i].status === SubagentState.RUNNING | |
| ) { | |
| recentActivity[i].status = isError | |
| ? SubagentState.ERROR | |
| : SubagentState.COMPLETED; | |
| updated = true; | |
| this.publishActivity(recentActivity[i]); | |
| break; | |
| } | |
| } | |
| } | |
| break; | |
| } | |
| case 'ERROR': { | |
| const error = String(activity.data['error']); | |
| const errorType = activity.data['errorType']; | |
| const sanitizedError = sanitizeErrorMessage(error); | |
| const isCancellation = | |
| errorType === SubagentActivityErrorType.CANCELLED || | |
| error === SUBAGENT_CANCELLED_ERROR_MESSAGE; | |
| const isRejection = | |
| errorType === SubagentActivityErrorType.REJECTED || | |
| error.startsWith(SUBAGENT_REJECTED_ERROR_PREFIX); | |
| const callId = activity.data['callId'] | |
| ? String(activity.data['callId']) | |
| : undefined; | |
| if (callId) { | |
| const targetStatus = | |
| isCancellation || isRejection | |
| ? SubagentState.CANCELLED | |
| : SubagentState.ERROR; | |
| for (let i = recentActivity.length - 1; i >= 0; i--) { | |
| if ( | |
| recentActivity[i].type === 'tool_call' && | |
| recentActivity[i].id === callId && | |
| recentActivity[i].status === SubagentState.RUNNING | |
| ) { | |
| recentActivity[i].status = targetStatus; | |
| updated = true; | |
| break; | |
| } | |
| } | |
| } | |
| recentActivity.push({ | |
| id: randomUUID(), | |
| type: 'thought', | |
| content: | |
| isCancellation || isRejection | |
| ? sanitizedError | |
| : `Error: ${sanitizedError}`, | |
| status: | |
| isCancellation || isRejection | |
| ? SubagentState.CANCELLED | |
| : SubagentState.ERROR, | |
| }); | |
| updated = true; | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| if (updated) { | |
| const progress: SubagentProgress = { | |
| isSubagentProgress: true, | |
| agentName: this.definition.name, | |
| recentActivity: [...recentActivity], // Copy to avoid mutation issues | |
| state: SubagentState.RUNNING, | |
| }; | |
| updateOutput(progress); | |
| } | |
| }; | |
| executor = await LocalAgentExecutor.create( | |
| this.definition, | |
| this.context, | |
| onActivity, | |
| ); | |
| const output = await executor.run(this.params, signal); | |
| if (output.terminate_reason === AgentTerminateMode.ABORTED) { | |
| const progress: SubagentProgress = { | |
| isSubagentProgress: true, | |
| agentName: this.definition.name, | |
| recentActivity: [...recentActivity], | |
| state: SubagentState.CANCELLED, | |
| }; | |
| if (updateOutput) { | |
| updateOutput(progress); | |
| } | |
| const cancelError = new Error('Operation cancelled by user'); | |
| cancelError.name = 'AbortError'; | |
| throw cancelError; | |
| } | |
| const progress: SubagentProgress = { | |
| isSubagentProgress: true, | |
| agentName: this.definition.name, | |
| recentActivity: [...recentActivity], | |
| state: SubagentState.COMPLETED, | |
| result: output.result, | |
| terminateReason: output.terminate_reason, | |
| }; | |
| if (updateOutput) { | |
| updateOutput(progress); | |
| } | |
| const resultContent = `Subagent '${this.definition.name}' finished. | |
| Termination Reason: ${output.terminate_reason} | |
| Result: | |
| ${output.result}`; | |
| return { | |
| llmContent: [{ text: resultContent }], | |
| returnDisplay: progress, | |
| data: { agentId: executor.agentId }, | |
| }; | |
| } catch (error) { | |
| const errorMessage = | |
| error instanceof Error ? error.message : String(error); | |
| debugLogger.error(`Subagent '${this.definition.name}' failed:`, error); | |
| const isAbort = | |
| (error instanceof Error && error.name === 'AbortError') || | |
| errorMessage.includes('Aborted'); | |
| // Mark any running items as error/cancelled | |
| for (const item of recentActivity) { | |
| if (item.status === SubagentState.RUNNING) { | |
| item.status = isAbort ? SubagentState.CANCELLED : SubagentState.ERROR; | |
| } | |
| } | |
| // Ensure the error is reflected in the recent activity for display | |
| // 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 !== SubagentState.ERROR) { | |
| recentActivity.push({ | |
| id: randomUUID(), | |
| type: 'thought', | |
| content: `Error: ${errorMessage}`, | |
| status: SubagentState.ERROR, | |
| }); | |
| // Maintain size limit | |
| // No limit on UI events sent via bus | |
| } | |
| } | |
| const progress: SubagentProgress = { | |
| isSubagentProgress: true, | |
| agentName: this.definition.name, | |
| recentActivity: [...recentActivity], | |
| state: isAbort ? SubagentState.CANCELLED : SubagentState.ERROR, | |
| }; | |
| if (updateOutput) { | |
| updateOutput(progress); | |
| } | |
| if (isAbort) { | |
| throw error; | |
| } | |
| return { | |
| llmContent: `Subagent '${this.definition.name}' failed. Error: ${errorMessage}`, | |
| returnDisplay: progress, | |
| data: executor ? { agentId: executor.agentId } : undefined, | |
| // We omit the 'error' property so that the UI renders our rich returnDisplay | |
| // instead of the raw error message. The llmContent still informs the agent of the failure. | |
| }; | |
| } | |
| } | |
| } | |