diff options
Diffstat (limited to 'packages/cli/src/ui/commands/backgroundCommand.ts')
| -rw-r--r-- | packages/cli/src/ui/commands/backgroundCommand.ts | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/packages/cli/src/ui/commands/backgroundCommand.ts b/packages/cli/src/ui/commands/backgroundCommand.ts new file mode 100644 index 00000000..cbeb18df --- /dev/null +++ b/packages/cli/src/ui/commands/backgroundCommand.ts @@ -0,0 +1,262 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SlashCommand, CommandContext } from './types.js'; +import { + Config, + BackgroundAgentMessage, + partListUnionToString, +} from '@google/gemini-cli-core'; + +const MAX_STATUS_MESSAGE_LENGTH = 100; + +function toMessageString(message?: BackgroundAgentMessage): string { + return partListUnionToString(message?.parts ?? []).trim(); +} + +function toOneliner(input: string, maxlength: number) { + let output = input.replace(/\r?\n|\r/g, ' '); + if (output.length > maxlength) { + output = output.substring(0, maxlength) + '...'; + } + return output; +} + +function getActiveAgent(context: CommandContext) { + const agent = + context.services.config?.getBackgroundAgentManager()?.activeAgent; + if (!agent) { + throw Error('There is no active background agent.'); + } + return agent; +} + +function addClientHistory(context: CommandContext, text: string) { + context.services.config!.getGeminiClient().addHistory({ + role: 'user', + parts: [{ text }], + }); + + context.services.config!.getGeminiClient().addHistory({ + role: 'model', + parts: [{ text: 'Got it.' }], + }); +} + +const startSubcommand: SlashCommand = { + name: 'start', + description: + 'Start a new task with the provided prompt. Usage: /bg start <prompt>', + action: async (context, args) => { + if (!args || args.trim() === '') { + return { + type: 'message', + messageType: 'error', + content: 'The `start` command requires a prompt.', + }; + } + + const agent = getActiveAgent(context); + const task = await agent.startTask(args); + + addClientHistory( + context, + `I started a background task with id '${task.id}' and prompt:\n${args}`, + ); + + return { + type: 'message', + messageType: 'info', + content: `Started background task with id '${task.id}' and prompt:\n${args}`, + }; + }, +}; + +const stopSubcommand: SlashCommand = { + name: 'stop', + description: 'Stops a running task. Usage: /bg stop <task_id>', + action: async (context, args) => { + if (!args || args.trim() === '') { + return { + type: 'message', + messageType: 'error', + content: 'The `stop` command requires a task id.', + }; + } + const agent = getActiveAgent(context); + await agent.cancelTask(args); + addClientHistory(context, `I canceled the background task with id ${args}`); + return { + type: 'message', + messageType: 'info', + content: `Stopped background task with id ${args}.`, + }; + }, +}; + +const listSubcommand: SlashCommand = { + name: 'list', + description: 'List all tasks', + action: async (context, args) => { + if (args && args.trim() !== '') { + return { + type: 'message', + messageType: 'error', + content: 'The `list` command takes no arguments.', + }; + } + + const agent = getActiveAgent(context); + const tasks = await agent.listTasks(); + let content: string; + if (tasks.length === 0) { + content = 'No background tasks found.'; + } else { + const taskList = tasks + .map((task) => { + const shortStatus = toOneliner( + toMessageString(task.status.message), + MAX_STATUS_MESSAGE_LENGTH, + ); + return ` - ${task.id}: (${task.status.state}) ${shortStatus}`; + }) + .join('\n'); + content = `Background tasks:\n${taskList}`; + } + return { + type: 'message', + messageType: 'info', + content, + }; + }, +}; + +const getSubcommand: SlashCommand = { + name: 'get', + description: 'View a task. Usage: /bg get <task_id>', + action: async (context, args) => { + if (!args || args.trim() === '') { + return { + type: 'message', + messageType: 'error', + content: 'The `get` command requires a task id.', + }; + } + const agent = getActiveAgent(context); + const task = await agent.getTask(args); + const content = `Task Details for ${task.id}: +Status: (${task.status.state}) ${toMessageString(task.status.message)}}`; + + return { + type: 'message', + messageType: 'info', + content, + }; + }, +}; + +const logsSubcommand: SlashCommand = { + name: 'logs', + description: "View a task's recent logs. Usage: /bg log <task_id>", + action: async (context, args) => { + if (!args || args.trim() === '') { + return { + type: 'message', + messageType: 'error', + content: 'The `log` command requires a task id.', + }; + } + const agent = getActiveAgent(context); + const task = await agent.getTask(args, 5); + const contents = [ + `Task logs for ${task.id}. status: (${task.status.state})`, + ]; + (task.history ?? []).forEach((message) => { + contents.push(toMessageString(message)); + }); + return { + type: 'message', + messageType: 'info', + content: contents.join('\n\n'), + }; + }, +}; + +const messageSubcommand: SlashCommand = { + name: 'message', + description: + 'Send a message to a task. Usage: /bg message <task_id> <message>', + action: async (context, args) => { + if (!args || args.trim() === '' || !args.trim().includes(' ')) { + return { + type: 'message', + messageType: 'error', + content: 'The `message` command requires a task id and a message.', + }; + } + + const firstSpaceIndex = args.indexOf(' '); + const id = args.substring(0, firstSpaceIndex); + const message = args.substring(firstSpaceIndex + 1); + + const agent = getActiveAgent(context); + await agent.messageTask(id, message); + addClientHistory( + context, + `I sent a message to the background task with id '${id}':\n${message}`, + ); + + return { + type: 'message', + messageType: 'info', + content: `Sent a message to the background task with id '${id}':\n${message}`, + }; + }, +}; + +const deleteSubcommand: SlashCommand = { + name: 'delete', + description: 'Deletes a task. Usage: /bg delete <task_id>', + action: async (context, args) => { + if (!args) { + return { + type: 'message', + messageType: 'error', + content: 'The `delete` command requires a task id.', + }; + } + const agent = getActiveAgent(context); + await agent.deleteTask(args); + addClientHistory(context, `I deleted the background task with id ${args}`); + return { + type: 'message', + messageType: 'info', + content: `Task ${args} deleted.`, + }; + }, +}; + +export const backgroundCommand = ( + config: Config | null, +): SlashCommand | null => { + if (!config?.getBackgroundAgentManager()?.activeAgent) { + return null; + } + return { + name: 'background', + altName: 'bg', + description: "Commands for managing the background agent's tasks", + subCommands: [ + startSubcommand, + stopSubcommand, + listSubcommand, + getSubcommand, + logsSubcommand, + messageSubcommand, + deleteSubcommand, + ], + }; +}; |
