illuma-agents 1.0.65 → 1.0.67
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.
- package/dist/cjs/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +3 -132
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/main.cjs +47 -4
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +0 -50
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/prompts/collab.cjs +11 -0
- package/dist/cjs/prompts/collab.cjs.map +1 -0
- package/dist/cjs/prompts/taskmanager.cjs +66 -0
- package/dist/cjs/prompts/taskmanager.cjs.map +1 -0
- package/dist/cjs/prompts/taskplanning.cjs +99 -0
- package/dist/cjs/prompts/taskplanning.cjs.map +1 -0
- package/dist/cjs/tools/Calculator.cjs +23 -2
- package/dist/cjs/tools/Calculator.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +19 -1
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/TaskProgress.cjs +172 -0
- package/dist/cjs/tools/TaskProgress.cjs.map +1 -0
- package/dist/cjs/tools/ToolNode.cjs +2 -1
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/search/schema.cjs +45 -0
- package/dist/cjs/tools/search/schema.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +3 -132
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/main.mjs +8 -3
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +2 -51
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/prompts/collab.mjs +9 -0
- package/dist/esm/prompts/collab.mjs.map +1 -0
- package/dist/esm/prompts/taskmanager.mjs +60 -0
- package/dist/esm/prompts/taskmanager.mjs.map +1 -0
- package/dist/esm/prompts/taskplanning.mjs +94 -0
- package/dist/esm/prompts/taskplanning.mjs.map +1 -0
- package/dist/esm/tools/Calculator.mjs +20 -3
- package/dist/esm/tools/Calculator.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +16 -2
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/TaskProgress.mjs +163 -0
- package/dist/esm/tools/TaskProgress.mjs.map +1 -0
- package/dist/esm/tools/ToolNode.mjs +2 -1
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/search/schema.mjs +42 -1
- package/dist/esm/tools/search/schema.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +2 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/messages/format.d.ts +0 -9
- package/dist/types/prompts/index.d.ts +1 -0
- package/dist/types/prompts/taskplanning.d.ts +54 -0
- package/dist/types/tools/Calculator.d.ts +26 -0
- package/dist/types/tools/CodeExecutor.d.ts +51 -0
- package/dist/types/tools/TaskProgress.d.ts +142 -0
- package/dist/types/tools/search/index.d.ts +1 -0
- package/dist/types/tools/search/schema.d.ts +69 -0
- package/package.json +1 -1
- package/src/common/enum.ts +2 -0
- package/src/graphs/Graph.ts +5 -160
- package/src/index.ts +4 -0
- package/src/messages/format.ts +0 -60
- package/src/prompts/index.ts +2 -1
- package/src/prompts/taskplanning.ts +96 -0
- package/src/specs/task-progress.test.ts +330 -0
- package/src/tools/Calculator.ts +24 -3
- package/src/tools/CodeExecutor.ts +19 -2
- package/src/tools/TaskProgress.ts +247 -0
- package/src/tools/ToolNode.ts +2 -1
- package/src/tools/search/index.ts +1 -0
- package/src/tools/search/schema.ts +45 -0
- package/src/tools/search/tool.ts +3 -3
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
// src/specs/task-progress.test.ts
|
|
2
|
+
import { GraphEvents } from '@/common';
|
|
3
|
+
import {
|
|
4
|
+
createTaskProgressTool,
|
|
5
|
+
TaskProgressToolName,
|
|
6
|
+
TaskProgressToolDescription,
|
|
7
|
+
TaskProgressToolSchema,
|
|
8
|
+
TaskProgressToolDefinition,
|
|
9
|
+
type TaskItem,
|
|
10
|
+
type TaskStatus,
|
|
11
|
+
type TaskProgressPayload,
|
|
12
|
+
} from '@/tools/TaskProgress';
|
|
13
|
+
|
|
14
|
+
// Mock the safeDispatchCustomEvent
|
|
15
|
+
jest.mock('@/utils/events', () => ({
|
|
16
|
+
safeDispatchCustomEvent: jest.fn().mockResolvedValue(undefined),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
20
|
+
const mockDispatch = safeDispatchCustomEvent as jest.MockedFunction<
|
|
21
|
+
typeof safeDispatchCustomEvent
|
|
22
|
+
>;
|
|
23
|
+
|
|
24
|
+
describe('TaskProgress Tool', () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
jest.clearAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('Tool Definition', () => {
|
|
30
|
+
it('should have correct tool name', () => {
|
|
31
|
+
expect(TaskProgressToolName).toBe('manage_todo_list');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should have description with usage guidelines', () => {
|
|
35
|
+
expect(TaskProgressToolDescription).toContain('todo list');
|
|
36
|
+
expect(TaskProgressToolDescription).toContain('not-started');
|
|
37
|
+
expect(TaskProgressToolDescription).toContain('in-progress');
|
|
38
|
+
expect(TaskProgressToolDescription).toContain('completed');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should have valid JSON schema', () => {
|
|
42
|
+
expect(TaskProgressToolSchema.type).toBe('object');
|
|
43
|
+
expect(TaskProgressToolSchema.required).toContain('todoList');
|
|
44
|
+
expect(TaskProgressToolSchema.properties.todoList.type).toBe('array');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should export complete tool definition', () => {
|
|
48
|
+
expect(TaskProgressToolDefinition.name).toBe(TaskProgressToolName);
|
|
49
|
+
expect(TaskProgressToolDefinition.description).toBe(
|
|
50
|
+
TaskProgressToolDescription
|
|
51
|
+
);
|
|
52
|
+
expect(TaskProgressToolDefinition.schema).toEqual(TaskProgressToolSchema);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('createTaskProgressTool', () => {
|
|
57
|
+
it('should create a tool with correct name', () => {
|
|
58
|
+
const tool = createTaskProgressTool();
|
|
59
|
+
expect(tool.name).toBe('manage_todo_list');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should create a tool with description', () => {
|
|
63
|
+
const tool = createTaskProgressTool();
|
|
64
|
+
expect(tool.description).toBeDefined();
|
|
65
|
+
expect(tool.description.length).toBeGreaterThan(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should accept onTaskUpdate callback parameter', () => {
|
|
69
|
+
const mockCallback = jest.fn();
|
|
70
|
+
const tool = createTaskProgressTool({ onTaskUpdate: mockCallback });
|
|
71
|
+
expect(tool).toBeDefined();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('Tool Invocation', () => {
|
|
76
|
+
it('should accept valid todo list and return success message', async () => {
|
|
77
|
+
const tool = createTaskProgressTool();
|
|
78
|
+
|
|
79
|
+
const validTodoList: TaskItem[] = [
|
|
80
|
+
{ id: 1, title: 'Research API', status: 'not-started' },
|
|
81
|
+
{ id: 2, title: 'Implement feature', status: 'not-started' },
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
const result = await tool.invoke({ todoList: validTodoList });
|
|
85
|
+
|
|
86
|
+
expect(result).toContain('Todo list updated successfully');
|
|
87
|
+
expect(result).toContain('0/2 completed');
|
|
88
|
+
expect(result).toContain('2 remaining');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should dispatch SSE event with correct event type', async () => {
|
|
92
|
+
const tool = createTaskProgressTool();
|
|
93
|
+
|
|
94
|
+
const todoList: TaskItem[] = [
|
|
95
|
+
{ id: 1, title: 'Task 1', status: 'in-progress' },
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
await tool.invoke({ todoList });
|
|
99
|
+
|
|
100
|
+
expect(mockDispatch).toHaveBeenCalledWith(
|
|
101
|
+
GraphEvents.ON_TASK_PROGRESS,
|
|
102
|
+
expect.objectContaining({
|
|
103
|
+
tasks: todoList,
|
|
104
|
+
timestamp: expect.any(String),
|
|
105
|
+
isComplete: false,
|
|
106
|
+
}),
|
|
107
|
+
expect.anything() // config object is passed
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should include timestamp in dispatched payload', async () => {
|
|
112
|
+
const tool = createTaskProgressTool();
|
|
113
|
+
|
|
114
|
+
const todoList: TaskItem[] = [
|
|
115
|
+
{ id: 1, title: 'Task', status: 'not-started' },
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
await tool.invoke({ todoList });
|
|
119
|
+
|
|
120
|
+
const dispatchedPayload = mockDispatch.mock.calls[0][1] as TaskProgressPayload;
|
|
121
|
+
expect(dispatchedPayload.timestamp).toBeDefined();
|
|
122
|
+
expect(new Date(dispatchedPayload.timestamp).getTime()).toBeGreaterThan(0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should call onTaskUpdate callback when provided', async () => {
|
|
126
|
+
const mockCallback = jest.fn();
|
|
127
|
+
const tool = createTaskProgressTool({ onTaskUpdate: mockCallback });
|
|
128
|
+
|
|
129
|
+
const todoList: TaskItem[] = [
|
|
130
|
+
{ id: 1, title: 'Task', status: 'completed' },
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
await tool.invoke({ todoList });
|
|
134
|
+
|
|
135
|
+
expect(mockCallback).toHaveBeenCalledWith(todoList);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should handle async onTaskUpdate callback', async () => {
|
|
139
|
+
const asyncCallback = jest.fn().mockResolvedValue(undefined);
|
|
140
|
+
const tool = createTaskProgressTool({ onTaskUpdate: asyncCallback });
|
|
141
|
+
|
|
142
|
+
const todoList: TaskItem[] = [
|
|
143
|
+
{ id: 1, title: 'Task', status: 'in-progress' },
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
await tool.invoke({ todoList });
|
|
147
|
+
|
|
148
|
+
expect(asyncCallback).toHaveBeenCalledWith(todoList);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should not fail if onTaskUpdate throws', async () => {
|
|
152
|
+
const failingCallback = jest.fn().mockRejectedValue(new Error('Callback error'));
|
|
153
|
+
const tool = createTaskProgressTool({ onTaskUpdate: failingCallback });
|
|
154
|
+
|
|
155
|
+
const todoList: TaskItem[] = [
|
|
156
|
+
{ id: 1, title: 'Task', status: 'in-progress' },
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
// Should not throw, just warn
|
|
160
|
+
const result = await tool.invoke({ todoList });
|
|
161
|
+
expect(result).toContain('Todo list updated successfully');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('Status Tracking', () => {
|
|
166
|
+
it('should correctly count completed tasks', async () => {
|
|
167
|
+
const tool = createTaskProgressTool();
|
|
168
|
+
|
|
169
|
+
const todoList: TaskItem[] = [
|
|
170
|
+
{ id: 1, title: 'Done 1', status: 'completed' },
|
|
171
|
+
{ id: 2, title: 'Done 2', status: 'completed' },
|
|
172
|
+
{ id: 3, title: 'Not done', status: 'not-started' },
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
const result = await tool.invoke({ todoList });
|
|
176
|
+
|
|
177
|
+
expect(result).toContain('2/3 completed');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should correctly count in-progress tasks', async () => {
|
|
181
|
+
const tool = createTaskProgressTool();
|
|
182
|
+
|
|
183
|
+
const todoList: TaskItem[] = [
|
|
184
|
+
{ id: 1, title: 'Working', status: 'in-progress' },
|
|
185
|
+
{ id: 2, title: 'Not started', status: 'not-started' },
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
const result = await tool.invoke({ todoList });
|
|
189
|
+
|
|
190
|
+
expect(result).toContain('1 in progress');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should correctly count not-started tasks', async () => {
|
|
194
|
+
const tool = createTaskProgressTool();
|
|
195
|
+
|
|
196
|
+
const todoList: TaskItem[] = [
|
|
197
|
+
{ id: 1, title: 'Task 1', status: 'not-started' },
|
|
198
|
+
{ id: 2, title: 'Task 2', status: 'not-started' },
|
|
199
|
+
{ id: 3, title: 'Task 3', status: 'completed' },
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
const result = await tool.invoke({ todoList });
|
|
203
|
+
|
|
204
|
+
expect(result).toContain('2 remaining');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should warn about multiple in-progress tasks', async () => {
|
|
208
|
+
const tool = createTaskProgressTool();
|
|
209
|
+
|
|
210
|
+
const todoList: TaskItem[] = [
|
|
211
|
+
{ id: 1, title: 'Task 1', status: 'in-progress' },
|
|
212
|
+
{ id: 2, title: 'Task 2', status: 'in-progress' },
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
const result = await tool.invoke({ todoList });
|
|
216
|
+
|
|
217
|
+
expect(result).toContain('Warning');
|
|
218
|
+
expect(result).toContain('2 tasks marked as in-progress');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should support failed status', async () => {
|
|
222
|
+
const tool = createTaskProgressTool();
|
|
223
|
+
|
|
224
|
+
const todoList: TaskItem[] = [
|
|
225
|
+
{ id: 1, title: 'Failed task', status: 'failed' },
|
|
226
|
+
{ id: 2, title: 'Completed', status: 'completed' },
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
const result = await tool.invoke({ todoList });
|
|
230
|
+
|
|
231
|
+
expect(result).toContain('Todo list updated successfully');
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('Validation', () => {
|
|
236
|
+
it('should throw on non-array todoList (schema validation)', async () => {
|
|
237
|
+
const tool = createTaskProgressTool();
|
|
238
|
+
|
|
239
|
+
// LangChain schema validation catches this before our code runs
|
|
240
|
+
await expect(
|
|
241
|
+
tool.invoke({ todoList: 'not an array' })
|
|
242
|
+
).rejects.toThrow();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should throw on tasks with non-numeric id (schema validation)', async () => {
|
|
246
|
+
const tool = createTaskProgressTool();
|
|
247
|
+
|
|
248
|
+
const invalidTodoList = [
|
|
249
|
+
{ id: 'abc', title: 'Task', status: 'not-started' },
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
// LangChain schema validation catches this before our code runs
|
|
253
|
+
await expect(
|
|
254
|
+
tool.invoke({ todoList: invalidTodoList })
|
|
255
|
+
).rejects.toThrow();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should reject tasks with empty title', async () => {
|
|
259
|
+
const tool = createTaskProgressTool();
|
|
260
|
+
|
|
261
|
+
const invalidTodoList = [{ id: 1, title: '', status: 'not-started' }];
|
|
262
|
+
|
|
263
|
+
const result = await tool.invoke({ todoList: invalidTodoList });
|
|
264
|
+
|
|
265
|
+
expect(result).toContain('Error');
|
|
266
|
+
expect(result).toContain('title');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should throw on tasks with invalid status (schema validation)', async () => {
|
|
270
|
+
const tool = createTaskProgressTool();
|
|
271
|
+
|
|
272
|
+
const invalidTodoList = [
|
|
273
|
+
{ id: 1, title: 'Task', status: 'invalid-status' },
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
// LangChain schema validation catches this before our code runs
|
|
277
|
+
await expect(
|
|
278
|
+
tool.invoke({ todoList: invalidTodoList })
|
|
279
|
+
).rejects.toThrow();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should accept empty todo list', async () => {
|
|
283
|
+
const tool = createTaskProgressTool();
|
|
284
|
+
|
|
285
|
+
const result = await tool.invoke({ todoList: [] });
|
|
286
|
+
|
|
287
|
+
expect(result).toContain('Todo list updated successfully');
|
|
288
|
+
expect(result).toContain('0/0 completed');
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe('Type Exports', () => {
|
|
293
|
+
it('should export TaskStatus type with correct values', () => {
|
|
294
|
+
const validStatuses: TaskStatus[] = [
|
|
295
|
+
'not-started',
|
|
296
|
+
'in-progress',
|
|
297
|
+
'completed',
|
|
298
|
+
'failed',
|
|
299
|
+
];
|
|
300
|
+
|
|
301
|
+
validStatuses.forEach((status) => {
|
|
302
|
+
expect(['not-started', 'in-progress', 'completed', 'failed']).toContain(
|
|
303
|
+
status
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should export TaskItem type with required fields', () => {
|
|
309
|
+
const validTask: TaskItem = {
|
|
310
|
+
id: 1,
|
|
311
|
+
title: 'Test task',
|
|
312
|
+
status: 'not-started',
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
expect(validTask.id).toBe(1);
|
|
316
|
+
expect(validTask.title).toBe('Test task');
|
|
317
|
+
expect(validTask.status).toBe('not-started');
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should export TaskProgressPayload type', () => {
|
|
321
|
+
const payload: TaskProgressPayload = {
|
|
322
|
+
tasks: [{ id: 1, title: 'Test', status: 'completed' }],
|
|
323
|
+
timestamp: new Date().toISOString(),
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
expect(payload.tasks).toBeDefined();
|
|
327
|
+
expect(payload.timestamp).toBeDefined();
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
});
|
package/src/tools/Calculator.ts
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
import { Tool } from '@langchain/core/tools';
|
|
2
2
|
import * as math from 'mathjs';
|
|
3
3
|
|
|
4
|
+
export const CalculatorToolName = 'calculator';
|
|
5
|
+
|
|
6
|
+
export const CalculatorToolDescription =
|
|
7
|
+
'Useful for getting the result of a math expression. The input to this tool should be a valid mathematical expression that could be executed by a simple calculator.';
|
|
8
|
+
|
|
9
|
+
export const CalculatorSchema = {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
input: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
description: 'A valid mathematical expression to evaluate',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
required: ['input'],
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
export const CalculatorToolDefinition = {
|
|
21
|
+
name: CalculatorToolName,
|
|
22
|
+
description: CalculatorToolDescription,
|
|
23
|
+
schema: CalculatorSchema,
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
4
26
|
export class Calculator extends Tool {
|
|
5
27
|
static lc_name(): string {
|
|
6
28
|
return 'Calculator';
|
|
@@ -10,7 +32,7 @@ export class Calculator extends Tool {
|
|
|
10
32
|
return [...super.lc_namespace, 'calculator'];
|
|
11
33
|
}
|
|
12
34
|
|
|
13
|
-
name =
|
|
35
|
+
name = CalculatorToolName;
|
|
14
36
|
|
|
15
37
|
async _call(input: string): Promise<string> {
|
|
16
38
|
try {
|
|
@@ -20,6 +42,5 @@ export class Calculator extends Tool {
|
|
|
20
42
|
}
|
|
21
43
|
}
|
|
22
44
|
|
|
23
|
-
description =
|
|
24
|
-
'Useful for getting the result of a math expression. The input to this tool should be a valid mathematical expression that could be executed by a simple calculator.';
|
|
45
|
+
description = CalculatorToolDescription;
|
|
25
46
|
}
|
|
@@ -35,7 +35,7 @@ const SUPPORTED_LANGUAGES = [
|
|
|
35
35
|
'r',
|
|
36
36
|
] as const;
|
|
37
37
|
|
|
38
|
-
const CodeExecutionToolSchema = {
|
|
38
|
+
export const CodeExecutionToolSchema = {
|
|
39
39
|
type: 'object',
|
|
40
40
|
properties: {
|
|
41
41
|
lang: {
|
|
@@ -74,6 +74,23 @@ const EXEC_ENDPOINT = `${baseEndpoint}/exec`;
|
|
|
74
74
|
|
|
75
75
|
type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];
|
|
76
76
|
|
|
77
|
+
export const CodeExecutionToolDescription = `
|
|
78
|
+
Runs code and returns stdout/stderr output from a stateless execution environment, similar to running scripts in a command-line interface. Each execution is isolated and independent.
|
|
79
|
+
|
|
80
|
+
Usage:
|
|
81
|
+
- No network access available.
|
|
82
|
+
- Generated files are automatically delivered; **DO NOT** provide download links.
|
|
83
|
+
- NEVER use this tool to execute malicious code.
|
|
84
|
+
`.trim();
|
|
85
|
+
|
|
86
|
+
export const CodeExecutionToolName = Constants.EXECUTE_CODE;
|
|
87
|
+
|
|
88
|
+
export const CodeExecutionToolDefinition = {
|
|
89
|
+
name: CodeExecutionToolName,
|
|
90
|
+
description: CodeExecutionToolDescription,
|
|
91
|
+
schema: CodeExecutionToolSchema,
|
|
92
|
+
} as const;
|
|
93
|
+
|
|
77
94
|
function createCodeExecutionTool(
|
|
78
95
|
params: t.CodeExecutionToolParams = {}
|
|
79
96
|
): DynamicStructuredTool {
|
|
@@ -242,7 +259,7 @@ Rules:
|
|
|
242
259
|
}
|
|
243
260
|
},
|
|
244
261
|
{
|
|
245
|
-
name:
|
|
262
|
+
name: CodeExecutionToolName,
|
|
246
263
|
description,
|
|
247
264
|
schema: CodeExecutionToolSchema,
|
|
248
265
|
responseFormat: Constants.CONTENT_AND_ARTIFACT,
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// src/tools/TaskProgress.ts
|
|
2
|
+
import { tool, DynamicStructuredTool } from '@langchain/core/tools';
|
|
3
|
+
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
4
|
+
import { GraphEvents } from '@/common';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Task status enum matching the UI component expectations
|
|
8
|
+
*/
|
|
9
|
+
export type TaskStatus = 'not-started' | 'in-progress' | 'completed' | 'failed';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Individual task item in the todo list
|
|
13
|
+
*/
|
|
14
|
+
export interface TaskItem {
|
|
15
|
+
/** Unique identifier for the task (sequential number starting from 1) */
|
|
16
|
+
id: number;
|
|
17
|
+
/** Short, action-oriented title (3-7 words) */
|
|
18
|
+
title: string;
|
|
19
|
+
/** Current status of the task */
|
|
20
|
+
status: TaskStatus;
|
|
21
|
+
/** Optional message for additional context */
|
|
22
|
+
message?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Payload sent via SSE event for task progress updates
|
|
27
|
+
* This matches the format expected by the Ranger UI
|
|
28
|
+
*/
|
|
29
|
+
export interface TaskProgressPayload {
|
|
30
|
+
/** Complete list of all tasks */
|
|
31
|
+
tasks: TaskItem[];
|
|
32
|
+
/** Timestamp of the update */
|
|
33
|
+
timestamp: string;
|
|
34
|
+
/** Optional title for the task group */
|
|
35
|
+
title?: string;
|
|
36
|
+
/** Whether all tasks are complete */
|
|
37
|
+
isComplete?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Tool name constant - matches what AI calls
|
|
42
|
+
*/
|
|
43
|
+
export const TaskProgressToolName = 'manage_todo_list';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Tool description for the AI
|
|
47
|
+
*/
|
|
48
|
+
export const TaskProgressToolDescription = `Manage a structured todo list to track progress and plan tasks throughout your session.
|
|
49
|
+
|
|
50
|
+
**When to use this tool:**
|
|
51
|
+
- Complex multi-step work requiring planning and tracking (3+ steps)
|
|
52
|
+
- When breaking down larger tasks into smaller actionable steps
|
|
53
|
+
- Before starting work on any todo (mark as in-progress)
|
|
54
|
+
- Immediately after completing each todo (mark as completed)
|
|
55
|
+
- To give users visibility into your progress and planning
|
|
56
|
+
|
|
57
|
+
**When NOT to use:**
|
|
58
|
+
- Single, trivial tasks completed in one step
|
|
59
|
+
- Purely conversational/informational requests
|
|
60
|
+
- Simple file reads or searches
|
|
61
|
+
|
|
62
|
+
**Critical workflow:**
|
|
63
|
+
1. Plan tasks by creating todo list with specific, actionable items
|
|
64
|
+
2. Mark ONE todo as in-progress before starting work
|
|
65
|
+
3. Complete the work (may involve multiple tool calls)
|
|
66
|
+
4. Mark that todo as completed IMMEDIATELY
|
|
67
|
+
5. Move to next todo and repeat
|
|
68
|
+
|
|
69
|
+
**Todo states:**
|
|
70
|
+
- not-started: Todo not yet begun
|
|
71
|
+
- in-progress: Currently working (limit ONE at a time)
|
|
72
|
+
- completed: Finished successfully
|
|
73
|
+
- failed: Task could not be completed
|
|
74
|
+
|
|
75
|
+
**Important:** Mark todos completed as soon as they are done. Do not batch completions.`;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* JSON Schema for the tool input
|
|
79
|
+
*/
|
|
80
|
+
export const TaskProgressToolSchema = {
|
|
81
|
+
type: 'object',
|
|
82
|
+
properties: {
|
|
83
|
+
todoList: {
|
|
84
|
+
type: 'array',
|
|
85
|
+
description:
|
|
86
|
+
'Complete array of all todo items. Must include ALL items - both existing and new.',
|
|
87
|
+
items: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
properties: {
|
|
90
|
+
id: {
|
|
91
|
+
type: 'number',
|
|
92
|
+
description:
|
|
93
|
+
'Unique identifier for the todo. Use sequential numbers starting from 1.',
|
|
94
|
+
},
|
|
95
|
+
title: {
|
|
96
|
+
type: 'string',
|
|
97
|
+
description:
|
|
98
|
+
'Concise action-oriented todo label (3-7 words). Displayed in UI.',
|
|
99
|
+
},
|
|
100
|
+
status: {
|
|
101
|
+
type: 'string',
|
|
102
|
+
enum: ['not-started', 'in-progress', 'completed', 'failed'],
|
|
103
|
+
description:
|
|
104
|
+
'not-started: Not begun | in-progress: Currently working (max 1) | completed: Fully finished | failed: Could not complete',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
required: ['id', 'title', 'status'],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
required: ['todoList'],
|
|
112
|
+
} as const;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Tool definition for registration/export
|
|
116
|
+
*/
|
|
117
|
+
export const TaskProgressToolDefinition = {
|
|
118
|
+
name: TaskProgressToolName,
|
|
119
|
+
description: TaskProgressToolDescription,
|
|
120
|
+
schema: TaskProgressToolSchema,
|
|
121
|
+
} as const;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Parameters for creating the TaskProgress tool
|
|
125
|
+
*/
|
|
126
|
+
export interface TaskProgressToolParams {
|
|
127
|
+
/**
|
|
128
|
+
* Optional callback to handle task updates externally
|
|
129
|
+
* Called with the task list whenever it's updated
|
|
130
|
+
*/
|
|
131
|
+
onTaskUpdate?: (tasks: TaskItem[]) => void | Promise<void>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Creates a TaskProgress tool that allows the AI to manage a todo list
|
|
136
|
+
* and emits events for the UI to display progress.
|
|
137
|
+
*
|
|
138
|
+
* This tool is designed for ephemeral agents doing complex, multi-step work.
|
|
139
|
+
* It should NOT be included for:
|
|
140
|
+
* - Persistent workflow agents
|
|
141
|
+
* - Simple tool-calling agents
|
|
142
|
+
* - No-code/low-code builder workflows (unless explicitly enabled)
|
|
143
|
+
*
|
|
144
|
+
* @param params - Optional parameters including external callback
|
|
145
|
+
* @returns DynamicStructuredTool that can be added to agent tools
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```typescript
|
|
149
|
+
* // Add to agent tools for ephemeral research/planning agents
|
|
150
|
+
* const tools = [
|
|
151
|
+
* createTaskProgressTool(),
|
|
152
|
+
* // ... other tools
|
|
153
|
+
* ];
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export function createTaskProgressTool(
|
|
157
|
+
params: TaskProgressToolParams = {}
|
|
158
|
+
): DynamicStructuredTool {
|
|
159
|
+
const { onTaskUpdate } = params;
|
|
160
|
+
|
|
161
|
+
return tool(
|
|
162
|
+
async (rawInput, config) => {
|
|
163
|
+
const { todoList } = rawInput as { todoList: TaskItem[] };
|
|
164
|
+
|
|
165
|
+
// Validate input
|
|
166
|
+
if (!Array.isArray(todoList)) {
|
|
167
|
+
return 'Error: todoList must be an array of task items.';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Validate each task
|
|
171
|
+
for (const task of todoList) {
|
|
172
|
+
if (typeof task.id !== 'number') {
|
|
173
|
+
return `Error: Task id must be a number. Got: ${typeof task.id}`;
|
|
174
|
+
}
|
|
175
|
+
if (typeof task.title !== 'string' || task.title.trim() === '') {
|
|
176
|
+
return `Error: Task title must be a non-empty string for task ${task.id}`;
|
|
177
|
+
}
|
|
178
|
+
if (
|
|
179
|
+
!['not-started', 'in-progress', 'completed', 'failed'].includes(
|
|
180
|
+
task.status
|
|
181
|
+
)
|
|
182
|
+
) {
|
|
183
|
+
return `Error: Invalid status "${task.status}" for task ${task.id}. Must be one of: not-started, in-progress, completed, failed`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Check for multiple in-progress tasks (warning, not error)
|
|
188
|
+
const inProgressCount = todoList.filter(
|
|
189
|
+
(t) => t.status === 'in-progress'
|
|
190
|
+
).length;
|
|
191
|
+
const warning =
|
|
192
|
+
inProgressCount > 1
|
|
193
|
+
? `\nWarning: ${inProgressCount} tasks marked as in-progress. Best practice is to work on one task at a time.`
|
|
194
|
+
: '';
|
|
195
|
+
|
|
196
|
+
// Check if all tasks are complete
|
|
197
|
+
const allComplete =
|
|
198
|
+
todoList.length > 0 &&
|
|
199
|
+
todoList.every(
|
|
200
|
+
(t) => t.status === 'completed' || t.status === 'failed'
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Create the payload for SSE event
|
|
204
|
+
const payload: TaskProgressPayload = {
|
|
205
|
+
tasks: todoList,
|
|
206
|
+
timestamp: new Date().toISOString(),
|
|
207
|
+
isComplete: allComplete,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// Dispatch SSE event for UI to consume
|
|
211
|
+
await safeDispatchCustomEvent(
|
|
212
|
+
GraphEvents.ON_TASK_PROGRESS,
|
|
213
|
+
payload,
|
|
214
|
+
config
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// Call external callback if provided
|
|
218
|
+
if (onTaskUpdate) {
|
|
219
|
+
try {
|
|
220
|
+
await onTaskUpdate(todoList);
|
|
221
|
+
} catch (e) {
|
|
222
|
+
// Don't fail the tool call if callback fails
|
|
223
|
+
// eslint-disable-next-line no-console
|
|
224
|
+
console.warn('TaskProgress callback error:', e);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Build response message
|
|
229
|
+
const completed = todoList.filter((t) => t.status === 'completed').length;
|
|
230
|
+
const inProgress = todoList.filter(
|
|
231
|
+
(t) => t.status === 'in-progress'
|
|
232
|
+
).length;
|
|
233
|
+
const notStarted = todoList.filter(
|
|
234
|
+
(t) => t.status === 'not-started'
|
|
235
|
+
).length;
|
|
236
|
+
|
|
237
|
+
return `Todo list updated successfully. Status: ${completed}/${todoList.length} completed, ${inProgress} in progress, ${notStarted} remaining.${warning}`;
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: TaskProgressToolName,
|
|
241
|
+
description: TaskProgressToolDescription,
|
|
242
|
+
schema: TaskProgressToolSchema,
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export default createTaskProgressTool;
|
package/src/tools/ToolNode.ts
CHANGED
|
@@ -392,8 +392,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
392
392
|
: JSON.stringify(result.content);
|
|
393
393
|
toolMessage = new ToolMessage({
|
|
394
394
|
status: 'success',
|
|
395
|
-
content: contentString,
|
|
396
395
|
name: toolName,
|
|
396
|
+
content: contentString,
|
|
397
|
+
artifact: result.artifact,
|
|
397
398
|
tool_call_id: result.toolCallId,
|
|
398
399
|
});
|
|
399
400
|
}
|