this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(tools): add save_item and update_item tools

CRUD operations for task items:
- save_item: Creates new items (brain_dump, task, subtask)
- update_item: Updates status, priority, content, parent
- User isolation via userId on all operations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

alice a95f44b9 e9721f1c

+195
+195
src/tools/items.ts
··· 1 + /** 2 + * Items tools for Letta agents 3 + * 4 + * Provides tools for managing tasks, brain dumps, and subtasks: 5 + * - save_item: Save a new item to the database 6 + * - update_item: Update an existing item's status, content, or priority 7 + */ 8 + 9 + import { eq } from 'drizzle-orm'; 10 + import { db, schema } from '../db'; 11 + import { registerTool, type ToolDefinition } from './dispatcher'; 12 + 13 + /** 14 + * Arguments for save_item tool 15 + */ 16 + export interface SaveItemArgs { 17 + /** Type of item to save */ 18 + type: 'brain_dump' | 'task' | 'subtask'; 19 + /** Item content */ 20 + content: string; 21 + /** Optional priority (0-4, default 2) */ 22 + priority?: number; 23 + /** Parent item ID for subtasks */ 24 + parentId?: string; 25 + } 26 + 27 + /** 28 + * Result from save_item tool 29 + */ 30 + export interface SaveItemResult { 31 + /** ID of saved item */ 32 + id: string; 33 + /** Success message */ 34 + message: string; 35 + } 36 + 37 + /** 38 + * Arguments for update_item tool 39 + */ 40 + export interface UpdateItemArgs { 41 + /** ID of item to update */ 42 + id: string; 43 + /** New status */ 44 + status?: 'open' | 'in_progress' | 'done' | 'archived'; 45 + /** Updated content */ 46 + content?: string; 47 + /** Updated priority (0-4) */ 48 + priority?: number; 49 + } 50 + 51 + /** 52 + * Result from update_item tool 53 + */ 54 + export interface UpdateItemResult { 55 + /** ID of updated item */ 56 + id: string; 57 + /** Success message */ 58 + message: string; 59 + } 60 + 61 + /** 62 + * save_item tool - Save a new task, brain dump, or subtask 63 + */ 64 + export const saveItemTool: ToolDefinition<SaveItemArgs, SaveItemResult> = registerTool({ 65 + name: 'save_item', 66 + description: 'Save a new task, brain dump, or subtask to the database', 67 + parameters: { 68 + type: 'object', 69 + properties: { 70 + type: { 71 + type: 'string', 72 + enum: ['brain_dump', 'task', 'subtask'], 73 + description: 'Type of item to save', 74 + }, 75 + content: { 76 + type: 'string', 77 + description: 'Item content', 78 + }, 79 + priority: { 80 + type: 'integer', 81 + minimum: 0, 82 + maximum: 4, 83 + description: 'Priority 0-4 (0=critical, 4=backlog, default 2=medium)', 84 + }, 85 + parentId: { 86 + type: 'string', 87 + description: 'Parent item ID for subtasks', 88 + }, 89 + }, 90 + required: ['type', 'content'], 91 + }, 92 + handler: async (args, context) => { 93 + // Generate a new UUID for the item 94 + const id = crypto.randomUUID(); 95 + 96 + // Validate subtask has parentId 97 + if ( 98 + args.type === 'subtask' && 99 + (args.parentId === undefined || (typeof args.parentId === 'string' && args.parentId.length === 0)) 100 + ) { 101 + throw new Error('Subtasks must have a parentId'); 102 + } 103 + 104 + // Insert the item into the database 105 + await db.insert(schema.items).values({ 106 + id, 107 + userId: context.userId, 108 + type: args.type, 109 + content: args.content, 110 + status: 'open', 111 + priority: args.priority ?? 2, 112 + parentId: args.parentId ?? null, 113 + }); 114 + 115 + return { 116 + id, 117 + message: `Successfully saved ${args.type} with ID: ${id}`, 118 + }; 119 + }, 120 + }); 121 + 122 + /** 123 + * update_item tool - Update an existing item 124 + */ 125 + export const updateItemTool: ToolDefinition<UpdateItemArgs, UpdateItemResult> = registerTool({ 126 + name: 'update_item', 127 + description: "Update an existing item's status, content, or priority", 128 + parameters: { 129 + type: 'object', 130 + properties: { 131 + id: { 132 + type: 'string', 133 + description: 'Item ID to update', 134 + }, 135 + status: { 136 + type: 'string', 137 + enum: ['open', 'in_progress', 'done', 'archived'], 138 + description: 'New status for the item', 139 + }, 140 + content: { 141 + type: 'string', 142 + description: 'Updated content', 143 + }, 144 + priority: { 145 + type: 'integer', 146 + minimum: 0, 147 + maximum: 4, 148 + description: 'Updated priority 0-4 (0=critical, 4=backlog)', 149 + }, 150 + }, 151 + required: ['id'], 152 + }, 153 + handler: async (args, context) => { 154 + // First, verify the item exists and belongs to the user 155 + const existingItem = await db.query.items.findFirst({ 156 + where: eq(schema.items.id, args.id), 157 + }); 158 + 159 + if (!existingItem) { 160 + throw new Error(`Item with ID '${args.id}' not found`); 161 + } 162 + 163 + if (existingItem.userId !== context.userId) { 164 + throw new Error(`Item with ID '${args.id}' does not belong to user ${String(context.userId)}`); 165 + } 166 + 167 + // Build the update object with only provided fields 168 + const updates: { 169 + status?: 'open' | 'in_progress' | 'done' | 'archived'; 170 + content?: string; 171 + priority?: number; 172 + updatedAt?: Date; 173 + } = { 174 + updatedAt: new Date(), // Always update the timestamp 175 + }; 176 + 177 + if (args.status !== undefined) { 178 + updates.status = args.status; 179 + } 180 + if (args.content !== undefined) { 181 + updates.content = args.content; 182 + } 183 + if (args.priority !== undefined) { 184 + updates.priority = args.priority; 185 + } 186 + 187 + // Update the item in the database 188 + await db.update(schema.items).set(updates).where(eq(schema.items.id, args.id)); 189 + 190 + return { 191 + id: args.id, 192 + message: `Successfully updated item ${args.id}`, 193 + }; 194 + }, 195 + });