saico 2.8.0 → 2.8.1
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 +8 -8
- package/index.js +3 -3
- package/msgs.js +29 -181
- package/package.json +1 -1
- package/saico.js +50 -32
package/README.md
CHANGED
|
@@ -260,7 +260,7 @@ agent.getSessionInfo();
|
|
|
260
260
|
await agent.closeSession(); // Saves full state to Store, cancels task
|
|
261
261
|
|
|
262
262
|
// Restore from Store
|
|
263
|
-
const restored = await Saico.rehydrate(agent.
|
|
263
|
+
const restored = await Saico.rehydrate(agent.id, { store });
|
|
264
264
|
```
|
|
265
265
|
|
|
266
266
|
## Database Access
|
|
@@ -307,7 +307,7 @@ const restored = Saico.deserialize(json);
|
|
|
307
307
|
|
|
308
308
|
// Durable persistence (compressed msgs, saved to Store)
|
|
309
309
|
await agent.closeSession();
|
|
310
|
-
const restored2 = await Saico.rehydrate(agent.
|
|
310
|
+
const restored2 = await Saico.rehydrate(agent.id, { store });
|
|
311
311
|
```
|
|
312
312
|
|
|
313
313
|
`serialize()` includes: id, name, prompt, userData, sessionConfig, tm_create, isolate, and full context state (raw messages, tool_digest). `closeSession()` saves the same shape but with compressed messages for durable storage.
|
|
@@ -330,7 +330,7 @@ Properties prefixed with `_` are internal and not persisted.
|
|
|
330
330
|
|
|
331
331
|
## Tool Implementation (TOOL_ methods)
|
|
332
332
|
|
|
333
|
-
Define tool implementations as `TOOL_`-prefixed methods on your Saico subclass. When the LLM returns a tool call,
|
|
333
|
+
Define tool implementations as `TOOL_`-prefixed methods on your Saico subclass. When the LLM returns a tool call, Saico automatically searches the task hierarchy (current → up parents → down children) to find and invoke the matching method with parsed arguments.
|
|
334
334
|
|
|
335
335
|
```js
|
|
336
336
|
class MyAgent extends Saico {
|
|
@@ -359,13 +359,13 @@ Return a string or `{ content: string, functions?: [] }`.
|
|
|
359
359
|
|
|
360
360
|
## Low-Level API
|
|
361
361
|
|
|
362
|
-
For cases where you need a standalone
|
|
362
|
+
For cases where you need a standalone message queue without the Saico master class:
|
|
363
363
|
|
|
364
364
|
```js
|
|
365
|
-
const {
|
|
365
|
+
const { createMsgs } = require('saico');
|
|
366
366
|
|
|
367
|
-
// Standalone
|
|
368
|
-
const ctx =
|
|
367
|
+
// Standalone message queue
|
|
368
|
+
const ctx = createMsgs('System prompt', { tag: 'my-tag', token_limit: 4000 });
|
|
369
369
|
const reply = await ctx.sendMessage('user', 'Hello', functions);
|
|
370
370
|
```
|
|
371
371
|
|
|
@@ -390,7 +390,7 @@ saico/
|
|
|
390
390
|
npm test
|
|
391
391
|
```
|
|
392
392
|
|
|
393
|
-
|
|
393
|
+
290 tests covering Saico lifecycle, context ownership, spawn/spawnAndRun, task hierarchy, message handling, tool calls, DB adapters, serialization, persistence (closeSession/rehydrate), and integration flows.
|
|
394
394
|
|
|
395
395
|
## Requirements
|
|
396
396
|
|
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Itask = require('./itask.js');
|
|
4
|
-
const {
|
|
4
|
+
const { Msgs, createMsgs } = require('./msgs.js');
|
|
5
5
|
const { Store, DynamoBackend } = require('./store.js');
|
|
6
6
|
const { Saico } = require('./saico.js');
|
|
7
7
|
const { DynamoDBAdapter } = require('./dynamo.js');
|
|
@@ -34,7 +34,7 @@ module.exports = {
|
|
|
34
34
|
|
|
35
35
|
// Core classes
|
|
36
36
|
Itask,
|
|
37
|
-
|
|
37
|
+
Msgs,
|
|
38
38
|
Store,
|
|
39
39
|
DynamoBackend,
|
|
40
40
|
|
|
@@ -42,7 +42,7 @@ module.exports = {
|
|
|
42
42
|
init,
|
|
43
43
|
|
|
44
44
|
// Factory
|
|
45
|
-
|
|
45
|
+
createMsgs,
|
|
46
46
|
|
|
47
47
|
// Utilities (re-export from util.js)
|
|
48
48
|
util: require('./util.js'),
|
package/msgs.js
CHANGED
|
@@ -8,23 +8,17 @@ const { _log, _lerr, _ldbg } = util;
|
|
|
8
8
|
const debug = 0;
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Key differences from the old Messages class:
|
|
14
|
-
* - Uses task hierarchy instead of parent/child messages
|
|
15
|
-
* - task reference replaces parent reference
|
|
16
|
-
* - getMsgContext() traverses task hierarchy
|
|
17
|
-
* - _createMsgQ() aggregates from task ancestors
|
|
11
|
+
* Msgs - Pure message queue with tool call handling, summarization, and LLM communication.
|
|
12
|
+
* Saico sets callback hooks after construction to wire in hierarchy access.
|
|
18
13
|
*/
|
|
19
|
-
class
|
|
20
|
-
constructor(prompt,
|
|
14
|
+
class Msgs {
|
|
15
|
+
constructor(prompt, config = {}) {
|
|
21
16
|
this.prompt = prompt;
|
|
22
|
-
this.task = task; // Reference to owning Itask (replaces parent)
|
|
23
17
|
this.tag = config.tag || crypto.randomBytes(4).toString('hex');
|
|
24
18
|
this.token_limit = config.token_limit || 1000000000;
|
|
25
19
|
this.lower_limit = this.token_limit * 0.85;
|
|
26
20
|
this.upper_limit = this.token_limit * 0.98;
|
|
27
|
-
this.functions = config.functions ||
|
|
21
|
+
this.functions = config.functions || null;
|
|
28
22
|
|
|
29
23
|
// Recursive depth and repetition control
|
|
30
24
|
this.max_depth = config.max_depth || 5;
|
|
@@ -50,18 +44,15 @@ class Context {
|
|
|
50
44
|
// Tool digest — persistent history of tool calls that mutated task state
|
|
51
45
|
this.tool_digest = config.tool_digest || [];
|
|
52
46
|
|
|
47
|
+
// Callback hooks — set by Saico after construction
|
|
48
|
+
this._findToolImpl = null; // (toolName) => { saico, methodName } | null
|
|
49
|
+
this._getSnapshot = null; // () => serializable snapshot for dirty detection
|
|
50
|
+
|
|
53
51
|
// Initialize messages: explicit msgs take priority over chat_history
|
|
54
52
|
this._chat_history = config.chat_history || null;
|
|
55
53
|
(config.msgs || []).forEach(m => this.push(m));
|
|
56
54
|
|
|
57
|
-
_log('created
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Set the task reference (used when context is created separately)
|
|
61
|
-
setTask(task) {
|
|
62
|
-
this.task = task;
|
|
63
|
-
if (!this.functions)
|
|
64
|
-
this.functions = task?.functions;
|
|
55
|
+
_log('created Msgs for tag', this.tag);
|
|
65
56
|
}
|
|
66
57
|
|
|
67
58
|
/**
|
|
@@ -135,32 +126,6 @@ class Context {
|
|
|
135
126
|
this.tool_digest = this.tool_digest.slice(-this.TOOL_DIGEST_LIMIT);
|
|
136
127
|
}
|
|
137
128
|
|
|
138
|
-
// Get the parent context by traversing task hierarchy (via Saico)
|
|
139
|
-
getParentContext() {
|
|
140
|
-
if (!this.task || !this.task.parent)
|
|
141
|
-
return null;
|
|
142
|
-
let task = this.task.parent;
|
|
143
|
-
while (task) {
|
|
144
|
-
if (task._saico?.context) return task._saico.context;
|
|
145
|
-
task = task.parent;
|
|
146
|
-
}
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Get all ancestor contexts via task hierarchy (via Saico)
|
|
151
|
-
getAncestorContexts() {
|
|
152
|
-
if (!this.task)
|
|
153
|
-
return [];
|
|
154
|
-
const contexts = [];
|
|
155
|
-
let task = this.task.parent;
|
|
156
|
-
while (task) {
|
|
157
|
-
if (task._saico?.context)
|
|
158
|
-
contexts.unshift(task._saico.context);
|
|
159
|
-
task = task.parent;
|
|
160
|
-
}
|
|
161
|
-
return contexts;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
129
|
_hasPendingToolCalls() {
|
|
165
130
|
const toolCallMsgs = this._msgs.filter(m => m.msg.tool_calls);
|
|
166
131
|
|
|
@@ -334,16 +299,15 @@ class Context {
|
|
|
334
299
|
};
|
|
335
300
|
} else {
|
|
336
301
|
this._trackActiveToolCall(call);
|
|
337
|
-
const _snap = this.
|
|
338
|
-
? JSON.stringify(this.
|
|
302
|
+
const _snap = this._getSnapshot
|
|
303
|
+
? JSON.stringify(this._getSnapshot()) : null;
|
|
339
304
|
|
|
340
305
|
try {
|
|
341
306
|
const correspondingDeferred = deferredGroup.find(d => d.call.id === call.id);
|
|
342
307
|
const timeout = correspondingDeferred?.originalMessage.opts.timeout;
|
|
343
308
|
|
|
344
309
|
result = await this._executeToolCallWithTimeout(call, timeout);
|
|
345
|
-
if (_snap !== null &&
|
|
346
|
-
_snap !== JSON.stringify(this._snapshotPublicProps(this.task)))
|
|
310
|
+
if (_snap !== null && _snap !== JSON.stringify(this._getSnapshot()))
|
|
347
311
|
this._appendToolDigest(call.function.name, result?.content || '');
|
|
348
312
|
} finally {
|
|
349
313
|
this._completeActiveToolCall(call);
|
|
@@ -433,24 +397,6 @@ class Context {
|
|
|
433
397
|
|
|
434
398
|
getSummaries() { return this._msgs.filter(m => m.opts.summary); }
|
|
435
399
|
|
|
436
|
-
// Get functions aggregated from this context and all ancestor contexts
|
|
437
|
-
getFunctions() {
|
|
438
|
-
const allFunctions = [];
|
|
439
|
-
|
|
440
|
-
// Get functions from ancestor contexts via task hierarchy
|
|
441
|
-
const ancestorContexts = this.getAncestorContexts();
|
|
442
|
-
for (const ctx of ancestorContexts) {
|
|
443
|
-
if (ctx.functions && Array.isArray(ctx.functions))
|
|
444
|
-
allFunctions.push(...ctx.functions);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Add our own functions
|
|
448
|
-
if (this.functions && Array.isArray(this.functions))
|
|
449
|
-
allFunctions.push(...this.functions);
|
|
450
|
-
|
|
451
|
-
return allFunctions.length > 0 ? allFunctions : null;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
400
|
async summarizeMessages() {
|
|
455
401
|
const tokens = util.countTokens(this.__msgs);
|
|
456
402
|
if (tokens < this.lower_limit)
|
|
@@ -459,7 +405,7 @@ class Context {
|
|
|
459
405
|
}
|
|
460
406
|
|
|
461
407
|
async close() {
|
|
462
|
-
_log('Closing
|
|
408
|
+
_log('Closing Msgs tag', this.tag);
|
|
463
409
|
|
|
464
410
|
if (this._sequential_mode && this._processing_sequential) {
|
|
465
411
|
_ldbg('Sequential mode: waiting for current message to complete before closing tag', this.tag);
|
|
@@ -472,22 +418,8 @@ class Context {
|
|
|
472
418
|
}
|
|
473
419
|
}
|
|
474
420
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
if (parentCtx && this._waitingQueue.length > 0) {
|
|
478
|
-
_log('Moving', this._waitingQueue.length, 'waiting messages to parent context');
|
|
479
|
-
parentCtx._waitingQueue.push(...this._waitingQueue);
|
|
480
|
-
this._waitingQueue = [];
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
if (parentCtx && this._sequential_queue.length > 0) {
|
|
484
|
-
_log('Moving', this._sequential_queue.length, 'sequential queue messages to parent context');
|
|
485
|
-
parentCtx._sequential_queue.push(...this._sequential_queue);
|
|
486
|
-
this._sequential_queue = [];
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
await this._summarizeContext(true, parentCtx);
|
|
490
|
-
_log('Finished closing Context tag', this.tag);
|
|
421
|
+
await this._summarizeContext(true);
|
|
422
|
+
_log('Finished closing Msgs tag', this.tag);
|
|
491
423
|
}
|
|
492
424
|
|
|
493
425
|
|
|
@@ -591,38 +523,6 @@ class Context {
|
|
|
591
523
|
return summary;
|
|
592
524
|
}
|
|
593
525
|
|
|
594
|
-
// Get message context - walks up task hierarchy to collect prompts and summaries
|
|
595
|
-
getMsgContext(add_tag) {
|
|
596
|
-
const msgs = [];
|
|
597
|
-
|
|
598
|
-
// Get context from ancestor tasks via task hierarchy
|
|
599
|
-
const ancestorContexts = this.getAncestorContexts();
|
|
600
|
-
for (const ctx of ancestorContexts) {
|
|
601
|
-
if (ctx.prompt)
|
|
602
|
-
msgs.push({role: 'system', content: ctx.prompt});
|
|
603
|
-
// Add summaries from ancestor contexts
|
|
604
|
-
const summaries = ctx._msgs.filter(m => m.opts.summary || m.msg.role === 'system').map(m => {
|
|
605
|
-
if (add_tag)
|
|
606
|
-
m.msg.tag = ctx.tag;
|
|
607
|
-
return m.msg;
|
|
608
|
-
});
|
|
609
|
-
msgs.push(...summaries);
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// Add this context's prompt
|
|
613
|
-
if (this.prompt)
|
|
614
|
-
msgs.push({role: 'system', content: this.prompt});
|
|
615
|
-
|
|
616
|
-
// Add this context's summaries
|
|
617
|
-
const mySummaries = this._msgs.filter(m => m.opts.summary || m.msg.role === 'system').map(m => {
|
|
618
|
-
if (add_tag)
|
|
619
|
-
m.msg.tag = this.tag;
|
|
620
|
-
return m.msg;
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
return msgs.concat(mySummaries);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
526
|
_createMsgObj(role, content, functions, opts) {
|
|
627
527
|
const name = opts?.name;
|
|
628
528
|
const tool_call_id = opts?.tool_call_id;
|
|
@@ -952,10 +852,10 @@ class Context {
|
|
|
952
852
|
? [...o.opts._aggregatedFunctions, ...messageFuncs]
|
|
953
853
|
: null;
|
|
954
854
|
} else {
|
|
955
|
-
const
|
|
855
|
+
const ownFuncs = this.functions || [];
|
|
956
856
|
const messageFuncs = o.functions || [];
|
|
957
|
-
funcs = [...
|
|
958
|
-
? [...
|
|
857
|
+
funcs = [...ownFuncs, ...messageFuncs].length > 0
|
|
858
|
+
? [...ownFuncs, ...messageFuncs]
|
|
959
859
|
: null;
|
|
960
860
|
}
|
|
961
861
|
|
|
@@ -1024,15 +924,15 @@ class Context {
|
|
|
1024
924
|
|
|
1025
925
|
for (const { call, isDuplicate } of toolCallsWithResults) {
|
|
1026
926
|
if (!isDuplicate) {
|
|
1027
|
-
const _snap = this.
|
|
1028
|
-
? JSON.stringify(this.
|
|
927
|
+
const _snap = this._getSnapshot
|
|
928
|
+
? JSON.stringify(this._getSnapshot()) : null;
|
|
1029
929
|
try {
|
|
1030
930
|
const result = await this._executeToolCallWithTimeout(
|
|
1031
931
|
call, o.opts?.timeout);
|
|
1032
932
|
const item = toolCallsWithResults.find(item => item.call.id === call.id);
|
|
1033
933
|
if (item) item.result = result;
|
|
1034
934
|
if (_snap !== null &&
|
|
1035
|
-
_snap !== JSON.stringify(this.
|
|
935
|
+
_snap !== JSON.stringify(this._getSnapshot()))
|
|
1036
936
|
this._appendToolDigest(call.function.name, result?.content || '');
|
|
1037
937
|
} finally {
|
|
1038
938
|
this._completeActiveToolCall(call);
|
|
@@ -1088,39 +988,11 @@ class Context {
|
|
|
1088
988
|
}
|
|
1089
989
|
|
|
1090
990
|
/**
|
|
1091
|
-
*
|
|
1092
|
-
*
|
|
991
|
+
* Find a TOOL_<toolName> implementation. Delegates to _findToolImpl callback
|
|
992
|
+
* set by Saico, which searches the hierarchy.
|
|
1093
993
|
*/
|
|
1094
994
|
_findToolImplementation(toolName) {
|
|
1095
|
-
|
|
1096
|
-
const check = (task) =>
|
|
1097
|
-
task?._saico && typeof task._saico[methodName] === 'function' ? task._saico : null;
|
|
1098
|
-
|
|
1099
|
-
// 1. Current task
|
|
1100
|
-
let found = check(this.task);
|
|
1101
|
-
if (found) return { saico: found, methodName };
|
|
1102
|
-
|
|
1103
|
-
// 2. Walk UP parent chain
|
|
1104
|
-
let t = this.task?.parent;
|
|
1105
|
-
while (t) {
|
|
1106
|
-
found = check(t);
|
|
1107
|
-
if (found) return { saico: found, methodName };
|
|
1108
|
-
t = t.parent;
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
// 3. Walk DOWN from this.task (BFS)
|
|
1112
|
-
if (this.task) {
|
|
1113
|
-
const queue = [...this.task.child];
|
|
1114
|
-
while (queue.length > 0) {
|
|
1115
|
-
const child = queue.shift();
|
|
1116
|
-
if (child._completed) continue;
|
|
1117
|
-
found = check(child);
|
|
1118
|
-
if (found) return { saico: found, methodName };
|
|
1119
|
-
if (child.child?.size > 0) queue.push(...child.child);
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
return null;
|
|
995
|
+
return this._findToolImpl ? this._findToolImpl(toolName) : null;
|
|
1124
996
|
}
|
|
1125
997
|
|
|
1126
998
|
async interpretAndApplyChanges(call) {
|
|
@@ -1169,35 +1041,11 @@ class Context {
|
|
|
1169
1041
|
return { content, functions };
|
|
1170
1042
|
}
|
|
1171
1043
|
|
|
1172
|
-
// Spawn child context (creates a child task with its own context)
|
|
1173
|
-
spawnChild(prompt, tag, config = {}) {
|
|
1174
|
-
if (!this.task) {
|
|
1175
|
-
// If no task, create a standalone context
|
|
1176
|
-
return createContext(prompt, null, { ...config, tag });
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// Create a child task with its own context
|
|
1180
|
-
const Itask = require('./itask.js');
|
|
1181
|
-
const childTask = new Itask({
|
|
1182
|
-
name: tag || 'child-context',
|
|
1183
|
-
async: true,
|
|
1184
|
-
}, []);
|
|
1185
|
-
this.task.spawn(childTask);
|
|
1186
|
-
|
|
1187
|
-
const childContext = new Context(prompt, childTask, { ...config, tag });
|
|
1188
|
-
// Store context on Saico if present, otherwise just set on task reference
|
|
1189
|
-
if (childTask._saico) {
|
|
1190
|
-
childTask._saico.context = childContext;
|
|
1191
|
-
childTask._saico.context_id = childContext.tag;
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
return childContext;
|
|
1195
|
-
}
|
|
1196
1044
|
}
|
|
1197
1045
|
|
|
1198
|
-
// Factory function to create a
|
|
1199
|
-
function
|
|
1200
|
-
const instance = new
|
|
1046
|
+
// Factory function to create a Msgs instance with Proxy wrapper
|
|
1047
|
+
function createMsgs(prompt, config = {}) {
|
|
1048
|
+
const instance = new Msgs(prompt, config);
|
|
1201
1049
|
|
|
1202
1050
|
return new Proxy(instance, {
|
|
1203
1051
|
get(target, prop, receiver) {
|
|
@@ -1246,4 +1094,4 @@ function createContext(prompt, task, config = {}) {
|
|
|
1246
1094
|
});
|
|
1247
1095
|
}
|
|
1248
1096
|
|
|
1249
|
-
module.exports = {
|
|
1097
|
+
module.exports = { Msgs, createMsgs };
|
package/package.json
CHANGED
package/saico.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const Itask = require('./itask.js');
|
|
5
|
-
const {
|
|
5
|
+
const { Msgs } = require('./msgs.js');
|
|
6
6
|
const { Store } = require('./store.js');
|
|
7
7
|
const util = require('./util.js');
|
|
8
8
|
|
|
@@ -47,7 +47,7 @@ class Saico {
|
|
|
47
47
|
*/
|
|
48
48
|
constructor(opt = {}) {
|
|
49
49
|
// Internal properties (underscore-prefixed, not persisted to Redis)
|
|
50
|
-
this.
|
|
50
|
+
this.id = opt.id || crypto.randomBytes(8).toString('hex');
|
|
51
51
|
this._task = null;
|
|
52
52
|
this._store = opt.store || Store.instance || null;
|
|
53
53
|
this._opt = opt;
|
|
@@ -91,7 +91,7 @@ class Saico {
|
|
|
91
91
|
try {
|
|
92
92
|
const redis = require('./redis.js');
|
|
93
93
|
if (redis.rclient && opt.redis !== false) {
|
|
94
|
-
const key = 'saico:' + (opt.key || this.
|
|
94
|
+
const key = 'saico:' + (opt.key || this.id);
|
|
95
95
|
return redis.createObservableForRedis(key, this);
|
|
96
96
|
}
|
|
97
97
|
} catch (e) { /* redis not available */ }
|
|
@@ -159,8 +159,14 @@ class Saico {
|
|
|
159
159
|
const augmentedPrompt = effectivePrompt
|
|
160
160
|
? effectivePrompt + Saico.BACKEND_EXPLANATION
|
|
161
161
|
: '';
|
|
162
|
-
const
|
|
163
|
-
this.
|
|
162
|
+
const msgs = new Msgs(augmentedPrompt, contextConfig);
|
|
163
|
+
this.context = msgs;
|
|
164
|
+
this.context_id = makeId(16);
|
|
165
|
+
msgs.tag = this.context_id;
|
|
166
|
+
|
|
167
|
+
// Wire callbacks for hierarchy access
|
|
168
|
+
msgs._findToolImpl = (toolName) => this._findToolImpl(toolName);
|
|
169
|
+
msgs._getSnapshot = () => msgs._snapshotPublicProps(this);
|
|
164
170
|
}
|
|
165
171
|
|
|
166
172
|
return this;
|
|
@@ -168,29 +174,6 @@ class Saico {
|
|
|
168
174
|
|
|
169
175
|
// ---- Context management (owned by Saico, not Itask) ----
|
|
170
176
|
|
|
171
|
-
/**
|
|
172
|
-
* Set context on this Saico instance.
|
|
173
|
-
* Generates context_id, sets context.tag, and calls context.setTask().
|
|
174
|
-
*/
|
|
175
|
-
setContext(context) {
|
|
176
|
-
this.context = context;
|
|
177
|
-
// Generate context_id if not already set
|
|
178
|
-
if (!this.context_id) {
|
|
179
|
-
if (this._store)
|
|
180
|
-
this.context_id = this._store.generateId();
|
|
181
|
-
else if (Store.instance)
|
|
182
|
-
this.context_id = Store.instance.generateId();
|
|
183
|
-
else
|
|
184
|
-
this.context_id = makeId(16);
|
|
185
|
-
}
|
|
186
|
-
if (context) {
|
|
187
|
-
context.tag = this.context_id;
|
|
188
|
-
if (typeof context.setTask === 'function')
|
|
189
|
-
context.setTask(this._task);
|
|
190
|
-
}
|
|
191
|
-
return this;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
177
|
/**
|
|
195
178
|
* Find the nearest context walking UP the Saico/task hierarchy.
|
|
196
179
|
*/
|
|
@@ -444,6 +427,41 @@ class Saico {
|
|
|
444
427
|
return parts.length > 0 ? parts : null;
|
|
445
428
|
}
|
|
446
429
|
|
|
430
|
+
// ---- Tool implementation search ----
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Search the Saico hierarchy for a TOOL_<toolName> method.
|
|
434
|
+
* Order: current task → walk UP parents → walk DOWN children (BFS).
|
|
435
|
+
*/
|
|
436
|
+
_findToolImpl(toolName) {
|
|
437
|
+
const methodName = 'TOOL_' + toolName;
|
|
438
|
+
const check = (task) =>
|
|
439
|
+
task?._saico && typeof task._saico[methodName] === 'function' ? task._saico : null;
|
|
440
|
+
|
|
441
|
+
let found = check(this._task);
|
|
442
|
+
if (found) return { saico: found, methodName };
|
|
443
|
+
|
|
444
|
+
let t = this._task?.parent;
|
|
445
|
+
while (t) {
|
|
446
|
+
found = check(t);
|
|
447
|
+
if (found) return { saico: found, methodName };
|
|
448
|
+
t = t.parent;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (this._task) {
|
|
452
|
+
const queue = [...this._task.child];
|
|
453
|
+
while (queue.length > 0) {
|
|
454
|
+
const child = queue.shift();
|
|
455
|
+
if (child._completed) continue;
|
|
456
|
+
found = check(child);
|
|
457
|
+
if (found) return { saico: found, methodName };
|
|
458
|
+
if (child.child?.size > 0) queue.push(...child.child);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
|
|
447
465
|
// ---- User Data (absorbed from Sid) ----
|
|
448
466
|
|
|
449
467
|
setUserData(key, value) {
|
|
@@ -464,7 +482,7 @@ class Saico {
|
|
|
464
482
|
|
|
465
483
|
getSessionInfo() {
|
|
466
484
|
return {
|
|
467
|
-
id: this.
|
|
485
|
+
id: this.id,
|
|
468
486
|
name: this.name,
|
|
469
487
|
running: this._task?.running || false,
|
|
470
488
|
completed: this._task?._completed || false,
|
|
@@ -488,7 +506,7 @@ class Saico {
|
|
|
488
506
|
if (store && this.context) {
|
|
489
507
|
const { chat_history, tool_digest } = await this.context.prepareForStorage();
|
|
490
508
|
const data = {
|
|
491
|
-
id: this.
|
|
509
|
+
id: this.id,
|
|
492
510
|
name: this.name,
|
|
493
511
|
prompt: this.prompt,
|
|
494
512
|
userData: this.userData,
|
|
@@ -504,7 +522,7 @@ class Saico {
|
|
|
504
522
|
functions: this.context.functions,
|
|
505
523
|
},
|
|
506
524
|
};
|
|
507
|
-
await store.save(this.
|
|
525
|
+
await store.save(this.id, data);
|
|
508
526
|
}
|
|
509
527
|
|
|
510
528
|
this._task._ecancel();
|
|
@@ -617,7 +635,7 @@ class Saico {
|
|
|
617
635
|
*/
|
|
618
636
|
serialize() {
|
|
619
637
|
const data = {
|
|
620
|
-
id: this.
|
|
638
|
+
id: this.id,
|
|
621
639
|
name: this.name,
|
|
622
640
|
prompt: this.prompt,
|
|
623
641
|
userData: this.userData,
|