/ tools / TaskListTool / TaskListTool.ts
TaskListTool.ts
  1  import { z } from 'zod/v4'
  2  import { buildTool, type ToolDef } from '../../Tool.js'
  3  import { lazySchema } from '../../utils/lazySchema.js'
  4  import {
  5    getTaskListId,
  6    isTodoV2Enabled,
  7    listTasks,
  8    TaskStatusSchema,
  9  } from '../../utils/tasks.js'
 10  import { TASK_LIST_TOOL_NAME } from './constants.js'
 11  import { DESCRIPTION, getPrompt } from './prompt.js'
 12  
 13  const inputSchema = lazySchema(() => z.strictObject({}))
 14  type InputSchema = ReturnType<typeof inputSchema>
 15  
 16  const outputSchema = lazySchema(() =>
 17    z.object({
 18      tasks: z.array(
 19        z.object({
 20          id: z.string(),
 21          subject: z.string(),
 22          status: TaskStatusSchema(),
 23          owner: z.string().optional(),
 24          blockedBy: z.array(z.string()),
 25        }),
 26      ),
 27    }),
 28  )
 29  type OutputSchema = ReturnType<typeof outputSchema>
 30  
 31  export type Output = z.infer<OutputSchema>
 32  
 33  export const TaskListTool = buildTool({
 34    name: TASK_LIST_TOOL_NAME,
 35    searchHint: 'list all tasks',
 36    maxResultSizeChars: 100_000,
 37    async description() {
 38      return DESCRIPTION
 39    },
 40    async prompt() {
 41      return getPrompt()
 42    },
 43    get inputSchema(): InputSchema {
 44      return inputSchema()
 45    },
 46    get outputSchema(): OutputSchema {
 47      return outputSchema()
 48    },
 49    userFacingName() {
 50      return 'TaskList'
 51    },
 52    shouldDefer: true,
 53    isEnabled() {
 54      return isTodoV2Enabled()
 55    },
 56    isConcurrencySafe() {
 57      return true
 58    },
 59    isReadOnly() {
 60      return true
 61    },
 62    renderToolUseMessage() {
 63      return null
 64    },
 65    async call() {
 66      const taskListId = getTaskListId()
 67  
 68      const allTasks = (await listTasks(taskListId)).filter(
 69        t => !t.metadata?._internal,
 70      )
 71  
 72      // Build a set of resolved task IDs for filtering
 73      const resolvedTaskIds = new Set(
 74        allTasks.filter(t => t.status === 'completed').map(t => t.id),
 75      )
 76  
 77      const tasks = allTasks.map(task => ({
 78        id: task.id,
 79        subject: task.subject,
 80        status: task.status,
 81        owner: task.owner,
 82        blockedBy: task.blockedBy.filter(id => !resolvedTaskIds.has(id)),
 83      }))
 84  
 85      return {
 86        data: {
 87          tasks,
 88        },
 89      }
 90    },
 91    mapToolResultToToolResultBlockParam(content, toolUseID) {
 92      const { tasks } = content as Output
 93      if (tasks.length === 0) {
 94        return {
 95          tool_use_id: toolUseID,
 96          type: 'tool_result',
 97          content: 'No tasks found',
 98        }
 99      }
100  
101      const lines = tasks.map(task => {
102        const owner = task.owner ? ` (${task.owner})` : ''
103        const blocked =
104          task.blockedBy.length > 0
105            ? ` [blocked by ${task.blockedBy.map(id => `#${id}`).join(', ')}]`
106            : ''
107        return `#${task.id} [${task.status}] ${task.subject}${owner}${blocked}`
108      })
109  
110      return {
111        tool_use_id: toolUseID,
112        type: 'tool_result',
113        content: lines.join('\n'),
114      }
115    },
116  } satisfies ToolDef<InputSchema, Output>)