saico 2.5.0 → 2.6.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 +19 -17
- package/dynamo.js +9 -9
- package/package.json +1 -1
- package/saico.js +45 -33
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ Saico separates construction from activation:
|
|
|
69
69
|
const agent = new Saico({
|
|
70
70
|
name: 'agent',
|
|
71
71
|
prompt: 'System prompt here',
|
|
72
|
-
|
|
72
|
+
dynamodb: { region: 'us-east-1', credentials: { accessKeyId: 'AK', secretAccessKey: 'SK' } },
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
// DB methods work before activation
|
|
@@ -189,8 +189,10 @@ new Saico({
|
|
|
189
189
|
// Storage
|
|
190
190
|
redis: true, // Set false to skip Redis proxy
|
|
191
191
|
key: 'custom-redis-key',
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
dynamodb: { // DynamoDB config (creates adapter)
|
|
193
|
+
region: 'us-east-1',
|
|
194
|
+
credentials: { accessKeyId: '...', secretAccessKey: '...' },
|
|
195
|
+
},
|
|
194
196
|
db: customAdapter, // Any adapter with put/get/delete/query interface
|
|
195
197
|
|
|
196
198
|
// User data
|
|
@@ -240,26 +242,26 @@ await agent.closeSession(); // Close context and cancel task
|
|
|
240
242
|
|
|
241
243
|
## Database Access
|
|
242
244
|
|
|
243
|
-
Saico provides backend-agnostic DB methods. Configure via `
|
|
245
|
+
Saico provides backend-agnostic DB methods. Configure via `opt.dynamodb` (auto-creates DynamoDB adapter) or `opt.db` (any adapter). Table name is required on every call. Child Saico instances without their own DB inherit the parent's adapter automatically via `_getDb()`.
|
|
244
246
|
|
|
245
247
|
```js
|
|
246
|
-
// CRUD
|
|
247
|
-
await agent.dbPutItem({ id: '123', name: 'test' });
|
|
248
|
-
const item = await agent.dbGetItem('id', '123');
|
|
249
|
-
await agent.dbDeleteItem('id', '123');
|
|
250
|
-
const items = await agent.dbQuery('email-index', 'email', 'user@test.com');
|
|
251
|
-
const all = await agent.dbGetAll();
|
|
248
|
+
// CRUD — table name required on every call
|
|
249
|
+
await agent.dbPutItem({ id: '123', name: 'test' }, 'my-table');
|
|
250
|
+
const item = await agent.dbGetItem('id', '123', 'my-table');
|
|
251
|
+
await agent.dbDeleteItem('id', '123', 'my-table');
|
|
252
|
+
const items = await agent.dbQuery('email-index', 'email', 'user@test.com', 'my-table');
|
|
253
|
+
const all = await agent.dbGetAll('my-table');
|
|
252
254
|
|
|
253
255
|
// Updates
|
|
254
|
-
await agent.dbUpdate('id', '123', 'status', 'active');
|
|
255
|
-
await agent.dbUpdatePath('id', '123', [{ key: 'nested' }], 'field', 'value');
|
|
256
|
-
await agent.dbListAppend('id', '123', 'tags', 'new-tag');
|
|
256
|
+
await agent.dbUpdate('id', '123', 'status', 'active', 'my-table');
|
|
257
|
+
await agent.dbUpdatePath('id', '123', [{ key: 'nested' }], 'field', 'value', 'my-table');
|
|
258
|
+
await agent.dbListAppend('id', '123', 'tags', 'new-tag', 'my-table');
|
|
257
259
|
|
|
258
260
|
// Counters
|
|
259
|
-
const nextId = await agent.dbNextCounterId('OrderId');
|
|
260
|
-
const count = await agent.dbGetCounterValue('OrderId');
|
|
261
|
-
await agent.dbSetCounterValue('OrderId', 100);
|
|
262
|
-
const total = await agent.dbCountItems();
|
|
261
|
+
const nextId = await agent.dbNextCounterId('OrderId', 'counters');
|
|
262
|
+
const count = await agent.dbGetCounterValue('OrderId', 'counters');
|
|
263
|
+
await agent.dbSetCounterValue('OrderId', 100, 'counters');
|
|
264
|
+
const total = await agent.dbCountItems('my-table');
|
|
263
265
|
```
|
|
264
266
|
|
|
265
267
|
Override `_deserializeRecord(raw)` to transform raw DB records on retrieval (e.g., restore class instances):
|
package/dynamo.js
CHANGED
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* DynamoDBAdapter — Generic DynamoDB access layer.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* parameter that defaults to `this.defaultTable`.
|
|
6
|
+
* Provides CRUD, update, list-append, counter, and scan operations.
|
|
7
|
+
* Table name is required on every call.
|
|
9
8
|
*
|
|
10
9
|
* AWS SDK v3 packages are required only when this module is loaded.
|
|
11
10
|
*/
|
|
@@ -18,14 +17,16 @@ const { unmarshall, marshall } = require('@aws-sdk/util-dynamodb');
|
|
|
18
17
|
class DynamoDBAdapter {
|
|
19
18
|
/**
|
|
20
19
|
* @param {Object} opt
|
|
21
|
-
* @param {string} opt.table - Default table name for all operations
|
|
22
20
|
* @param {string} [opt.region='us-east-1'] - AWS region
|
|
21
|
+
* @param {Object} [opt.credentials] - AWS credentials { accessKeyId, secretAccessKey }
|
|
23
22
|
* @param {DynamoDBClient} [opt.client] - Injectable DynamoDB client (for testing)
|
|
24
23
|
*/
|
|
25
24
|
constructor(opt = {}) {
|
|
26
|
-
this.defaultTable = opt.table || null;
|
|
27
25
|
this._region = opt.region || 'us-east-1';
|
|
28
|
-
this._client = opt.client || new DynamoDBClient({
|
|
26
|
+
this._client = opt.client || new DynamoDBClient({
|
|
27
|
+
region: this._region,
|
|
28
|
+
...(opt.credentials && { credentials: opt.credentials }),
|
|
29
|
+
});
|
|
29
30
|
this.__docClient = null;
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -36,9 +37,8 @@ class DynamoDBAdapter {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
_table(table) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return t;
|
|
40
|
+
if (!table) throw new Error('DynamoDBAdapter: table name required');
|
|
41
|
+
return table;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
_unmarshall(item) {
|
package/package.json
CHANGED
package/saico.js
CHANGED
|
@@ -33,9 +33,7 @@ class Saico {
|
|
|
33
33
|
* @param {string} [opt.key] - Redis key override (default: 'saico:<id>')
|
|
34
34
|
* @param {boolean} [opt.redis=true] - Set false to skip Redis proxy
|
|
35
35
|
* @param {boolean} [opt.isolate] - Isolate: don't aggregate from ancestors
|
|
36
|
-
* @param {
|
|
37
|
-
* @param {string} [opt.dynamodb_region] - AWS region for DynamoDB
|
|
38
|
-
* @param {Object} [opt.dynamodb_client] - Injectable DynamoDB client (for testing)
|
|
36
|
+
* @param {Object} [opt.dynamodb] - DynamoDB config { region, credentials: { accessKeyId, secretAccessKey }, client }
|
|
39
37
|
* @param {Object} [opt.db] - Pluggable DB backend
|
|
40
38
|
* @param {Object} [opt.store] - Store instance override
|
|
41
39
|
* @param {Object} [opt.userData] - Initial user data
|
|
@@ -68,12 +66,12 @@ class Saico {
|
|
|
68
66
|
|
|
69
67
|
// DB backend — pluggable storage adapter.
|
|
70
68
|
this._db = opt.db || null;
|
|
71
|
-
if (!this._db && opt.
|
|
69
|
+
if (!this._db && opt.dynamodb) {
|
|
72
70
|
const { DynamoDBAdapter } = require('./dynamo.js');
|
|
73
71
|
this._db = new DynamoDBAdapter({
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
client: opt.
|
|
72
|
+
region: opt.dynamodb.region,
|
|
73
|
+
credentials: opt.dynamodb.credentials,
|
|
74
|
+
client: opt.dynamodb.client,
|
|
77
75
|
});
|
|
78
76
|
}
|
|
79
77
|
|
|
@@ -446,76 +444,90 @@ class Saico {
|
|
|
446
444
|
|
|
447
445
|
// ---- Generic DB access ----
|
|
448
446
|
|
|
447
|
+
/**
|
|
448
|
+
* Find a DB backend — own _db first, then walk UP the parent Saico chain.
|
|
449
|
+
* Throws if no backend found anywhere.
|
|
450
|
+
*/
|
|
451
|
+
_getDb() {
|
|
452
|
+
if (this._db) return this._db;
|
|
453
|
+
let task = this._task?.parent;
|
|
454
|
+
while (task) {
|
|
455
|
+
if (task._saico?._db) return task._saico._db;
|
|
456
|
+
task = task.parent;
|
|
457
|
+
}
|
|
458
|
+
throw new Error('No DB backend configured. Set opt.dynamodb or opt.db on this Saico or an ancestor.');
|
|
459
|
+
}
|
|
460
|
+
|
|
449
461
|
async dbPutItem(item, table) {
|
|
450
|
-
|
|
451
|
-
return
|
|
462
|
+
const db = this._getDb();
|
|
463
|
+
return db.put(item, table);
|
|
452
464
|
}
|
|
453
465
|
|
|
454
466
|
async dbGetItem(key, value, table) {
|
|
455
|
-
|
|
456
|
-
const result = await
|
|
467
|
+
const db = this._getDb();
|
|
468
|
+
const result = await db.get(key, value, table);
|
|
457
469
|
return result ? this._deserializeRecord(result) : result;
|
|
458
470
|
}
|
|
459
471
|
|
|
460
472
|
async dbDeleteItem(key, value, table) {
|
|
461
|
-
|
|
462
|
-
return
|
|
473
|
+
const db = this._getDb();
|
|
474
|
+
return db.delete(key, value, table);
|
|
463
475
|
}
|
|
464
476
|
|
|
465
477
|
async dbQuery(index, key, value, table) {
|
|
466
|
-
|
|
467
|
-
const results = await
|
|
478
|
+
const db = this._getDb();
|
|
479
|
+
const results = await db.query(index, key, value, table);
|
|
468
480
|
return Array.isArray(results)
|
|
469
481
|
? results.map(r => this._deserializeRecord(r))
|
|
470
482
|
: results;
|
|
471
483
|
}
|
|
472
484
|
|
|
473
485
|
async dbGetAll(table) {
|
|
474
|
-
|
|
475
|
-
const results = await
|
|
486
|
+
const db = this._getDb();
|
|
487
|
+
const results = await db.getAll(table);
|
|
476
488
|
return Array.isArray(results)
|
|
477
489
|
? results.map(r => this._deserializeRecord(r))
|
|
478
490
|
: results;
|
|
479
491
|
}
|
|
480
492
|
|
|
481
493
|
async dbUpdate(key, keyValue, setKey, item, table) {
|
|
482
|
-
|
|
483
|
-
return
|
|
494
|
+
const db = this._getDb();
|
|
495
|
+
return db.update(key, keyValue, setKey, item, table);
|
|
484
496
|
}
|
|
485
497
|
|
|
486
498
|
async dbUpdatePath(key, keyValue, path, setKey, item, table) {
|
|
487
|
-
|
|
488
|
-
return
|
|
499
|
+
const db = this._getDb();
|
|
500
|
+
return db.updatePath(key, keyValue, path, setKey, item, table);
|
|
489
501
|
}
|
|
490
502
|
|
|
491
503
|
async dbListAppend(key, keyValue, setKey, item, table) {
|
|
492
|
-
|
|
493
|
-
return
|
|
504
|
+
const db = this._getDb();
|
|
505
|
+
return db.listAppend(key, keyValue, setKey, item, table);
|
|
494
506
|
}
|
|
495
507
|
|
|
496
508
|
async dbListAppendPath(key, keyValue, path, setKey, item, table) {
|
|
497
|
-
|
|
498
|
-
return
|
|
509
|
+
const db = this._getDb();
|
|
510
|
+
return db.listAppendPath(key, keyValue, path, setKey, item, table);
|
|
499
511
|
}
|
|
500
512
|
|
|
501
513
|
async dbNextCounterId(counter, table) {
|
|
502
|
-
|
|
503
|
-
return
|
|
514
|
+
const db = this._getDb();
|
|
515
|
+
return db.nextCounterId(counter, table);
|
|
504
516
|
}
|
|
505
517
|
|
|
506
518
|
async dbGetCounterValue(counter, table) {
|
|
507
|
-
|
|
508
|
-
return
|
|
519
|
+
const db = this._getDb();
|
|
520
|
+
return db.getCounterValue(counter, table);
|
|
509
521
|
}
|
|
510
522
|
|
|
511
523
|
async dbSetCounterValue(counter, value, table) {
|
|
512
|
-
|
|
513
|
-
return
|
|
524
|
+
const db = this._getDb();
|
|
525
|
+
return db.setCounterValue(counter, value, table);
|
|
514
526
|
}
|
|
515
527
|
|
|
516
528
|
async dbCountItems(table) {
|
|
517
|
-
|
|
518
|
-
return
|
|
529
|
+
const db = this._getDb();
|
|
530
|
+
return db.countItems(table);
|
|
519
531
|
}
|
|
520
532
|
|
|
521
533
|
// ---- DB deserialization hook ----
|