saico 2.4.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 +39 -34
- package/dynamo.js +9 -9
- package/index.js +1 -5
- package/itask.js +0 -1
- package/msgs.js +71 -18
- package/package.json +1 -1
- package/saico.js +46 -42
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
|
|
|
@@ -72,7 +69,7 @@ Saico separates construction from activation:
|
|
|
72
69
|
const agent = new Saico({
|
|
73
70
|
name: 'agent',
|
|
74
71
|
prompt: 'System prompt here',
|
|
75
|
-
|
|
72
|
+
dynamodb: { region: 'us-east-1', credentials: { accessKeyId: 'AK', secretAccessKey: 'SK' } },
|
|
76
73
|
});
|
|
77
74
|
|
|
78
75
|
// DB methods work before activation
|
|
@@ -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
|
|
@@ -194,8 +189,10 @@ new Saico({
|
|
|
194
189
|
// Storage
|
|
195
190
|
redis: true, // Set false to skip Redis proxy
|
|
196
191
|
key: 'custom-redis-key',
|
|
197
|
-
|
|
198
|
-
|
|
192
|
+
dynamodb: { // DynamoDB config (creates adapter)
|
|
193
|
+
region: 'us-east-1',
|
|
194
|
+
credentials: { accessKeyId: '...', secretAccessKey: '...' },
|
|
195
|
+
},
|
|
199
196
|
db: customAdapter, // Any adapter with put/get/delete/query interface
|
|
200
197
|
|
|
201
198
|
// User data
|
|
@@ -245,26 +242,26 @@ await agent.closeSession(); // Close context and cancel task
|
|
|
245
242
|
|
|
246
243
|
## Database Access
|
|
247
244
|
|
|
248
|
-
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()`.
|
|
249
246
|
|
|
250
247
|
```js
|
|
251
|
-
// CRUD
|
|
252
|
-
await agent.dbPutItem({ id: '123', name: 'test' });
|
|
253
|
-
const item = await agent.dbGetItem('id', '123');
|
|
254
|
-
await agent.dbDeleteItem('id', '123');
|
|
255
|
-
const items = await agent.dbQuery('email-index', 'email', 'user@test.com');
|
|
256
|
-
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');
|
|
257
254
|
|
|
258
255
|
// Updates
|
|
259
|
-
await agent.dbUpdate('id', '123', 'status', 'active');
|
|
260
|
-
await agent.dbUpdatePath('id', '123', [{ key: 'nested' }], 'field', 'value');
|
|
261
|
-
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');
|
|
262
259
|
|
|
263
260
|
// Counters
|
|
264
|
-
const nextId = await agent.dbNextCounterId('OrderId');
|
|
265
|
-
const count = await agent.dbGetCounterValue('OrderId');
|
|
266
|
-
await agent.dbSetCounterValue('OrderId', 100);
|
|
267
|
-
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');
|
|
268
265
|
```
|
|
269
266
|
|
|
270
267
|
Override `_deserializeRecord(raw)` to transform raw DB records on retrieval (e.g., restore class instances):
|
|
@@ -286,7 +283,6 @@ const json = agent.serialize();
|
|
|
286
283
|
|
|
287
284
|
// Restore
|
|
288
285
|
const restored = Saico.deserialize(json, {
|
|
289
|
-
tool_handler: myHandler,
|
|
290
286
|
functions: myFunctions,
|
|
291
287
|
});
|
|
292
288
|
```
|
|
@@ -309,16 +305,26 @@ agent.someProperty = 'value'; // Auto-saved to Redis
|
|
|
309
305
|
|
|
310
306
|
Properties prefixed with `_` are internal and not persisted.
|
|
311
307
|
|
|
312
|
-
## Tool
|
|
308
|
+
## Tool Implementation (TOOL_ methods)
|
|
309
|
+
|
|
310
|
+
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
311
|
|
|
314
312
|
```js
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
313
|
+
class MyAgent extends Saico {
|
|
314
|
+
async TOOL_get_weather(args) {
|
|
315
|
+
// args is already JSON.parse'd
|
|
316
|
+
return `Weather in ${args.location}: 72F, sunny`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async TOOL_search(args) {
|
|
320
|
+
const results = await search(args.query);
|
|
321
|
+
return { content: JSON.stringify(results), functions: updatedTools };
|
|
322
|
+
}
|
|
319
323
|
}
|
|
320
324
|
```
|
|
321
325
|
|
|
326
|
+
Return a string or `{ content: string, functions?: [] }`.
|
|
327
|
+
|
|
322
328
|
### Tool Safety Features
|
|
323
329
|
|
|
324
330
|
- **Depth control** — `max_depth` (default: 5) prevents infinite tool call recursion
|
|
@@ -339,13 +345,12 @@ const { createTask, createContext, createQ } = require('saico');
|
|
|
339
345
|
const task = createTask({
|
|
340
346
|
name: 'my-task',
|
|
341
347
|
prompt: 'You are helpful',
|
|
342
|
-
tool_handler: handler,
|
|
343
348
|
functions: tools
|
|
344
349
|
});
|
|
345
350
|
const reply = await task.sendMessage('Hello');
|
|
346
351
|
|
|
347
352
|
// Standalone context (legacy)
|
|
348
|
-
const ctx = createQ('System prompt', null, 'tag', 4000
|
|
353
|
+
const ctx = createQ('System prompt', null, 'tag', 4000);
|
|
349
354
|
const reply = await ctx.sendMessage('user', 'Hello', functions);
|
|
350
355
|
```
|
|
351
356
|
|
|
@@ -371,7 +376,7 @@ saico/
|
|
|
371
376
|
npm test
|
|
372
377
|
```
|
|
373
378
|
|
|
374
|
-
|
|
379
|
+
293 tests covering Saico lifecycle, task hierarchy, message handling, tool calls, DB adapters, serialization, and integration flows.
|
|
375
380
|
|
|
376
381
|
## Requirements
|
|
377
382
|
|
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/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,14 +29,11 @@ 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
|
|
36
35
|
* @param {boolean} [opt.isolate] - Isolate: don't aggregate from ancestors
|
|
37
|
-
* @param {
|
|
38
|
-
* @param {string} [opt.dynamodb_region] - AWS region for DynamoDB
|
|
39
|
-
* @param {Object} [opt.dynamodb_client] - Injectable DynamoDB client (for testing)
|
|
36
|
+
* @param {Object} [opt.dynamodb] - DynamoDB config { region, credentials: { accessKeyId, secretAccessKey }, client }
|
|
40
37
|
* @param {Object} [opt.db] - Pluggable DB backend
|
|
41
38
|
* @param {Object} [opt.store] - Store instance override
|
|
42
39
|
* @param {Object} [opt.userData] - Initial user data
|
|
@@ -53,7 +50,6 @@ class Saico {
|
|
|
53
50
|
// Public configuration
|
|
54
51
|
this.name = opt.name || this.constructor.name || 'saico';
|
|
55
52
|
this.prompt = opt.prompt || '';
|
|
56
|
-
this.tool_handler = opt.tool_handler || null;
|
|
57
53
|
this.functions = opt.functions || null;
|
|
58
54
|
|
|
59
55
|
// Absorbed from Sid
|
|
@@ -70,12 +66,12 @@ class Saico {
|
|
|
70
66
|
|
|
71
67
|
// DB backend — pluggable storage adapter.
|
|
72
68
|
this._db = opt.db || null;
|
|
73
|
-
if (!this._db && opt.
|
|
69
|
+
if (!this._db && opt.dynamodb) {
|
|
74
70
|
const { DynamoDBAdapter } = require('./dynamo.js');
|
|
75
71
|
this._db = new DynamoDBAdapter({
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
client: opt.
|
|
72
|
+
region: opt.dynamodb.region,
|
|
73
|
+
credentials: opt.dynamodb.credentials,
|
|
74
|
+
client: opt.dynamodb.client,
|
|
79
75
|
});
|
|
80
76
|
}
|
|
81
77
|
|
|
@@ -96,7 +92,6 @@ class Saico {
|
|
|
96
92
|
* @param {Object} opts
|
|
97
93
|
* @param {boolean} [opts.createQ] - If true, attach a message Q (Context)
|
|
98
94
|
* @param {string} [opts.prompt] - Additional prompt (appended to class-level)
|
|
99
|
-
* @param {Function} [opts.tool_handler] - Override tool handler
|
|
100
95
|
* @param {Array} [opts.functions] - Override functions
|
|
101
96
|
* @param {Array} [opts.states] - Task state functions
|
|
102
97
|
* @param {Itask} [opts.parent] - Parent task to spawn under
|
|
@@ -126,7 +121,6 @@ class Saico {
|
|
|
126
121
|
id: opts.taskId,
|
|
127
122
|
async: true,
|
|
128
123
|
store: this._store,
|
|
129
|
-
tool_handler: opts.tool_handler || this.tool_handler,
|
|
130
124
|
functions: opts.functions || this.functions,
|
|
131
125
|
bind: this, // State functions run with Saico instance as `this`
|
|
132
126
|
};
|
|
@@ -148,7 +142,6 @@ class Saico {
|
|
|
148
142
|
max_tool_repetition: opts.max_tool_repetition ?? this.sessionConfig.max_tool_repetition,
|
|
149
143
|
queue_limit: opts.queue_limit ?? this.sessionConfig.queue_limit,
|
|
150
144
|
min_chat_messages: opts.min_chat_messages ?? this.sessionConfig.min_chat_messages,
|
|
151
|
-
tool_handler: taskOpt.tool_handler,
|
|
152
145
|
functions: taskOpt.functions,
|
|
153
146
|
sequential_mode: opts.sequential_mode,
|
|
154
147
|
msgs: opts.msgs,
|
|
@@ -334,7 +327,6 @@ class Saico {
|
|
|
334
327
|
max_tool_repetition: opt.max_tool_repetition ?? this.sessionConfig.max_tool_repetition,
|
|
335
328
|
queue_limit: opt.queue_limit ?? this.sessionConfig.queue_limit,
|
|
336
329
|
min_chat_messages: opt.min_chat_messages ?? this.sessionConfig.min_chat_messages,
|
|
337
|
-
tool_handler: opt.tool_handler || this.tool_handler,
|
|
338
330
|
functions: opt.functions || this.functions,
|
|
339
331
|
});
|
|
340
332
|
childTask.setContext(childContext);
|
|
@@ -452,76 +444,90 @@ class Saico {
|
|
|
452
444
|
|
|
453
445
|
// ---- Generic DB access ----
|
|
454
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
|
+
|
|
455
461
|
async dbPutItem(item, table) {
|
|
456
|
-
|
|
457
|
-
return
|
|
462
|
+
const db = this._getDb();
|
|
463
|
+
return db.put(item, table);
|
|
458
464
|
}
|
|
459
465
|
|
|
460
466
|
async dbGetItem(key, value, table) {
|
|
461
|
-
|
|
462
|
-
const result = await
|
|
467
|
+
const db = this._getDb();
|
|
468
|
+
const result = await db.get(key, value, table);
|
|
463
469
|
return result ? this._deserializeRecord(result) : result;
|
|
464
470
|
}
|
|
465
471
|
|
|
466
472
|
async dbDeleteItem(key, value, table) {
|
|
467
|
-
|
|
468
|
-
return
|
|
473
|
+
const db = this._getDb();
|
|
474
|
+
return db.delete(key, value, table);
|
|
469
475
|
}
|
|
470
476
|
|
|
471
477
|
async dbQuery(index, key, value, table) {
|
|
472
|
-
|
|
473
|
-
const results = await
|
|
478
|
+
const db = this._getDb();
|
|
479
|
+
const results = await db.query(index, key, value, table);
|
|
474
480
|
return Array.isArray(results)
|
|
475
481
|
? results.map(r => this._deserializeRecord(r))
|
|
476
482
|
: results;
|
|
477
483
|
}
|
|
478
484
|
|
|
479
485
|
async dbGetAll(table) {
|
|
480
|
-
|
|
481
|
-
const results = await
|
|
486
|
+
const db = this._getDb();
|
|
487
|
+
const results = await db.getAll(table);
|
|
482
488
|
return Array.isArray(results)
|
|
483
489
|
? results.map(r => this._deserializeRecord(r))
|
|
484
490
|
: results;
|
|
485
491
|
}
|
|
486
492
|
|
|
487
493
|
async dbUpdate(key, keyValue, setKey, item, table) {
|
|
488
|
-
|
|
489
|
-
return
|
|
494
|
+
const db = this._getDb();
|
|
495
|
+
return db.update(key, keyValue, setKey, item, table);
|
|
490
496
|
}
|
|
491
497
|
|
|
492
498
|
async dbUpdatePath(key, keyValue, path, setKey, item, table) {
|
|
493
|
-
|
|
494
|
-
return
|
|
499
|
+
const db = this._getDb();
|
|
500
|
+
return db.updatePath(key, keyValue, path, setKey, item, table);
|
|
495
501
|
}
|
|
496
502
|
|
|
497
503
|
async dbListAppend(key, keyValue, setKey, item, table) {
|
|
498
|
-
|
|
499
|
-
return
|
|
504
|
+
const db = this._getDb();
|
|
505
|
+
return db.listAppend(key, keyValue, setKey, item, table);
|
|
500
506
|
}
|
|
501
507
|
|
|
502
508
|
async dbListAppendPath(key, keyValue, path, setKey, item, table) {
|
|
503
|
-
|
|
504
|
-
return
|
|
509
|
+
const db = this._getDb();
|
|
510
|
+
return db.listAppendPath(key, keyValue, path, setKey, item, table);
|
|
505
511
|
}
|
|
506
512
|
|
|
507
513
|
async dbNextCounterId(counter, table) {
|
|
508
|
-
|
|
509
|
-
return
|
|
514
|
+
const db = this._getDb();
|
|
515
|
+
return db.nextCounterId(counter, table);
|
|
510
516
|
}
|
|
511
517
|
|
|
512
518
|
async dbGetCounterValue(counter, table) {
|
|
513
|
-
|
|
514
|
-
return
|
|
519
|
+
const db = this._getDb();
|
|
520
|
+
return db.getCounterValue(counter, table);
|
|
515
521
|
}
|
|
516
522
|
|
|
517
523
|
async dbSetCounterValue(counter, value, table) {
|
|
518
|
-
|
|
519
|
-
return
|
|
524
|
+
const db = this._getDb();
|
|
525
|
+
return db.setCounterValue(counter, value, table);
|
|
520
526
|
}
|
|
521
527
|
|
|
522
528
|
async dbCountItems(table) {
|
|
523
|
-
|
|
524
|
-
return
|
|
529
|
+
const db = this._getDb();
|
|
530
|
+
return db.countItems(table);
|
|
525
531
|
}
|
|
526
532
|
|
|
527
533
|
// ---- DB deserialization hook ----
|
|
@@ -565,7 +571,7 @@ class Saico {
|
|
|
565
571
|
/**
|
|
566
572
|
* Restore a Saico instance from serialized data.
|
|
567
573
|
* @param {string|Object} data - Serialized data (JSON string or object)
|
|
568
|
-
* @param {Object} opt - Options (
|
|
574
|
+
* @param {Object} opt - Options (functions, store, states, etc.)
|
|
569
575
|
* @returns {Saico}
|
|
570
576
|
*/
|
|
571
577
|
static deserialize(data, opt = {}) {
|
|
@@ -578,7 +584,6 @@ class Saico {
|
|
|
578
584
|
userData: parsed.userData,
|
|
579
585
|
sessionConfig: parsed.sessionConfig,
|
|
580
586
|
isolate: parsed.isolate,
|
|
581
|
-
tool_handler: opt.tool_handler,
|
|
582
587
|
functions: opt.functions || parsed.task?.context?.functions,
|
|
583
588
|
store: opt.store,
|
|
584
589
|
redis: false, // No Redis proxy during deserialization
|
|
@@ -593,7 +598,6 @@ class Saico {
|
|
|
593
598
|
taskId: parsed.task.id,
|
|
594
599
|
tag: parsed.task.context?.tag,
|
|
595
600
|
chat_history: parsed.task.context?.chat_history,
|
|
596
|
-
tool_handler: opt.tool_handler,
|
|
597
601
|
functions: opt.functions || parsed.task.context?.functions,
|
|
598
602
|
states: opt.states || [],
|
|
599
603
|
...opt,
|