mojentic 0.5.4__py3-none-any.whl → 0.5.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. _examples/broker_examples.py +12 -22
  2. _examples/broker_image_examples.py +41 -0
  3. _examples/ephemeral_task_manager_example.py +48 -0
  4. _examples/streaming.py +1 -1
  5. _examples/tell_user_example.py +43 -0
  6. mojentic/llm/gateways/models.py +29 -1
  7. mojentic/llm/llm_broker_spec.py +0 -49
  8. mojentic/llm/message_composers_spec.py +0 -80
  9. mojentic/llm/tools/ask_user_tool.py +1 -1
  10. mojentic/llm/tools/ephemeral_task_manager/__init__.py +27 -0
  11. mojentic/llm/tools/ephemeral_task_manager/append_task_tool.py +77 -0
  12. mojentic/llm/tools/ephemeral_task_manager/append_task_tool_spec.py +34 -0
  13. mojentic/llm/tools/ephemeral_task_manager/clear_tasks_tool.py +57 -0
  14. mojentic/llm/tools/ephemeral_task_manager/clear_tasks_tool_spec.py +32 -0
  15. mojentic/llm/tools/ephemeral_task_manager/complete_task_tool.py +81 -0
  16. mojentic/llm/tools/ephemeral_task_manager/complete_task_tool_spec.py +43 -0
  17. mojentic/llm/tools/ephemeral_task_manager/ephemeral_task_list.py +202 -0
  18. mojentic/llm/tools/ephemeral_task_manager/ephemeral_task_list_spec.py +137 -0
  19. mojentic/llm/tools/ephemeral_task_manager/insert_task_after_tool.py +84 -0
  20. mojentic/llm/tools/ephemeral_task_manager/insert_task_after_tool_spec.py +42 -0
  21. mojentic/llm/tools/ephemeral_task_manager/list_tasks_tool.py +80 -0
  22. mojentic/llm/tools/ephemeral_task_manager/list_tasks_tool_spec.py +38 -0
  23. mojentic/llm/tools/ephemeral_task_manager/prepend_task_tool.py +77 -0
  24. mojentic/llm/tools/ephemeral_task_manager/prepend_task_tool_spec.py +34 -0
  25. mojentic/llm/tools/ephemeral_task_manager/start_task_tool.py +81 -0
  26. mojentic/llm/tools/ephemeral_task_manager/start_task_tool_spec.py +43 -0
  27. mojentic/llm/tools/llm_tool.py +15 -0
  28. mojentic/llm/tools/llm_tool_spec.py +68 -0
  29. mojentic/llm/tools/organic_web_search.py +37 -0
  30. mojentic/llm/tools/tell_user_tool.py +27 -0
  31. {mojentic-0.5.4.dist-info → mojentic-0.5.6.dist-info}/METADATA +1 -1
  32. {mojentic-0.5.4.dist-info → mojentic-0.5.6.dist-info}/RECORD +35 -13
  33. {mojentic-0.5.4.dist-info → mojentic-0.5.6.dist-info}/WHEEL +1 -1
  34. mojentic/llm/tools/web_search.py +0 -35
  35. {mojentic-0.5.4.dist-info → mojentic-0.5.6.dist-info}/licenses/LICENSE.md +0 -0
  36. {mojentic-0.5.4.dist-info → mojentic-0.5.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,34 @@
1
+ from unittest.mock import Mock
2
+
3
+ import pytest
4
+
5
+ from mojentic.llm.tools.ephemeral_task_manager.append_task_tool import AppendTaskTool
6
+ from mojentic.llm.tools.ephemeral_task_manager.ephemeral_task_list import EphemeralTaskList, Task, TaskStatus
7
+
8
+
9
+ @pytest.fixture
10
+ def mock_task_list():
11
+ mock = Mock(spec=EphemeralTaskList)
12
+ return mock
13
+
14
+
15
+ @pytest.fixture
16
+ def append_task_tool(mock_task_list):
17
+ return AppendTaskTool(task_list=mock_task_list)
18
+
19
+
20
+ class DescribeAppendTaskTool:
21
+ def should_call_append_task_with_description(self, append_task_tool, mock_task_list):
22
+ mock_task = Task(id=1, description="Test task", status=TaskStatus.PENDING)
23
+ mock_task_list.append_task.return_value = mock_task
24
+
25
+ append_task_tool.run(description="Test task")
26
+
27
+ mock_task_list.append_task.assert_called_once_with(description="Test task")
28
+
29
+ def should_handle_error_when_append_task_fails(self, append_task_tool, mock_task_list):
30
+ mock_task_list.append_task.side_effect = ValueError("Test error")
31
+
32
+ append_task_tool.run(description="Test task")
33
+
34
+ mock_task_list.append_task.assert_called_once_with(description="Test task")
@@ -0,0 +1,57 @@
1
+ """
2
+ Tool for clearing all tasks from the ephemeral task manager.
3
+ """
4
+
5
+ from typing import Dict
6
+
7
+ from mojentic.llm.tools.llm_tool import LLMTool
8
+ from mojentic.llm.tools.ephemeral_task_manager.ephemeral_task_list import EphemeralTaskList
9
+
10
+
11
+ class ClearTasksTool(LLMTool):
12
+ """
13
+ Tool for clearing all tasks from the ephemeral task manager.
14
+ """
15
+
16
+ def __init__(self, task_list: EphemeralTaskList):
17
+ """
18
+ Initialize the tool with a shared task list.
19
+
20
+ Args:
21
+ task_list: The shared task list to use
22
+ """
23
+ self._task_list = task_list
24
+
25
+ def run(self) -> Dict[str, str]:
26
+ """
27
+ Remove all tasks from the list.
28
+
29
+ Returns:
30
+ A dictionary with the result of the operation
31
+ """
32
+ count = self._task_list.clear_tasks()
33
+ return {
34
+ "count": str(count),
35
+ "summary": f"Cleared {count} tasks from the list"
36
+ }
37
+
38
+ @property
39
+ def descriptor(self):
40
+ """
41
+ Get the descriptor for the tool.
42
+
43
+ Returns:
44
+ The descriptor dictionary
45
+ """
46
+ return {
47
+ "type": "function",
48
+ "function": {
49
+ "name": "clear_tasks",
50
+ "description": "Remove all tasks from the task list.",
51
+ "parameters": {
52
+ "type": "object",
53
+ "properties": {},
54
+ "additionalProperties": False
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,32 @@
1
+ import pytest
2
+ from unittest.mock import Mock
3
+
4
+ from mojentic.llm.tools.ephemeral_task_manager.clear_tasks_tool import ClearTasksTool
5
+ from mojentic.llm.tools.ephemeral_task_manager.ephemeral_task_list import EphemeralTaskList
6
+
7
+
8
+ @pytest.fixture
9
+ def mock_task_list():
10
+ mock = Mock(spec=EphemeralTaskList)
11
+ return mock
12
+
13
+
14
+ @pytest.fixture
15
+ def clear_tasks_tool(mock_task_list):
16
+ return ClearTasksTool(task_list=mock_task_list)
17
+
18
+
19
+ class DescribeClearTasksTool:
20
+ def should_call_clear_tasks(self, clear_tasks_tool, mock_task_list):
21
+ mock_task_list.clear_tasks.return_value = 3 # Simulate clearing 3 tasks
22
+
23
+ clear_tasks_tool.run()
24
+
25
+ mock_task_list.clear_tasks.assert_called_once()
26
+
27
+ def should_handle_empty_list(self, clear_tasks_tool, mock_task_list):
28
+ mock_task_list.clear_tasks.return_value = 0 # Simulate clearing 0 tasks
29
+
30
+ clear_tasks_tool.run()
31
+
32
+ mock_task_list.clear_tasks.assert_called_once()
@@ -0,0 +1,81 @@
1
+ """
2
+ Tool for completing a task in the ephemeral task manager.
3
+ """
4
+
5
+ from typing import Dict
6
+
7
+ from mojentic.llm.tools.llm_tool import LLMTool
8
+ from mojentic.llm.tools.ephemeral_task_manager.ephemeral_task_list import EphemeralTaskList
9
+
10
+
11
+ class CompleteTaskTool(LLMTool):
12
+ """
13
+ Tool for completing a task in the ephemeral task manager.
14
+
15
+ This tool changes a task's status from IN_PROGRESS to COMPLETED.
16
+ """
17
+
18
+ def __init__(self, task_list: EphemeralTaskList):
19
+ """
20
+ Initialize the tool with a shared task list.
21
+
22
+ Args:
23
+ task_list: The shared task list to use
24
+ """
25
+ self._task_list = task_list
26
+
27
+ def run(self, id: int) -> Dict[str, str]:
28
+ """
29
+ Complete a task by changing its status from IN_PROGRESS to COMPLETED.
30
+
31
+ Args:
32
+ id: The ID of the task to complete
33
+
34
+ Returns:
35
+ A dictionary with the result of the operation
36
+
37
+ Raises:
38
+ ValueError: If no task with the given ID exists or if the task is not in IN_PROGRESS status
39
+ """
40
+ try:
41
+ # Convert id to int if it's a string
42
+ task_id = int(id) if isinstance(id, str) else id
43
+ task = self._task_list.complete_task(id=task_id)
44
+ return {
45
+ "id": task.id,
46
+ "description": task.description,
47
+ "status": task.status.value,
48
+ "summary": f"Task '{id}' completed successfully"
49
+ }
50
+ except ValueError as e:
51
+ return {
52
+ "error": str(e),
53
+ "summary": f"Failed to complete task: {str(e)}"
54
+ }
55
+
56
+ @property
57
+ def descriptor(self):
58
+ """
59
+ Get the descriptor for the tool.
60
+
61
+ Returns:
62
+ The descriptor dictionary
63
+ """
64
+ return {
65
+ "type": "function",
66
+ "function": {
67
+ "name": "complete_task",
68
+ "description": "Complete a task by changing its status from IN_PROGRESS to COMPLETED.",
69
+ "parameters": {
70
+ "type": "object",
71
+ "properties": {
72
+ "id": {
73
+ "type": "integer",
74
+ "description": "The ID of the task to complete"
75
+ }
76
+ },
77
+ "required": ["id"],
78
+ "additionalProperties": False
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,43 @@
1
+ from unittest.mock import Mock
2
+
3
+ import pytest
4
+
5
+ from mojentic.llm.tools.ephemeral_task_manager.complete_task_tool import CompleteTaskTool
6
+ from mojentic.llm.tools.ephemeral_task_manager.ephemeral_task_list import EphemeralTaskList, Task, TaskStatus
7
+
8
+
9
+ @pytest.fixture
10
+ def mock_task_list():
11
+ mock = Mock(spec=EphemeralTaskList)
12
+ return mock
13
+
14
+
15
+ @pytest.fixture
16
+ def complete_task_tool(mock_task_list):
17
+ return CompleteTaskTool(task_list=mock_task_list)
18
+
19
+
20
+ class DescribeCompleteTaskTool:
21
+ def should_call_complete_task_with_correct_id(self, complete_task_tool, mock_task_list):
22
+ mock_task = Task(id=1, description="Test task", status=TaskStatus.COMPLETED)
23
+ mock_task_list.complete_task.return_value = mock_task
24
+
25
+ complete_task_tool.run(id=1)
26
+
27
+ mock_task_list.complete_task.assert_called_once_with(id=1)
28
+
29
+ def should_convert_string_id_to_int(self, complete_task_tool, mock_task_list):
30
+ mock_task = Task(id=1, description="Test task", status=TaskStatus.COMPLETED)
31
+ mock_task_list.complete_task.return_value = mock_task
32
+
33
+ complete_task_tool.run(id="1")
34
+
35
+ mock_task_list.complete_task.assert_called_once_with(id=1)
36
+
37
+ def should_handle_error_when_complete_task_fails(self, complete_task_tool, mock_task_list):
38
+ error_message = "Task '1' cannot be completed because it is not in IN_PROGRESS status"
39
+ mock_task_list.complete_task.side_effect = ValueError(error_message)
40
+
41
+ complete_task_tool.run(id=1)
42
+
43
+ mock_task_list.complete_task.assert_called_once_with(id=1)
@@ -0,0 +1,202 @@
1
+ """
2
+ Task list for the ephemeral task manager.
3
+
4
+ This module provides a class for managing a list of tasks with state transitions.
5
+ """
6
+
7
+ from enum import Enum
8
+ from typing import List, Optional
9
+ from pydantic import BaseModel
10
+
11
+
12
+ class TaskStatus(str, Enum):
13
+ """
14
+ Enumeration of possible task statuses.
15
+
16
+ Tasks follow a state machine that transitions from PENDING through IN_PROGRESS to COMPLETED.
17
+ """
18
+ PENDING = "pending"
19
+ IN_PROGRESS = "in_progress"
20
+ COMPLETED = "completed"
21
+
22
+
23
+ class Task(BaseModel):
24
+ """
25
+ Represents a task with an identifier, description, and status.
26
+ """
27
+ id: int
28
+ description: str
29
+ status: TaskStatus = TaskStatus.PENDING
30
+
31
+
32
+ class EphemeralTaskList:
33
+ """
34
+ Manages a list of tasks for the ephemeral task manager.
35
+
36
+ This class provides methods for adding, starting, completing, and listing tasks.
37
+ Tasks follow a state machine that transitions from PENDING through IN_PROGRESS to COMPLETED.
38
+ """
39
+
40
+ def __init__(self):
41
+ """
42
+ Initialize an empty task list and ID counter.
43
+ """
44
+ self._tasks: List[Task] = []
45
+ self._next_id: int = 1
46
+
47
+ def _claim_next_id(self) -> int:
48
+ """
49
+ Claim the next available ID and increment the counter.
50
+
51
+ Returns:
52
+ The claimed ID
53
+ """
54
+ id = self._next_id
55
+ self._next_id += 1
56
+ return id
57
+
58
+ def append_task(self, description: str) -> Task:
59
+ """
60
+ Add a new task to the end of the list.
61
+
62
+ Args:
63
+ description: The description of the task
64
+
65
+ Returns:
66
+ The newly created task with PENDING status
67
+ """
68
+ # Generate a new ID
69
+ id = self._claim_next_id()
70
+
71
+ task = Task(id=id, description=description, status=TaskStatus.PENDING)
72
+ self._tasks.append(task)
73
+ return task
74
+
75
+ def prepend_task(self, description: str) -> Task:
76
+ """
77
+ Add a new task to the beginning of the list.
78
+
79
+ Args:
80
+ description: The description of the task
81
+
82
+ Returns:
83
+ The newly created task with PENDING status
84
+ """
85
+ # Generate a new ID
86
+ id = self._claim_next_id()
87
+
88
+ task = Task(id=id, description=description, status=TaskStatus.PENDING)
89
+ self._tasks.insert(0, task)
90
+ return task
91
+
92
+ def insert_task_after(self, existing_task_id: int, description: str) -> Task:
93
+ """
94
+ Insert a new task after an existing task.
95
+
96
+ Args:
97
+ existing_task_id: The ID of the existing task after which to insert the new task
98
+ description: The description of the new task
99
+
100
+ Returns:
101
+ The newly created task with PENDING status
102
+
103
+ Raises:
104
+ ValueError: If no task with the given ID exists
105
+ """
106
+ # Find the position of the existing task
107
+ position = None
108
+ for i, task in enumerate(self._tasks):
109
+ if task.id == existing_task_id:
110
+ position = i
111
+ break
112
+
113
+ if position is None:
114
+ raise ValueError(f"No task with ID '{existing_task_id}' exists")
115
+
116
+ # Generate a new ID
117
+ id = self._claim_next_id()
118
+
119
+ task = Task(id=id, description=description, status=TaskStatus.PENDING)
120
+ self._tasks.insert(position + 1, task)
121
+ return task
122
+
123
+ def start_task(self, id: int) -> Task:
124
+ """
125
+ Start a task by changing its status from PENDING to IN_PROGRESS.
126
+
127
+ Args:
128
+ id: The ID of the task to start
129
+
130
+ Returns:
131
+ The started task
132
+
133
+ Raises:
134
+ ValueError: If no task with the given ID exists or if the task is not in PENDING status
135
+ """
136
+ task = self._get_task(id)
137
+
138
+ if task.status != TaskStatus.PENDING:
139
+ raise ValueError(f"Task '{id}' cannot be started because it is not in PENDING status")
140
+
141
+ task.status = TaskStatus.IN_PROGRESS
142
+ return task
143
+
144
+ def complete_task(self, id: int) -> Task:
145
+ """
146
+ Complete a task by changing its status from IN_PROGRESS to COMPLETED.
147
+
148
+ Args:
149
+ id: The ID of the task to complete
150
+
151
+ Returns:
152
+ The completed task
153
+
154
+ Raises:
155
+ ValueError: If no task with the given ID exists or if the task is not in IN_PROGRESS status
156
+ """
157
+ task = self._get_task(id)
158
+
159
+ if task.status != TaskStatus.IN_PROGRESS:
160
+ raise ValueError(f"Task '{id}' cannot be completed because it is not in IN_PROGRESS status")
161
+
162
+ task.status = TaskStatus.COMPLETED
163
+ return task
164
+
165
+ def list_tasks(self) -> List[Task]:
166
+ """
167
+ Get all tasks in the list.
168
+
169
+ Returns:
170
+ A list of all tasks
171
+ """
172
+ return self._tasks.copy()
173
+
174
+ def clear_tasks(self) -> int:
175
+ """
176
+ Remove all tasks from the list.
177
+
178
+ Returns:
179
+ The number of tasks that were cleared
180
+ """
181
+ count = len(self._tasks)
182
+ self._tasks = []
183
+ return count
184
+
185
+ def _get_task(self, id: int) -> Task:
186
+ """
187
+ Get a task by its ID.
188
+
189
+ Args:
190
+ id: The ID of the task to get
191
+
192
+ Returns:
193
+ The task with the given ID
194
+
195
+ Raises:
196
+ ValueError: If no task with the given ID exists
197
+ """
198
+ for task in self._tasks:
199
+ if task.id == id:
200
+ return task
201
+
202
+ raise ValueError(f"No task with ID '{id}' exists")
@@ -0,0 +1,137 @@
1
+ import pytest
2
+
3
+ from mojentic.llm.tools.ephemeral_task_manager.ephemeral_task_list import EphemeralTaskList, TaskStatus
4
+
5
+
6
+ @pytest.fixture
7
+ def task_list():
8
+ return EphemeralTaskList()
9
+
10
+
11
+ @pytest.fixture
12
+ def populated_task_list():
13
+ task_list = EphemeralTaskList()
14
+ task_list.append_task("Task 1")
15
+ task_list.append_task("Task 2")
16
+ task_list.append_task("Task 3")
17
+ return task_list
18
+
19
+
20
+ class DescribeEphemeralTaskList:
21
+
22
+ def should_initialize_with_empty_task_list(self, task_list):
23
+ tasks = task_list.list_tasks()
24
+ assert len(tasks) == 0
25
+
26
+ def should_append_task(self, task_list):
27
+ task = task_list.append_task("Test task")
28
+
29
+ tasks = task_list.list_tasks()
30
+ assert len(tasks) == 1
31
+ assert tasks[0].id == task.id
32
+ assert tasks[0].description == "Test task"
33
+ assert tasks[0].status == TaskStatus.PENDING
34
+
35
+ def should_prepend_task(self, task_list):
36
+ task_list.append_task("Existing task")
37
+ task = task_list.prepend_task("New task")
38
+
39
+ tasks = task_list.list_tasks()
40
+ assert len(tasks) == 2
41
+ assert tasks[0].id == task.id
42
+ assert tasks[0].description == "New task"
43
+ assert tasks[0].status == TaskStatus.PENDING
44
+
45
+ def should_insert_task_after(self, populated_task_list):
46
+ tasks_before = populated_task_list.list_tasks()
47
+ existing_task_id = tasks_before[1].id
48
+
49
+ task = populated_task_list.insert_task_after(existing_task_id, "Inserted task")
50
+
51
+ tasks_after = populated_task_list.list_tasks()
52
+ assert len(tasks_after) == 4
53
+ assert tasks_after[2].id == task.id
54
+ assert tasks_after[2].description == "Inserted task"
55
+ assert tasks_after[2].status == TaskStatus.PENDING
56
+
57
+ def should_raise_error_when_inserting_after_nonexistent_task(self, task_list):
58
+ with pytest.raises(ValueError) as e:
59
+ task_list.insert_task_after(999, "This should fail")
60
+
61
+ assert "No task with ID '999' exists" in str(e.value)
62
+
63
+ def should_start_task(self, populated_task_list):
64
+ tasks = populated_task_list.list_tasks()
65
+ task_id = tasks[0].id
66
+
67
+ started_task = populated_task_list.start_task(task_id)
68
+
69
+ assert started_task.status == TaskStatus.IN_PROGRESS
70
+
71
+ def should_raise_error_when_starting_non_pending_task(self, populated_task_list):
72
+ tasks = populated_task_list.list_tasks()
73
+ task_id = tasks[0].id
74
+
75
+ populated_task_list.start_task(task_id)
76
+
77
+ # Second start should fail
78
+ with pytest.raises(ValueError) as e:
79
+ populated_task_list.start_task(task_id)
80
+
81
+ assert f"Task '{task_id}' cannot be started because it is not in PENDING status" in str(e.value)
82
+
83
+ def should_complete_task(self, populated_task_list):
84
+ tasks = populated_task_list.list_tasks()
85
+ task_id = tasks[0].id
86
+
87
+ # Start the task first
88
+ populated_task_list.start_task(task_id)
89
+
90
+ # Now complete it
91
+ completed_task = populated_task_list.complete_task(task_id)
92
+
93
+ assert completed_task.status == TaskStatus.COMPLETED
94
+
95
+ def should_raise_error_when_completing_non_in_progress_task(self, populated_task_list):
96
+ tasks = populated_task_list.list_tasks()
97
+ task_id = tasks[0].id
98
+
99
+ with pytest.raises(ValueError) as excinfo:
100
+ populated_task_list.complete_task(task_id)
101
+
102
+ assert f"Task '{task_id}' cannot be completed because it is not in IN_PROGRESS status" in str(excinfo.value)
103
+
104
+ def should_clear_tasks(self, populated_task_list):
105
+ populated_task_list.clear_tasks()
106
+
107
+ tasks_after = populated_task_list.list_tasks()
108
+ assert len(tasks_after) == 0
109
+
110
+ def should_maintain_task_ids_across_operations(self, task_list):
111
+ # Add some tasks
112
+ task1 = task_list.append_task("Task 1")
113
+ task2 = task_list.append_task("Task 2")
114
+
115
+ # Start and complete task1
116
+ task_list.start_task(task1.id)
117
+ task_list.complete_task(task1.id)
118
+
119
+ # Add another task
120
+ task3 = task_list.append_task("Task 3")
121
+
122
+ # Verify all tasks have correct IDs and statuses
123
+ tasks = task_list.list_tasks()
124
+ assert len(tasks) == 3
125
+
126
+ # Find tasks by ID
127
+ task1_in_list = next((t for t in tasks if t.id == task1.id), None)
128
+ task2_in_list = next((t for t in tasks if t.id == task2.id), None)
129
+ task3_in_list = next((t for t in tasks if t.id == task3.id), None)
130
+
131
+ assert task1_in_list is not None
132
+ assert task2_in_list is not None
133
+ assert task3_in_list is not None
134
+
135
+ assert task1_in_list.status == TaskStatus.COMPLETED
136
+ assert task2_in_list.status == TaskStatus.PENDING
137
+ assert task3_in_list.status == TaskStatus.PENDING
@@ -0,0 +1,84 @@
1
+ """
2
+ Tool for inserting a new task after an existing task in the ephemeral task manager list.
3
+ """
4
+
5
+ from typing import Dict
6
+
7
+ from mojentic.llm.tools.llm_tool import LLMTool
8
+ from mojentic.llm.tools.ephemeral_task_manager.ephemeral_task_list import EphemeralTaskList
9
+
10
+
11
+ class InsertTaskAfterTool(LLMTool):
12
+ """
13
+ Tool for inserting a new task after an existing task in the ephemeral task manager list.
14
+ """
15
+
16
+ def __init__(self, task_list: EphemeralTaskList):
17
+ """
18
+ Initialize the tool with a shared task list.
19
+
20
+ Args:
21
+ task_list: The shared task list to use
22
+ """
23
+ self._task_list = task_list
24
+
25
+ def run(self, existing_task_id: int, description: str) -> Dict[str, str]:
26
+ """
27
+ Insert a new task after an existing task.
28
+
29
+ Args:
30
+ existing_task_id: The ID of the existing task after which to insert the new task
31
+ description: The description of the new task
32
+
33
+ Returns:
34
+ A dictionary with the result of the operation
35
+
36
+ Raises:
37
+ ValueError: If no task with the given ID exists
38
+ """
39
+ try:
40
+ # Convert existing_task_id to int if it's a string
41
+ task_id = int(existing_task_id) if isinstance(existing_task_id, str) else existing_task_id
42
+ task = self._task_list.insert_task_after(existing_task_id=task_id, description=description)
43
+ return {
44
+ "id": task.id,
45
+ "description": task.description,
46
+ "status": task.status.value,
47
+ "summary": f"Task '{task.id}' inserted after task '{existing_task_id}' successfully"
48
+ }
49
+ except ValueError as e:
50
+ return {
51
+ "error": str(e),
52
+ "summary": f"Failed to insert task: {str(e)}"
53
+ }
54
+
55
+ @property
56
+ def descriptor(self):
57
+ """
58
+ Get the descriptor for the tool.
59
+
60
+ Returns:
61
+ The descriptor dictionary
62
+ """
63
+ return {
64
+ "type": "function",
65
+ "function": {
66
+ "name": "insert_task_after",
67
+ "description": "Insert a new task after an existing task in the task list. The task will start with 'pending' status.",
68
+ "parameters": {
69
+ "type": "object",
70
+ "properties": {
71
+ "existing_task_id": {
72
+ "type": "integer",
73
+ "description": "The ID of the existing task after which to insert the new task"
74
+ },
75
+ "description": {
76
+ "type": "string",
77
+ "description": "The description of the new task"
78
+ }
79
+ },
80
+ "required": ["existing_task_id", "description"],
81
+ "additionalProperties": False
82
+ }
83
+ }
84
+ }