summaryrefslogtreecommitdiffhomepage
path: root/packages/core/src/tools/task-list.ts
blob: 29f15438b2246537bb89ae2a63ea1abee3dcd6d6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import { z } from "zod";
import type { TaskItem, TaskStatus, ToolDefinition } from "../types/index.js";

export class TaskList {
	private tasks: TaskItem[] = [];
	private counter = 0;
	private listeners: Array<(tasks: TaskItem[]) => void> = [];

	private notify(): void {
		const snapshot = this.getTasks();
		for (const listener of this.listeners) {
			listener(snapshot);
		}
	}

	getTasks(): TaskItem[] {
		return [...this.tasks];
	}

	getTask(id: string): TaskItem | undefined {
		return this.tasks.find((t) => t.id === id);
	}

	addTask(title: string, description: string): TaskItem {
		this.counter++;
		const task: TaskItem = {
			id: `task-${this.counter}`,
			title,
			description,
			status: "pending",
		};
		this.tasks.push(task);
		this.notify();
		return task;
	}

	updateTask(id: string, status: TaskStatus): TaskItem | undefined {
		const task = this.tasks.find((t) => t.id === id);
		if (!task) return undefined;
		task.status = status;
		this.notify();
		return { ...task };
	}

	removeTask(id: string): boolean {
		const index = this.tasks.findIndex((t) => t.id === id);
		if (index === -1) return false;
		this.tasks.splice(index, 1);
		this.notify();
		return true;
	}

	onChange(callback: (tasks: TaskItem[]) => void): () => void {
		this.listeners.push(callback);
		return () => {
			this.listeners = this.listeners.filter((l) => l !== callback);
		};
	}
}

export function createTaskListTool(taskList: TaskList): ToolDefinition {
	return {
		name: "todo",
		description:
			"Manage a todo list for planning and tracking work. Add items, update their status, list all items, or get details on a specific item.",
		parameters: z.object({
			action: z.enum(["add", "update", "list", "get", "remove"]).describe("The action to perform"),
			title: z.string().optional().describe("Task title (required for 'add')"),
			description: z
				.string()
				.optional()
				.describe("Task description (for 'add', defaults to empty)"),
			task_id: z.string().optional().describe("Task ID (required for 'update', 'get', 'remove')"),
			status: z
				.enum(["pending", "in_progress", "done"])
				.optional()
				.describe("New status (required for 'update')"),
		}),
		execute: async (args: Record<string, unknown>): Promise<string> => {
			const action = args.action as string;

			if (action === "add") {
				const title = args.title as string | undefined;
				if (!title) {
					return "Error: 'title' is required for the 'add' action.";
				}
				const description = (args.description as string | undefined) ?? "";
				const task = taskList.addTask(title, description);
				return JSON.stringify(task);
			}

			if (action === "update") {
				const task_id = args.task_id as string | undefined;
				const status = args.status as TaskStatus | undefined;
				if (!task_id) {
					return "Error: 'task_id' is required for the 'update' action.";
				}
				if (!status) {
					return "Error: 'status' is required for the 'update' action.";
				}
				const updated = taskList.updateTask(task_id, status);
				if (!updated) {
					return `Error: Task with ID '${task_id}' not found.`;
				}
				return JSON.stringify(updated);
			}

			if (action === "get") {
				const task_id = args.task_id as string | undefined;
				if (!task_id) {
					return "Error: 'task_id' is required for the 'get' action.";
				}
				const task = taskList.getTask(task_id);
				if (!task) {
					return `Error: Task with ID '${task_id}' not found.`;
				}
				return JSON.stringify(task);
			}

			if (action === "list") {
				const tasks = taskList.getTasks();
				if (tasks.length === 0) {
					return "No tasks.";
				}
				return JSON.stringify(tasks);
			}

			if (action === "remove") {
				const task_id = args.task_id as string | undefined;
				if (!task_id) {
					return "Error: 'task_id' is required for the 'remove' action.";
				}
				const removed = taskList.removeTask(task_id);
				if (!removed) {
					return `Error: Task with ID '${task_id}' not found.`;
				}
				return `Task '${task_id}' removed successfully.`;
			}

			return `Error: Unknown action '${action}'.`;
		},
	};
}