saico 2.9.0 → 2.9.2
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 +19 -13
- package/package.json +1 -1
- package/saico.js +39 -27
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Saico -
|
|
1
|
+
# Saico - Simple AI-Agent Conversation Orchestrator
|
|
2
2
|
|
|
3
3
|
Saico is a Node.js library for building AI agents with hierarchical conversations, automatic context aggregation, and enterprise-grade tool calling. It manages nested task trees where each node can have its own message queue, system prompt, tools, and state — and the library automatically assembles the full payload sent to the LLM by walking the tree.
|
|
4
4
|
|
|
@@ -210,7 +210,7 @@ new Saico({
|
|
|
210
210
|
// Storage
|
|
211
211
|
redis: true, // Set false to skip Redis proxy
|
|
212
212
|
key: 'custom-redis-key',
|
|
213
|
-
store: 'my-table', // Table name for instance persistence (closeSession/
|
|
213
|
+
store: 'my-table', // Table name for instance persistence (store/closeSession/restore)
|
|
214
214
|
dynamodb: { // DynamoDB config (creates instance-level adapter)
|
|
215
215
|
region: 'us-east-1',
|
|
216
216
|
credentials: { accessKeyId: '...', secretAccessKey: '...' },
|
|
@@ -258,28 +258,33 @@ agent.getSessionInfo();
|
|
|
258
258
|
// userData, uptime
|
|
259
259
|
// }
|
|
260
260
|
|
|
261
|
-
await agent.
|
|
261
|
+
await agent.store(); // save to registered backend independently
|
|
262
|
+
await agent.closeSession(); // store + cancel task
|
|
262
263
|
|
|
263
264
|
// Restore from registered backend
|
|
264
|
-
const restored = await Saico.
|
|
265
|
+
const restored = await Saico.restore(agent.id, { store: 'sessions' });
|
|
265
266
|
```
|
|
266
267
|
|
|
267
268
|
## Database Access
|
|
268
269
|
|
|
269
|
-
Saico provides backend-agnostic DB methods. Configure via `Saico.registerBackend('dynamodb', config)` (library-level), `opt.dynamodb` (instance-level auto-creates adapter), or `opt.db` (any adapter).
|
|
270
|
+
Saico provides backend-agnostic DB methods. Configure via `Saico.registerBackend('dynamodb', config)` (library-level), `opt.dynamodb` (instance-level auto-creates adapter), or `opt.db` (any adapter). Key, key value, and table default to `'id'`, `this.id`, and `this._storeName` — so operating on own record is a one-liner. Child Saico instances inherit the parent's adapter via `_getDb()`, which also falls back to the registered backend.
|
|
270
271
|
|
|
271
272
|
```js
|
|
272
|
-
// CRUD —
|
|
273
|
+
// CRUD — shorthand (defaults: key='id', value=this.id, table=this._storeName)
|
|
274
|
+
const me = await agent.dbGetItem(); // get own record
|
|
275
|
+
await agent.dbDeleteItem(); // delete own record
|
|
276
|
+
// Explicit
|
|
273
277
|
await agent.dbPutItem({ id: '123', name: 'test' }, 'my-table');
|
|
274
278
|
const item = await agent.dbGetItem('id', '123', 'my-table');
|
|
275
279
|
await agent.dbDeleteItem('id', '123', 'my-table');
|
|
276
280
|
const items = await agent.dbQuery('email-index', 'email', 'user@test.com', 'my-table');
|
|
277
281
|
const all = await agent.dbGetAll('my-table');
|
|
278
282
|
|
|
279
|
-
// Updates
|
|
280
|
-
await agent.dbUpdate('
|
|
281
|
-
await agent.
|
|
282
|
-
await agent.
|
|
283
|
+
// Updates — setKey, item first; key, keyValue, table last (with defaults)
|
|
284
|
+
await agent.dbUpdate('status', 'active'); // update own record
|
|
285
|
+
await agent.dbUpdate('status', 'active', 'id', '123', 'my-table');
|
|
286
|
+
await agent.dbUpdatePath([{ key: 'nested' }], 'field', 'value');
|
|
287
|
+
await agent.dbListAppend('tags', 'new-tag');
|
|
283
288
|
|
|
284
289
|
// Counters
|
|
285
290
|
const nextId = await agent.dbNextCounterId('OrderId', 'counters');
|
|
@@ -312,8 +317,9 @@ const json = await agent.serialize();
|
|
|
312
317
|
const restored = await Saico.deserialize(json);
|
|
313
318
|
|
|
314
319
|
// Durable persistence (uses registered backend + opt.store table name)
|
|
315
|
-
await agent.
|
|
316
|
-
|
|
320
|
+
await agent.store(); // save independently
|
|
321
|
+
await agent.closeSession(); // store + cancel task
|
|
322
|
+
const restored2 = await Saico.restore(agent.id, { store: 'sessions' });
|
|
317
323
|
```
|
|
318
324
|
|
|
319
325
|
`prepareForStorage()` automatically picks up all non-underscore properties (id, name, prompt, userData, sessionConfig, tm_create, isolate, etc.) and produces compressed chat_history for the msgs Q.
|
|
@@ -405,7 +411,7 @@ saico/
|
|
|
405
411
|
npm test
|
|
406
412
|
```
|
|
407
413
|
|
|
408
|
-
300 tests covering Saico lifecycle, msgs Q ownership, spawn/spawnAndRun, task hierarchy, message handling, tool calls, DB adapters, async serialization, prepareForStorage, backend registration, persistence (closeSession/
|
|
414
|
+
300 tests covering Saico lifecycle, msgs Q ownership, spawn/spawnAndRun, task hierarchy, message handling, tool calls, DB adapters, async serialization, prepareForStorage, backend registration, persistence (store/closeSession/restore via registered backend), storage integration, and full hierarchy flows.
|
|
409
415
|
|
|
410
416
|
## Requirements
|
|
411
417
|
|
package/package.json
CHANGED
package/saico.js
CHANGED
|
@@ -38,7 +38,8 @@ class Saico {
|
|
|
38
38
|
* @param {boolean} [opt.redis=true] - Set false to skip Redis proxy
|
|
39
39
|
* @param {boolean} [opt.createQ] - Create message Q on activate()
|
|
40
40
|
* @param {boolean} [opt.isolate] - Isolate: don't aggregate from ancestors
|
|
41
|
-
* @param {Object} [opt.dynamodb] - DynamoDB config { region, credentials: { accessKeyId, secretAccessKey },
|
|
41
|
+
* @param {Object} [opt.dynamodb] - DynamoDB config { region, credentials: { accessKeyId, secretAccessKey },
|
|
42
|
+
* client }
|
|
42
43
|
* @param {Object} [opt.db] - Pluggable DB backend
|
|
43
44
|
* @param {string} [opt.store] - Table name for instance persistence
|
|
44
45
|
* @param {Object} [opt.userData] - Initial user data
|
|
@@ -46,7 +47,8 @@ class Saico {
|
|
|
46
47
|
*/
|
|
47
48
|
constructor(opt = {}) {
|
|
48
49
|
// Internal properties (underscore-prefixed, not persisted to Redis)
|
|
49
|
-
this.
|
|
50
|
+
this.name = opt.name || this.constructor.name || 'saico';
|
|
51
|
+
this.id = opt.id || this._genId();
|
|
50
52
|
this._task = null;
|
|
51
53
|
this._storeName = (typeof opt.store === 'string') ? opt.store : null;
|
|
52
54
|
this._opt = opt;
|
|
@@ -57,7 +59,6 @@ class Saico {
|
|
|
57
59
|
this.msgs_id = null;
|
|
58
60
|
|
|
59
61
|
// Public configuration
|
|
60
|
-
this.name = opt.name || this.constructor.name || 'saico';
|
|
61
62
|
this.prompt = opt.prompt || '';
|
|
62
63
|
this.functions = opt.functions || null;
|
|
63
64
|
this.createQ = opt.createQ || false;
|
|
@@ -501,19 +502,30 @@ class Saico {
|
|
|
501
502
|
}
|
|
502
503
|
|
|
503
504
|
/**
|
|
504
|
-
*
|
|
505
|
+
* Generate an id. Called by constructor when opt.id is not provided.
|
|
506
|
+
* Overridable by subclasses for custom ID schemes.
|
|
505
507
|
*/
|
|
506
|
-
|
|
507
|
-
|
|
508
|
+
_genId() {
|
|
509
|
+
return crypto.randomBytes(8).toString('hex');
|
|
510
|
+
}
|
|
508
511
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
512
|
+
/**
|
|
513
|
+
* Save instance state to registered backend under _storeName.
|
|
514
|
+
*/
|
|
515
|
+
async store() {
|
|
516
|
+
if (!this._storeName) return;
|
|
517
|
+
const backend = Saico.getBackend();
|
|
518
|
+
if (!backend) return;
|
|
519
|
+
const data = await this.prepareForStorage();
|
|
520
|
+
await backend.put(data, this._storeName);
|
|
521
|
+
}
|
|
516
522
|
|
|
523
|
+
/**
|
|
524
|
+
* Close the session — cancel task. Call store() first if persistence needed.
|
|
525
|
+
*/
|
|
526
|
+
async closeSession() {
|
|
527
|
+
if (!this._task) return;
|
|
528
|
+
await this.store();
|
|
517
529
|
this._task._ecancel();
|
|
518
530
|
}
|
|
519
531
|
|
|
@@ -534,23 +546,23 @@ class Saico {
|
|
|
534
546
|
throw new Error('No DB backend configured. Call Saico.registerBackend() or set opt.db.');
|
|
535
547
|
}
|
|
536
548
|
|
|
537
|
-
async dbPutItem(item, table) {
|
|
549
|
+
async dbPutItem(item, table = this._storeName) {
|
|
538
550
|
const db = this._getDb();
|
|
539
551
|
return db.put(item, table);
|
|
540
552
|
}
|
|
541
553
|
|
|
542
|
-
async dbGetItem(key, value, table) {
|
|
554
|
+
async dbGetItem(key = 'id', value = this.id, table = this._storeName) {
|
|
543
555
|
const db = this._getDb();
|
|
544
556
|
const result = await db.get(key, value, table);
|
|
545
557
|
return result ? this._deserializeRecord(result) : result;
|
|
546
558
|
}
|
|
547
559
|
|
|
548
|
-
async dbDeleteItem(key, value, table) {
|
|
560
|
+
async dbDeleteItem(key = 'id', value = this.id, table = this._storeName) {
|
|
549
561
|
const db = this._getDb();
|
|
550
562
|
return db.delete(key, value, table);
|
|
551
563
|
}
|
|
552
564
|
|
|
553
|
-
async dbQuery(index, key, value, table) {
|
|
565
|
+
async dbQuery(index, key, value, table = this._storeName) {
|
|
554
566
|
const db = this._getDb();
|
|
555
567
|
const results = await db.query(index, key, value, table);
|
|
556
568
|
return Array.isArray(results)
|
|
@@ -558,7 +570,7 @@ class Saico {
|
|
|
558
570
|
: results;
|
|
559
571
|
}
|
|
560
572
|
|
|
561
|
-
async dbGetAll(table) {
|
|
573
|
+
async dbGetAll(table = this._storeName) {
|
|
562
574
|
const db = this._getDb();
|
|
563
575
|
const results = await db.getAll(table);
|
|
564
576
|
return Array.isArray(results)
|
|
@@ -566,42 +578,42 @@ class Saico {
|
|
|
566
578
|
: results;
|
|
567
579
|
}
|
|
568
580
|
|
|
569
|
-
async dbUpdate(
|
|
581
|
+
async dbUpdate(setKey, item, key = 'id', keyValue = this.id, table = this._storeName) {
|
|
570
582
|
const db = this._getDb();
|
|
571
583
|
return db.update(key, keyValue, setKey, item, table);
|
|
572
584
|
}
|
|
573
585
|
|
|
574
|
-
async dbUpdatePath(
|
|
586
|
+
async dbUpdatePath(path, setKey, item, key = 'id', keyValue = this.id, table = this._storeName) {
|
|
575
587
|
const db = this._getDb();
|
|
576
588
|
return db.updatePath(key, keyValue, path, setKey, item, table);
|
|
577
589
|
}
|
|
578
590
|
|
|
579
|
-
async dbListAppend(
|
|
591
|
+
async dbListAppend(setKey, item, key = 'id', keyValue = this.id, table = this._storeName) {
|
|
580
592
|
const db = this._getDb();
|
|
581
593
|
return db.listAppend(key, keyValue, setKey, item, table);
|
|
582
594
|
}
|
|
583
595
|
|
|
584
|
-
async dbListAppendPath(
|
|
596
|
+
async dbListAppendPath(path, setKey, item, key = 'id', keyValue = this.id, table = this._storeName) {
|
|
585
597
|
const db = this._getDb();
|
|
586
598
|
return db.listAppendPath(key, keyValue, path, setKey, item, table);
|
|
587
599
|
}
|
|
588
600
|
|
|
589
|
-
async dbNextCounterId(counter, table) {
|
|
601
|
+
async dbNextCounterId(counter, table = this._storeName) {
|
|
590
602
|
const db = this._getDb();
|
|
591
603
|
return db.nextCounterId(counter, table);
|
|
592
604
|
}
|
|
593
605
|
|
|
594
|
-
async dbGetCounterValue(counter, table) {
|
|
606
|
+
async dbGetCounterValue(counter, table = this._storeName) {
|
|
595
607
|
const db = this._getDb();
|
|
596
608
|
return db.getCounterValue(counter, table);
|
|
597
609
|
}
|
|
598
610
|
|
|
599
|
-
async dbSetCounterValue(counter, value, table) {
|
|
611
|
+
async dbSetCounterValue(counter, value, table = this._storeName) {
|
|
600
612
|
const db = this._getDb();
|
|
601
613
|
return db.setCounterValue(counter, value, table);
|
|
602
614
|
}
|
|
603
615
|
|
|
604
|
-
async dbCountItems(table) {
|
|
616
|
+
async dbCountItems(table = this._storeName) {
|
|
605
617
|
const db = this._getDb();
|
|
606
618
|
return db.countItems(table);
|
|
607
619
|
}
|
|
@@ -718,7 +730,7 @@ class Saico {
|
|
|
718
730
|
* @param {Object} opt - Options (store: table name, backend, functions, states, etc.)
|
|
719
731
|
* @returns {Promise<Saico|null>}
|
|
720
732
|
*/
|
|
721
|
-
static async
|
|
733
|
+
static async restore(id, opt = {}) {
|
|
722
734
|
const backend = opt.backend || Saico.getBackend();
|
|
723
735
|
if (!backend)
|
|
724
736
|
throw new Error('No backend registered. Call Saico.registerBackend() first.');
|