Files
gemini-cli/packages/core/src/tools/write-todos.ts

146 lines
3.5 KiB
TypeScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {
BaseDeclarativeTool,
BaseToolInvocation,
Kind,
type ToolInvocation,
type Todo,
type ToolResult,
} from './tools.js';
import type { MessageBus } from '../confirmation-bus/message-bus.js';
import { WRITE_TODOS_TOOL_NAME } from './tool-names.js';
import { WRITE_TODOS_DEFINITION } from './definitions/coreTools.js';
import { resolveToolDeclaration } from './definitions/resolver.js';
const TODO_STATUSES = [
'pending',
'in_progress',
'completed',
'cancelled',
] as const;
export interface WriteTodosToolParams {
/**
* The full list of todos. This will overwrite any existing list.
*/
todos: Todo[];
}
class WriteTodosToolInvocation extends BaseToolInvocation<
WriteTodosToolParams,
ToolResult
> {
constructor(
params: WriteTodosToolParams,
messageBus: MessageBus,
_toolName?: string,
_toolDisplayName?: string,
) {
super(params, messageBus, _toolName, _toolDisplayName);
}
getDescription(): string {
const count = this.params.todos?.length ?? 0;
if (count === 0) {
return 'Cleared todo list';
}
return `Set ${count} todo(s)`;
}
async execute(
_signal: AbortSignal,
_updateOutput?: (output: string) => void,
): Promise<ToolResult> {
const todos = this.params.todos ?? [];
const todoListString = todos
.map(
(todo, index) => `${index + 1}. [${todo.status}] ${todo.description}`,
)
.join('\n');
const llmContent =
todos.length > 0
? `Successfully updated the todo list. The current list is now:\n${todoListString}`
: 'Successfully cleared the todo list.';
return {
llmContent,
returnDisplay: { todos },
};
}
}
export class WriteTodosTool extends BaseDeclarativeTool<
WriteTodosToolParams,
ToolResult
> {
static readonly Name = WRITE_TODOS_TOOL_NAME;
constructor(messageBus: MessageBus) {
super(
WriteTodosTool.Name,
'WriteTodos',
WRITE_TODOS_DEFINITION.base.description!,
Kind.Other,
WRITE_TODOS_DEFINITION.base.parametersJsonSchema,
messageBus,
true, // isOutputMarkdown
false, // canUpdateOutput
);
}
override getSchema(modelId?: string) {
return resolveToolDeclaration(WRITE_TODOS_DEFINITION, modelId);
}
protected override validateToolParamValues(
params: WriteTodosToolParams,
): string | null {
const todos = params?.todos;
if (!params || !Array.isArray(todos)) {
return '`todos` parameter must be an array';
}
for (const todo of todos) {
if (typeof todo !== 'object' || todo === null) {
return 'Each todo item must be an object';
}
if (typeof todo.description !== 'string' || !todo.description.trim()) {
return 'Each todo must have a non-empty description string';
}
if (!TODO_STATUSES.includes(todo.status)) {
return `Each todo must have a valid status (${TODO_STATUSES.join(', ')})`;
}
}
const inProgressCount = todos.filter(
(todo: Todo) => todo.status === 'in_progress',
).length;
if (inProgressCount > 1) {
return 'Invalid parameters: Only one task can be "in_progress" at a time.';
}
return null;
}
protected createInvocation(
params: WriteTodosToolParams,
messageBus: MessageBus,
_toolName?: string,
_displayName?: string,
): ToolInvocation<WriteTodosToolParams, ToolResult> {
return new WriteTodosToolInvocation(
params,
messageBus,
_toolName,
_displayName,
);
}
}