saico 2.4.0 → 2.5.0
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/README.md +20 -17
- package/index.js +1 -5
- package/itask.js +0 -1
- package/msgs.js +71 -18
- package/package.json +1 -1
- package/saico.js +1 -9
package/README.md
CHANGED
|
@@ -27,7 +27,6 @@ class MyAgent extends Saico {
|
|
|
27
27
|
super({
|
|
28
28
|
name: 'my-agent',
|
|
29
29
|
prompt: 'You are a helpful assistant.',
|
|
30
|
-
tool_handler: (name, args) => this.handleTool(name, args),
|
|
31
30
|
functions: [{
|
|
32
31
|
type: 'function',
|
|
33
32
|
function: {
|
|
@@ -43,11 +42,9 @@ class MyAgent extends Saico {
|
|
|
43
42
|
});
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return `Weather in ${args.location}: 72F, sunny`;
|
|
50
|
-
return 'Unknown tool';
|
|
45
|
+
// Tool implementations — define TOOL_ prefix methods
|
|
46
|
+
async TOOL_get_weather(args) {
|
|
47
|
+
return `Weather in ${args.location}: 72F, sunny`;
|
|
51
48
|
}
|
|
52
49
|
}
|
|
53
50
|
|
|
@@ -145,7 +142,6 @@ When a Saico's context is not the deepest active one, its last 5 user/assistant
|
|
|
145
142
|
const child = agent.spawnTaskWithContext({
|
|
146
143
|
name: 'subtask',
|
|
147
144
|
prompt: 'Handle this specific sub-task',
|
|
148
|
-
tool_handler: (name, args) => handleSubTools(name, args),
|
|
149
145
|
functions: [/* child-specific tools */]
|
|
150
146
|
}, [
|
|
151
147
|
async function main() {
|
|
@@ -177,7 +173,6 @@ new Saico({
|
|
|
177
173
|
|
|
178
174
|
// AI config
|
|
179
175
|
prompt: 'System prompt',
|
|
180
|
-
tool_handler: fn, // async (name, argsString) => result
|
|
181
176
|
functions: [], // OpenAI function definitions
|
|
182
177
|
|
|
183
178
|
// Behavior
|
|
@@ -286,7 +281,6 @@ const json = agent.serialize();
|
|
|
286
281
|
|
|
287
282
|
// Restore
|
|
288
283
|
const restored = Saico.deserialize(json, {
|
|
289
|
-
tool_handler: myHandler,
|
|
290
284
|
functions: myFunctions,
|
|
291
285
|
});
|
|
292
286
|
```
|
|
@@ -309,16 +303,26 @@ agent.someProperty = 'value'; // Auto-saved to Redis
|
|
|
309
303
|
|
|
310
304
|
Properties prefixed with `_` are internal and not persisted.
|
|
311
305
|
|
|
312
|
-
## Tool
|
|
306
|
+
## Tool Implementation (TOOL_ methods)
|
|
307
|
+
|
|
308
|
+
Define tool implementations as `TOOL_`-prefixed methods on your Saico subclass. When the LLM returns a tool call, Context automatically searches the Saico hierarchy (current → up parents → down children) to find and invoke the matching method with parsed arguments.
|
|
313
309
|
|
|
314
310
|
```js
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
311
|
+
class MyAgent extends Saico {
|
|
312
|
+
async TOOL_get_weather(args) {
|
|
313
|
+
// args is already JSON.parse'd
|
|
314
|
+
return `Weather in ${args.location}: 72F, sunny`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async TOOL_search(args) {
|
|
318
|
+
const results = await search(args.query);
|
|
319
|
+
return { content: JSON.stringify(results), functions: updatedTools };
|
|
320
|
+
}
|
|
319
321
|
}
|
|
320
322
|
```
|
|
321
323
|
|
|
324
|
+
Return a string or `{ content: string, functions?: [] }`.
|
|
325
|
+
|
|
322
326
|
### Tool Safety Features
|
|
323
327
|
|
|
324
328
|
- **Depth control** — `max_depth` (default: 5) prevents infinite tool call recursion
|
|
@@ -339,13 +343,12 @@ const { createTask, createContext, createQ } = require('saico');
|
|
|
339
343
|
const task = createTask({
|
|
340
344
|
name: 'my-task',
|
|
341
345
|
prompt: 'You are helpful',
|
|
342
|
-
tool_handler: handler,
|
|
343
346
|
functions: tools
|
|
344
347
|
});
|
|
345
348
|
const reply = await task.sendMessage('Hello');
|
|
346
349
|
|
|
347
350
|
// Standalone context (legacy)
|
|
348
|
-
const ctx = createQ('System prompt', null, 'tag', 4000
|
|
351
|
+
const ctx = createQ('System prompt', null, 'tag', 4000);
|
|
349
352
|
const reply = await ctx.sendMessage('user', 'Hello', functions);
|
|
350
353
|
```
|
|
351
354
|
|
|
@@ -371,7 +374,7 @@ saico/
|
|
|
371
374
|
npm test
|
|
372
375
|
```
|
|
373
376
|
|
|
374
|
-
|
|
377
|
+
293 tests covering Saico lifecycle, task hierarchy, message handling, tool calls, DB adapters, serialization, and integration flows.
|
|
375
378
|
|
|
376
379
|
## Requirements
|
|
377
380
|
|
package/index.js
CHANGED
|
@@ -54,7 +54,6 @@ async function init(config = {}) {
|
|
|
54
54
|
* @param {Object|string} opt - Task options or name string
|
|
55
55
|
* @param {string} opt.name - Task name
|
|
56
56
|
* @param {string} opt.prompt - System prompt (if provided, creates a context)
|
|
57
|
-
* @param {Function} opt.tool_handler - Tool handler function
|
|
58
57
|
* @param {Array} opt.functions - Available functions for AI
|
|
59
58
|
* @param {boolean} opt.cancel - Whether task is cancelable
|
|
60
59
|
* @param {Object} opt.bind - Bind context for state functions
|
|
@@ -80,7 +79,6 @@ function createTask(opt, states = []) {
|
|
|
80
79
|
token_limit: opt.token_limit,
|
|
81
80
|
max_depth: opt.max_depth,
|
|
82
81
|
max_tool_repetition: opt.max_tool_repetition,
|
|
83
|
-
tool_handler: opt.tool_handler,
|
|
84
82
|
functions: opt.functions,
|
|
85
83
|
sequential_mode: opt.sequential_mode
|
|
86
84
|
});
|
|
@@ -99,11 +97,10 @@ function createTask(opt, states = []) {
|
|
|
99
97
|
* @param {string} tag - Context tag identifier
|
|
100
98
|
* @param {number} token_limit - Token limit for summarization
|
|
101
99
|
* @param {Array} msgs - Initial messages
|
|
102
|
-
* @param {Function} tool_handler - Tool handler function
|
|
103
100
|
* @param {Object} config - Additional configuration
|
|
104
101
|
* @returns {Context} Proxied Context instance
|
|
105
102
|
*/
|
|
106
|
-
function createQ(prompt, parent, tag, token_limit, msgs,
|
|
103
|
+
function createQ(prompt, parent, tag, token_limit, msgs, config = {}) {
|
|
107
104
|
// For backward compatibility, if parent is a Context, get its task
|
|
108
105
|
let task = null;
|
|
109
106
|
if (parent && parent.task) {
|
|
@@ -114,7 +111,6 @@ function createQ(prompt, parent, tag, token_limit, msgs, tool_handler, config =
|
|
|
114
111
|
tag,
|
|
115
112
|
token_limit,
|
|
116
113
|
msgs,
|
|
117
|
-
tool_handler,
|
|
118
114
|
...config
|
|
119
115
|
});
|
|
120
116
|
|
package/itask.js
CHANGED
|
@@ -110,7 +110,6 @@ function Itask(opt, states){
|
|
|
110
110
|
// Store options for context creation (prompt, functions, etc.)
|
|
111
111
|
this.prompt = opt.prompt;
|
|
112
112
|
this.functions = opt.functions;
|
|
113
|
-
this.tool_handler = opt.tool_handler;
|
|
114
113
|
|
|
115
114
|
// register root if no explicit spawn_parent provided
|
|
116
115
|
// If opt.spawn_parent provided, spawn under it
|
package/msgs.js
CHANGED
|
@@ -24,7 +24,6 @@ class Context {
|
|
|
24
24
|
this.token_limit = config.token_limit || 1000000000;
|
|
25
25
|
this.lower_limit = this.token_limit * 0.85;
|
|
26
26
|
this.upper_limit = this.token_limit * 0.98;
|
|
27
|
-
this.tool_handler = config.tool_handler || task?.tool_handler;
|
|
28
27
|
this.functions = config.functions || task?.functions || null;
|
|
29
28
|
|
|
30
29
|
// Recursive depth and repetition control
|
|
@@ -63,8 +62,6 @@ class Context {
|
|
|
63
62
|
// Set the task reference (used when context is created separately)
|
|
64
63
|
setTask(task) {
|
|
65
64
|
this.task = task;
|
|
66
|
-
if (!this.tool_handler)
|
|
67
|
-
this.tool_handler = task?.tool_handler;
|
|
68
65
|
if (!this.functions)
|
|
69
66
|
this.functions = task?.functions;
|
|
70
67
|
}
|
|
@@ -287,10 +284,9 @@ class Context {
|
|
|
287
284
|
|
|
288
285
|
try {
|
|
289
286
|
const correspondingDeferred = deferredGroup.find(d => d.call.id === call.id);
|
|
290
|
-
const handler = correspondingDeferred?.originalMessage.opts.handler || this.tool_handler;
|
|
291
287
|
const timeout = correspondingDeferred?.originalMessage.opts.timeout;
|
|
292
288
|
|
|
293
|
-
result = await this._executeToolCallWithTimeout(call,
|
|
289
|
+
result = await this._executeToolCallWithTimeout(call, timeout);
|
|
294
290
|
if (_snap !== null &&
|
|
295
291
|
_snap !== JSON.stringify(this._snapshotPublicProps(this.task)))
|
|
296
292
|
this._appendToolDigest(call.function.name, result?.content || '');
|
|
@@ -669,7 +665,7 @@ class Context {
|
|
|
669
665
|
}
|
|
670
666
|
}
|
|
671
667
|
|
|
672
|
-
async _executeToolCallWithTimeout(call,
|
|
668
|
+
async _executeToolCallWithTimeout(call, customTimeoutMs = null) {
|
|
673
669
|
const timeoutMs = customTimeoutMs || 5000;
|
|
674
670
|
|
|
675
671
|
return new Promise(async (resolve) => {
|
|
@@ -688,7 +684,7 @@ class Context {
|
|
|
688
684
|
}, timeoutMs);
|
|
689
685
|
|
|
690
686
|
try {
|
|
691
|
-
const result = await this.interpretAndApplyChanges(call
|
|
687
|
+
const result = await this.interpretAndApplyChanges(call);
|
|
692
688
|
|
|
693
689
|
if (!completed) {
|
|
694
690
|
completed = true;
|
|
@@ -1005,7 +1001,7 @@ class Context {
|
|
|
1005
1001
|
? JSON.stringify(this._snapshotPublicProps(this.task)) : null;
|
|
1006
1002
|
try {
|
|
1007
1003
|
const result = await this._executeToolCallWithTimeout(
|
|
1008
|
-
call, o.opts?.
|
|
1004
|
+
call, o.opts?.timeout);
|
|
1009
1005
|
const item = toolCallsWithResults.find(item => item.call.id === call.id);
|
|
1010
1006
|
if (item) item.result = result;
|
|
1011
1007
|
if (_snap !== null &&
|
|
@@ -1064,27 +1060,84 @@ class Context {
|
|
|
1064
1060
|
}
|
|
1065
1061
|
}
|
|
1066
1062
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1063
|
+
/**
|
|
1064
|
+
* Search the Saico hierarchy for a TOOL_<toolName> method.
|
|
1065
|
+
* Order: current task → walk UP parents → walk DOWN children (BFS).
|
|
1066
|
+
*/
|
|
1067
|
+
_findToolImplementation(toolName) {
|
|
1068
|
+
const methodName = 'TOOL_' + toolName;
|
|
1069
|
+
const check = (task) =>
|
|
1070
|
+
task?._saico && typeof task._saico[methodName] === 'function' ? task._saico : null;
|
|
1071
|
+
|
|
1072
|
+
// 1. Current task
|
|
1073
|
+
let found = check(this.task);
|
|
1074
|
+
if (found) return { saico: found, methodName };
|
|
1075
|
+
|
|
1076
|
+
// 2. Walk UP parent chain
|
|
1077
|
+
let t = this.task?.parent;
|
|
1078
|
+
while (t) {
|
|
1079
|
+
found = check(t);
|
|
1080
|
+
if (found) return { saico: found, methodName };
|
|
1081
|
+
t = t.parent;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// 3. Walk DOWN from this.task (BFS)
|
|
1085
|
+
if (this.task) {
|
|
1086
|
+
const queue = [...this.task.child];
|
|
1087
|
+
while (queue.length > 0) {
|
|
1088
|
+
const child = queue.shift();
|
|
1089
|
+
if (child._completed) continue;
|
|
1090
|
+
found = check(child);
|
|
1091
|
+
if (found) return { saico: found, methodName };
|
|
1092
|
+
if (child.child?.size > 0) queue.push(...child.child);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
async interpretAndApplyChanges(call) {
|
|
1069
1100
|
if (!call)
|
|
1070
1101
|
return { content: '', functions: null };
|
|
1071
1102
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1103
|
+
const toolName = call.function.name;
|
|
1104
|
+
_log('apply tool', toolName);
|
|
1105
|
+
|
|
1106
|
+
const impl = this._findToolImplementation(toolName);
|
|
1107
|
+
if (!impl) {
|
|
1108
|
+
_log('No TOOL_ method found for:', toolName);
|
|
1109
|
+
return {
|
|
1110
|
+
content: `Error: No implementation found for tool "${toolName}". ` +
|
|
1111
|
+
`Expected a TOOL_${toolName}(args) method on a Saico instance in the hierarchy.`,
|
|
1112
|
+
functions: null
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
_log('invoking TOOL_' + toolName, 'on', impl.saico.name || impl.saico.constructor.name);
|
|
1117
|
+
|
|
1118
|
+
let parsedArgs;
|
|
1119
|
+
try {
|
|
1120
|
+
parsedArgs = JSON.parse(call.function.arguments);
|
|
1121
|
+
} catch (e) {
|
|
1122
|
+
return {
|
|
1123
|
+
content: `Error: Failed to parse arguments for tool "${toolName}": ${e.message}`,
|
|
1124
|
+
functions: null
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
let result = await impl.saico[impl.methodName](parsedArgs);
|
|
1075
1129
|
|
|
1076
1130
|
let content = result?.content || result || '';
|
|
1077
1131
|
let functions = result?.functions || null;
|
|
1078
1132
|
|
|
1079
1133
|
if (content && typeof content !== 'string')
|
|
1080
1134
|
content = JSON.stringify(content);
|
|
1081
|
-
else if (!content)
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
+`from the user`;
|
|
1135
|
+
else if (!content) {
|
|
1136
|
+
content = `tool call ${toolName} ${call.id} completed. do not reply. wait for the next msg `
|
|
1137
|
+
+ `from the user`;
|
|
1085
1138
|
}
|
|
1086
1139
|
|
|
1087
|
-
_log('FUNCTION RESULT',
|
|
1140
|
+
_log('FUNCTION RESULT', toolName, call.id, content.substring(0, 50) + '...',
|
|
1088
1141
|
functions ? 'with functions' : 'no functions');
|
|
1089
1142
|
return { content, functions };
|
|
1090
1143
|
}
|
package/package.json
CHANGED
package/saico.js
CHANGED
|
@@ -29,7 +29,6 @@ class Saico {
|
|
|
29
29
|
* @param {string} [opt.id] - Instance ID (auto-generated if omitted)
|
|
30
30
|
* @param {string} [opt.name] - Instance name (defaults to class name)
|
|
31
31
|
* @param {string} [opt.prompt] - Class-level system prompt
|
|
32
|
-
* @param {Function} [opt.tool_handler] - Tool handler function
|
|
33
32
|
* @param {Array} [opt.functions] - Available AI functions
|
|
34
33
|
* @param {string} [opt.key] - Redis key override (default: 'saico:<id>')
|
|
35
34
|
* @param {boolean} [opt.redis=true] - Set false to skip Redis proxy
|
|
@@ -53,7 +52,6 @@ class Saico {
|
|
|
53
52
|
// Public configuration
|
|
54
53
|
this.name = opt.name || this.constructor.name || 'saico';
|
|
55
54
|
this.prompt = opt.prompt || '';
|
|
56
|
-
this.tool_handler = opt.tool_handler || null;
|
|
57
55
|
this.functions = opt.functions || null;
|
|
58
56
|
|
|
59
57
|
// Absorbed from Sid
|
|
@@ -96,7 +94,6 @@ class Saico {
|
|
|
96
94
|
* @param {Object} opts
|
|
97
95
|
* @param {boolean} [opts.createQ] - If true, attach a message Q (Context)
|
|
98
96
|
* @param {string} [opts.prompt] - Additional prompt (appended to class-level)
|
|
99
|
-
* @param {Function} [opts.tool_handler] - Override tool handler
|
|
100
97
|
* @param {Array} [opts.functions] - Override functions
|
|
101
98
|
* @param {Array} [opts.states] - Task state functions
|
|
102
99
|
* @param {Itask} [opts.parent] - Parent task to spawn under
|
|
@@ -126,7 +123,6 @@ class Saico {
|
|
|
126
123
|
id: opts.taskId,
|
|
127
124
|
async: true,
|
|
128
125
|
store: this._store,
|
|
129
|
-
tool_handler: opts.tool_handler || this.tool_handler,
|
|
130
126
|
functions: opts.functions || this.functions,
|
|
131
127
|
bind: this, // State functions run with Saico instance as `this`
|
|
132
128
|
};
|
|
@@ -148,7 +144,6 @@ class Saico {
|
|
|
148
144
|
max_tool_repetition: opts.max_tool_repetition ?? this.sessionConfig.max_tool_repetition,
|
|
149
145
|
queue_limit: opts.queue_limit ?? this.sessionConfig.queue_limit,
|
|
150
146
|
min_chat_messages: opts.min_chat_messages ?? this.sessionConfig.min_chat_messages,
|
|
151
|
-
tool_handler: taskOpt.tool_handler,
|
|
152
147
|
functions: taskOpt.functions,
|
|
153
148
|
sequential_mode: opts.sequential_mode,
|
|
154
149
|
msgs: opts.msgs,
|
|
@@ -334,7 +329,6 @@ class Saico {
|
|
|
334
329
|
max_tool_repetition: opt.max_tool_repetition ?? this.sessionConfig.max_tool_repetition,
|
|
335
330
|
queue_limit: opt.queue_limit ?? this.sessionConfig.queue_limit,
|
|
336
331
|
min_chat_messages: opt.min_chat_messages ?? this.sessionConfig.min_chat_messages,
|
|
337
|
-
tool_handler: opt.tool_handler || this.tool_handler,
|
|
338
332
|
functions: opt.functions || this.functions,
|
|
339
333
|
});
|
|
340
334
|
childTask.setContext(childContext);
|
|
@@ -565,7 +559,7 @@ class Saico {
|
|
|
565
559
|
/**
|
|
566
560
|
* Restore a Saico instance from serialized data.
|
|
567
561
|
* @param {string|Object} data - Serialized data (JSON string or object)
|
|
568
|
-
* @param {Object} opt - Options (
|
|
562
|
+
* @param {Object} opt - Options (functions, store, states, etc.)
|
|
569
563
|
* @returns {Saico}
|
|
570
564
|
*/
|
|
571
565
|
static deserialize(data, opt = {}) {
|
|
@@ -578,7 +572,6 @@ class Saico {
|
|
|
578
572
|
userData: parsed.userData,
|
|
579
573
|
sessionConfig: parsed.sessionConfig,
|
|
580
574
|
isolate: parsed.isolate,
|
|
581
|
-
tool_handler: opt.tool_handler,
|
|
582
575
|
functions: opt.functions || parsed.task?.context?.functions,
|
|
583
576
|
store: opt.store,
|
|
584
577
|
redis: false, // No Redis proxy during deserialization
|
|
@@ -593,7 +586,6 @@ class Saico {
|
|
|
593
586
|
taskId: parsed.task.id,
|
|
594
587
|
tag: parsed.task.context?.tag,
|
|
595
588
|
chat_history: parsed.task.context?.chat_history,
|
|
596
|
-
tool_handler: opt.tool_handler,
|
|
597
589
|
functions: opt.functions || parsed.task.context?.functions,
|
|
598
590
|
states: opt.states || [],
|
|
599
591
|
...opt,
|