Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions pkg/concurrent/slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,10 @@ func (s *Slice[V]) Update(index int, f func(V) V) bool {
s.values[index] = f(s.values[index])
return true
}

func (s *Slice[V]) Clear() {
s.mu.Lock()
defer s.mu.Unlock()

s.values = nil
}
20 changes: 20 additions & 0 deletions pkg/tools/builtin/todo.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,32 @@ func (h *todoHandler) updateTodos(_ context.Context, params UpdateTodosArgs) (*t
return tools.ResultError(output.String()), nil
}

// Clear all todos if all are completed
if h.allCompleted() {
h.todos.Clear()
}

return &tools.ToolCallResult{
Output: output.String(),
Meta: h.todos.All(),
}, nil
}

func (h *todoHandler) allCompleted() bool {
if h.todos.Length() == 0 {
return false
}
allDone := true
h.todos.Range(func(_ int, todo Todo) bool {
if todo.Status != "completed" {
allDone = false
return false
}
return true
})
return allDone
}

func (h *todoHandler) listTodos(_ context.Context, _ tools.ToolCall) (*tools.ToolCallResult, error) {
var output strings.Builder
output.WriteString("Current todos:\n")
Expand Down
40 changes: 35 additions & 5 deletions pkg/tools/builtin/todo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,9 @@ func TestTodoTool_UpdateTodos(t *testing.T) {
func TestTodoTool_UpdateTodos_PartialFailure(t *testing.T) {
tool := NewTodoTool()

// Create a single todo
_, err := tool.handler.createTodo(t.Context(), CreateTodoArgs{
Description: "Test todo item",
// Create two todos so we can complete one without clearing the list
_, err := tool.handler.createTodos(t.Context(), CreateTodosArgs{
Descriptions: []string{"First todo item", "Second todo item"},
})
require.NoError(t, err)

Expand All @@ -185,10 +185,11 @@ func TestTodoTool_UpdateTodos_PartialFailure(t *testing.T) {
assert.Contains(t, result.Output, "Updated 1 todos")
assert.Contains(t, result.Output, "Not found: nonexistent")

// Verify the existing todo was updated
// Verify the existing todo was updated (list not cleared because todo_2 still pending)
todos := tool.handler.todos.All()
require.Len(t, todos, 1)
require.Len(t, todos, 2)
assert.Equal(t, "completed", todos[0].Status)
assert.Equal(t, "pending", todos[1].Status)
}

func TestTodoTool_UpdateTodos_AllNotFound(t *testing.T) {
Expand All @@ -206,6 +207,35 @@ func TestTodoTool_UpdateTodos_AllNotFound(t *testing.T) {
assert.Contains(t, result.Output, "Not found: nonexistent1, nonexistent2")
}

func TestTodoTool_UpdateTodos_ClearsWhenAllCompleted(t *testing.T) {
tool := NewTodoTool()

// Create multiple todos
_, err := tool.handler.createTodos(t.Context(), CreateTodosArgs{
Descriptions: []string{"First todo item", "Second todo item"},
})
require.NoError(t, err)

// Complete all todos
result, err := tool.handler.updateTodos(t.Context(), UpdateTodosArgs{
Updates: []TodoUpdate{
{ID: "todo_1", Status: "completed"},
{ID: "todo_2", Status: "completed"},
},
})
require.NoError(t, err)
assert.Contains(t, result.Output, "Updated 2 todos")

// Verify all todos were cleared
todos := tool.handler.todos.All()
assert.Empty(t, todos)

// Verify Meta is also empty
metaTodos, ok := result.Meta.([]Todo)
require.True(t, ok, "Meta should be []Todo")
assert.Empty(t, metaTodos)
}

func TestTodoTool_OutputSchema(t *testing.T) {
tool := NewTodoTool()

Expand Down