saico 2.3.0 → 2.4.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 +287 -300
- package/index.js +1 -4
- package/itask.js +16 -3
- package/msgs.js +35 -81
- package/package.json +1 -2
- package/saico.js +307 -35
- package/sid.js +0 -248
package/saico.js
CHANGED
|
@@ -17,6 +17,9 @@ const { Store } = require('./store.js');
|
|
|
17
17
|
* message Q context (when opts.createQ is true).
|
|
18
18
|
* - DB access works before and after activation.
|
|
19
19
|
*
|
|
20
|
+
* Saico orchestrates the full message payload sent to the LLM by walking its
|
|
21
|
+
* parent chain to aggregate prompts, tools, digests, and state summaries.
|
|
22
|
+
*
|
|
20
23
|
* `new Saico(opt)` returns a Redis observable proxy of the instance when
|
|
21
24
|
* Redis is available, enabling automatic persistence of public properties.
|
|
22
25
|
*/
|
|
@@ -30,10 +33,14 @@ class Saico {
|
|
|
30
33
|
* @param {Array} [opt.functions] - Available AI functions
|
|
31
34
|
* @param {string} [opt.key] - Redis key override (default: 'saico:<id>')
|
|
32
35
|
* @param {boolean} [opt.redis=true] - Set false to skip Redis proxy
|
|
36
|
+
* @param {boolean} [opt.isolate] - Isolate: don't aggregate from ancestors
|
|
33
37
|
* @param {string} [opt.dynamodb_table] - DynamoDB table name (enables db accessor)
|
|
34
38
|
* @param {string} [opt.dynamodb_region] - AWS region for DynamoDB
|
|
35
39
|
* @param {Object} [opt.dynamodb_client] - Injectable DynamoDB client (for testing)
|
|
40
|
+
* @param {Object} [opt.db] - Pluggable DB backend
|
|
36
41
|
* @param {Object} [opt.store] - Store instance override
|
|
42
|
+
* @param {Object} [opt.userData] - Initial user data
|
|
43
|
+
* @param {Object} [opt.sessionConfig] - Session config overrides
|
|
37
44
|
*/
|
|
38
45
|
constructor(opt = {}) {
|
|
39
46
|
// Internal properties (underscore-prefixed, not persisted to Redis)
|
|
@@ -41,6 +48,7 @@ class Saico {
|
|
|
41
48
|
this._task = null;
|
|
42
49
|
this._store = opt.store || Store.instance || null;
|
|
43
50
|
this._opt = opt;
|
|
51
|
+
this._isolate = opt.isolate || false;
|
|
44
52
|
|
|
45
53
|
// Public configuration
|
|
46
54
|
this.name = opt.name || this.constructor.name || 'saico';
|
|
@@ -48,10 +56,19 @@ class Saico {
|
|
|
48
56
|
this.tool_handler = opt.tool_handler || null;
|
|
49
57
|
this.functions = opt.functions || null;
|
|
50
58
|
|
|
59
|
+
// Absorbed from Sid
|
|
60
|
+
this.userData = opt.userData || {};
|
|
61
|
+
this.tm_create = Date.now();
|
|
62
|
+
this.sessionConfig = {
|
|
63
|
+
token_limit: opt.token_limit,
|
|
64
|
+
max_depth: opt.max_depth,
|
|
65
|
+
max_tool_repetition: opt.max_tool_repetition,
|
|
66
|
+
queue_limit: opt.queue_limit,
|
|
67
|
+
min_chat_messages: opt.min_chat_messages,
|
|
68
|
+
...opt.sessionConfig,
|
|
69
|
+
};
|
|
70
|
+
|
|
51
71
|
// DB backend — pluggable storage adapter.
|
|
52
|
-
// Any adapter that implements the same interface (put/get/delete/query/
|
|
53
|
-
// getAll/update/updatePath/listAppend/listAppendPath/nextCounterId/
|
|
54
|
-
// getCounterValue/setCounterValue/countItems) can be used.
|
|
55
72
|
this._db = opt.db || null;
|
|
56
73
|
if (!this._db && opt.dynamodb_table) {
|
|
57
74
|
const { DynamoDBAdapter } = require('./dynamo.js');
|
|
@@ -119,21 +136,18 @@ class Saico {
|
|
|
119
136
|
|
|
120
137
|
this._task = new Itask(taskOpt, states);
|
|
121
138
|
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
this._task.getStateSummary = function () {
|
|
125
|
-
return saicoInstance.getStateSummary();
|
|
126
|
-
};
|
|
139
|
+
// Store Saico reference on task for parent chain traversal
|
|
140
|
+
this._task._saico = this;
|
|
127
141
|
|
|
128
142
|
// Create message Q context if requested (only via createQ flag, NOT prompt)
|
|
129
143
|
if (opts.createQ) {
|
|
130
144
|
const contextConfig = {
|
|
131
145
|
tag: opts.tag || this._task.id,
|
|
132
|
-
token_limit: opts.token_limit,
|
|
133
|
-
max_depth: opts.max_depth,
|
|
134
|
-
max_tool_repetition: opts.max_tool_repetition,
|
|
135
|
-
queue_limit: opts.queue_limit,
|
|
136
|
-
min_chat_messages: opts.min_chat_messages,
|
|
146
|
+
token_limit: opts.token_limit ?? this.sessionConfig.token_limit,
|
|
147
|
+
max_depth: opts.max_depth ?? this.sessionConfig.max_depth,
|
|
148
|
+
max_tool_repetition: opts.max_tool_repetition ?? this.sessionConfig.max_tool_repetition,
|
|
149
|
+
queue_limit: opts.queue_limit ?? this.sessionConfig.queue_limit,
|
|
150
|
+
min_chat_messages: opts.min_chat_messages ?? this.sessionConfig.min_chat_messages,
|
|
137
151
|
tool_handler: taskOpt.tool_handler,
|
|
138
152
|
functions: taskOpt.functions,
|
|
139
153
|
sequential_mode: opts.sequential_mode,
|
|
@@ -153,28 +167,143 @@ class Saico {
|
|
|
153
167
|
}
|
|
154
168
|
|
|
155
169
|
/**
|
|
156
|
-
* Deactivate — close context, cancel task
|
|
170
|
+
* Deactivate — bubble cleaned messages to parent, close context, cancel task.
|
|
171
|
+
* Pushes cleaned messages (no tool calls, no BACKEND) into the parent's Q,
|
|
172
|
+
* then closes the context without the default summary bubbling.
|
|
157
173
|
*/
|
|
158
174
|
async deactivate() {
|
|
159
175
|
if (!this._task) return;
|
|
160
|
-
if (this._task.context)
|
|
161
|
-
|
|
176
|
+
if (this._task.context) {
|
|
177
|
+
// Find parent context to bubble cleaned messages
|
|
178
|
+
let parentTask = this._task.parent;
|
|
179
|
+
let parentCtx = null;
|
|
180
|
+
while (parentTask) {
|
|
181
|
+
if (parentTask.context) { parentCtx = parentTask.context; break; }
|
|
182
|
+
parentTask = parentTask.parent;
|
|
183
|
+
}
|
|
184
|
+
if (parentCtx) {
|
|
185
|
+
const cleaned = this.getRecentMessages(Infinity);
|
|
186
|
+
for (const msg of cleaned)
|
|
187
|
+
parentCtx.push(msg);
|
|
188
|
+
}
|
|
189
|
+
// Clean tool calls and close context without additional summary bubbling.
|
|
190
|
+
// We already pushed cleaned messages above — closeContext's own
|
|
191
|
+
// summarization would double-bubble.
|
|
192
|
+
if (this._task.context_id && typeof this._task.context.cleanToolCallsByTag === 'function')
|
|
193
|
+
this._task.context.cleanToolCallsByTag(this._task.context_id);
|
|
194
|
+
this._task.context = null;
|
|
195
|
+
}
|
|
162
196
|
this._task._ecancel();
|
|
163
197
|
this._task = null;
|
|
164
198
|
}
|
|
165
199
|
|
|
166
|
-
// ----
|
|
200
|
+
// ---- Saico parent chain traversal ----
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Walk up the Saico parent chain (stop at isolate boundary or root).
|
|
204
|
+
* Returns array ordered root -> ... -> this.
|
|
205
|
+
*/
|
|
206
|
+
_getSaicoAncestors() {
|
|
207
|
+
const chain = [this];
|
|
208
|
+
if (this._isolate) return chain;
|
|
209
|
+
let task = this._task?.parent;
|
|
210
|
+
while (task) {
|
|
211
|
+
if (task._saico) {
|
|
212
|
+
chain.unshift(task._saico);
|
|
213
|
+
if (task._saico._isolate) break;
|
|
214
|
+
}
|
|
215
|
+
task = task.parent;
|
|
216
|
+
}
|
|
217
|
+
return chain; // root -> ... -> this
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Build preamble and aggregated functions by walking the Saico chain.
|
|
222
|
+
* @param {Context} activeCtx - The deepest active context (for state summary logic)
|
|
223
|
+
* @returns {{ preamble: Array, allFunctions: Array }}
|
|
224
|
+
*/
|
|
225
|
+
_buildPreamble(activeCtx) {
|
|
226
|
+
const chain = this._getSaicoAncestors();
|
|
227
|
+
const preamble = [];
|
|
228
|
+
const allFunctions = [];
|
|
229
|
+
|
|
230
|
+
for (const saico of chain) {
|
|
231
|
+
// Prompt
|
|
232
|
+
if (saico.prompt)
|
|
233
|
+
preamble.push({ role: 'system', content: saico.prompt });
|
|
234
|
+
|
|
235
|
+
// State summary (can return array)
|
|
236
|
+
const summary = saico._getStateSummary(activeCtx);
|
|
237
|
+
if (Array.isArray(summary)) {
|
|
238
|
+
for (const item of summary) {
|
|
239
|
+
if (typeof item === 'string')
|
|
240
|
+
preamble.push({ role: 'system', content: '[State Summary]\n' + item });
|
|
241
|
+
else
|
|
242
|
+
preamble.push(item); // {role, content} message object
|
|
243
|
+
}
|
|
244
|
+
} else if (summary) {
|
|
245
|
+
preamble.push({ role: 'system', content: '[State Summary]\n' + summary });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Tools digest
|
|
249
|
+
if (saico.context?.tool_digest?.length > 0) {
|
|
250
|
+
const digestText = saico.context.tool_digest.map(entry =>
|
|
251
|
+
`[${new Date(entry.tm).toISOString()}] ${entry.tool}: ${entry.result}`
|
|
252
|
+
).join('\n');
|
|
253
|
+
preamble.push({ role: 'system', content: '[Tool Activity Log]\n' + digestText });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Collect functions
|
|
257
|
+
if (saico.functions) allFunctions.push(...saico.functions);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return { preamble, allFunctions };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ---- Message orchestration ----
|
|
167
264
|
|
|
168
265
|
async sendMessage(content, functions, opts) {
|
|
169
266
|
if (!this._task)
|
|
170
267
|
throw new Error('Not activated. Call activate() first.');
|
|
171
|
-
|
|
268
|
+
|
|
269
|
+
// Find the active context (own or walk up)
|
|
270
|
+
let ctx = this._task.getContext() || this._task.findContext();
|
|
271
|
+
if (!ctx)
|
|
272
|
+
throw new Error('No context available');
|
|
273
|
+
|
|
274
|
+
// Build preamble by walking Saico chain
|
|
275
|
+
const activeCtx = this._task.findDeepestContext() || ctx;
|
|
276
|
+
const { preamble, allFunctions } = this._buildPreamble(activeCtx);
|
|
277
|
+
|
|
278
|
+
// Merge with call-specific functions
|
|
279
|
+
if (functions) allFunctions.push(...(Array.isArray(functions) ? functions : [functions]));
|
|
280
|
+
|
|
281
|
+
opts = Object.assign({}, opts, {
|
|
282
|
+
tag: this._task.context_id,
|
|
283
|
+
_preamble: preamble,
|
|
284
|
+
_aggregatedFunctions: allFunctions.length > 0 ? allFunctions : null,
|
|
285
|
+
});
|
|
286
|
+
return ctx.sendMessage('user', '[BACKEND] ' + content, null, opts);
|
|
172
287
|
}
|
|
173
288
|
|
|
174
289
|
async recvChatMessage(content, opts) {
|
|
175
290
|
if (!this._task)
|
|
176
291
|
throw new Error('Not activated. Call activate() first.');
|
|
177
|
-
|
|
292
|
+
|
|
293
|
+
// Route DOWN to deepest descendant with a msg Q
|
|
294
|
+
const ctx = this._task.findDeepestContext();
|
|
295
|
+
if (!ctx)
|
|
296
|
+
throw new Error('No context available');
|
|
297
|
+
|
|
298
|
+
// Build preamble by walking Saico chain
|
|
299
|
+
const { preamble, allFunctions } = this._buildPreamble(ctx);
|
|
300
|
+
|
|
301
|
+
opts = Object.assign({}, opts, {
|
|
302
|
+
tag: ctx.tag,
|
|
303
|
+
_preamble: preamble,
|
|
304
|
+
_aggregatedFunctions: allFunctions.length > 0 ? allFunctions : null,
|
|
305
|
+
});
|
|
306
|
+
return ctx.sendMessage('user', content, null, opts);
|
|
178
307
|
}
|
|
179
308
|
|
|
180
309
|
// ---- Task delegation ----
|
|
@@ -200,11 +329,11 @@ class Saico {
|
|
|
200
329
|
if (opt.prompt) {
|
|
201
330
|
const childContext = new Context(opt.prompt, childTask, {
|
|
202
331
|
tag: opt.tag || childTask.id,
|
|
203
|
-
token_limit: opt.token_limit,
|
|
204
|
-
max_depth: opt.max_depth,
|
|
205
|
-
max_tool_repetition: opt.max_tool_repetition,
|
|
206
|
-
queue_limit: opt.queue_limit,
|
|
207
|
-
min_chat_messages: opt.min_chat_messages,
|
|
332
|
+
token_limit: opt.token_limit ?? this.sessionConfig.token_limit,
|
|
333
|
+
max_depth: opt.max_depth ?? this.sessionConfig.max_depth,
|
|
334
|
+
max_tool_repetition: opt.max_tool_repetition ?? this.sessionConfig.max_tool_repetition,
|
|
335
|
+
queue_limit: opt.queue_limit ?? this.sessionConfig.queue_limit,
|
|
336
|
+
min_chat_messages: opt.min_chat_messages ?? this.sessionConfig.min_chat_messages,
|
|
208
337
|
tool_handler: opt.tool_handler || this.tool_handler,
|
|
209
338
|
functions: opt.functions || this.functions,
|
|
210
339
|
});
|
|
@@ -238,10 +367,90 @@ class Saico {
|
|
|
238
367
|
return childTask;
|
|
239
368
|
}
|
|
240
369
|
|
|
370
|
+
// ---- State Summary ----
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Override in subclasses to provide a state summary.
|
|
374
|
+
* @returns {string}
|
|
375
|
+
*/
|
|
376
|
+
getStateSummary() { return ''; }
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Get recent user/assistant messages (filtering out tool calls and BACKEND msgs).
|
|
380
|
+
* @param {number} n - Max number of messages to return
|
|
381
|
+
* @returns {Array<{role: string, content: string}>}
|
|
382
|
+
*/
|
|
383
|
+
getRecentMessages(n = 5) {
|
|
384
|
+
if (!this.context) return [];
|
|
385
|
+
return this.context._msgs
|
|
386
|
+
.filter(m => {
|
|
387
|
+
if (m.msg.role === 'tool' || m.msg.tool_calls) return false;
|
|
388
|
+
if (typeof m.msg.content === 'string' && m.msg.content.startsWith('[BACKEND]')) return false;
|
|
389
|
+
return m.msg.role === 'user' || m.msg.role === 'assistant';
|
|
390
|
+
})
|
|
391
|
+
.slice(-n)
|
|
392
|
+
.map(m => ({ role: m.msg.role, content: m.msg.content }));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Internal state summary builder. Includes own getStateSummary() and,
|
|
397
|
+
* if this context is NOT the active (deepest) Q, includes recent messages.
|
|
398
|
+
* @param {Context} activeCtx - The deepest active context
|
|
399
|
+
* @returns {Array|string|null}
|
|
400
|
+
*/
|
|
401
|
+
_getStateSummary(activeCtx) {
|
|
402
|
+
const parts = [];
|
|
403
|
+
const own = this.getStateSummary();
|
|
404
|
+
if (own) parts.push(own);
|
|
405
|
+
|
|
406
|
+
// If this context is NOT the active (deepest) Q, include recent messages
|
|
407
|
+
if (this.context && activeCtx && this.context !== activeCtx) {
|
|
408
|
+
const recent = this.getRecentMessages(5);
|
|
409
|
+
if (recent.length > 0) parts.push(...recent);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return parts.length > 0 ? parts : null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// ---- User Data (absorbed from Sid) ----
|
|
416
|
+
|
|
417
|
+
setUserData(key, value) {
|
|
418
|
+
this.userData[key] = value;
|
|
419
|
+
return this;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
getUserData(key) {
|
|
423
|
+
return key ? this.userData[key] : this.userData;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
clearUserData() {
|
|
427
|
+
this.userData = {};
|
|
428
|
+
return this;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ---- Session Info ----
|
|
432
|
+
|
|
433
|
+
getSessionInfo() {
|
|
434
|
+
return {
|
|
435
|
+
id: this._id,
|
|
436
|
+
name: this.name,
|
|
437
|
+
running: this._task?.running || false,
|
|
438
|
+
completed: this._task?._completed || false,
|
|
439
|
+
messageCount: this.context?.length || 0,
|
|
440
|
+
childCount: this._task?.child?.size || 0,
|
|
441
|
+
userData: this.userData,
|
|
442
|
+
uptime: Date.now() - this.tm_create,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
async closeSession() {
|
|
447
|
+
if (!this._task) return;
|
|
448
|
+
if (this._task.context)
|
|
449
|
+
await this._task.context.close();
|
|
450
|
+
this._task._ecancel();
|
|
451
|
+
}
|
|
452
|
+
|
|
241
453
|
// ---- Generic DB access ----
|
|
242
|
-
// These delegate to whatever _db backend was configured (DynamoDB, MongoDB,
|
|
243
|
-
// etc). Upper layers call these and don't care about the storage impl.
|
|
244
|
-
// All are no-ops (return undefined) when no backend is configured.
|
|
245
454
|
|
|
246
455
|
async dbPutItem(item, table) {
|
|
247
456
|
if (!this._db) return;
|
|
@@ -250,7 +459,8 @@ class Saico {
|
|
|
250
459
|
|
|
251
460
|
async dbGetItem(key, value, table) {
|
|
252
461
|
if (!this._db) return;
|
|
253
|
-
|
|
462
|
+
const result = await this._db.get(key, value, table);
|
|
463
|
+
return result ? this._deserializeRecord(result) : result;
|
|
254
464
|
}
|
|
255
465
|
|
|
256
466
|
async dbDeleteItem(key, value, table) {
|
|
@@ -260,12 +470,18 @@ class Saico {
|
|
|
260
470
|
|
|
261
471
|
async dbQuery(index, key, value, table) {
|
|
262
472
|
if (!this._db) return;
|
|
263
|
-
|
|
473
|
+
const results = await this._db.query(index, key, value, table);
|
|
474
|
+
return Array.isArray(results)
|
|
475
|
+
? results.map(r => this._deserializeRecord(r))
|
|
476
|
+
: results;
|
|
264
477
|
}
|
|
265
478
|
|
|
266
479
|
async dbGetAll(table) {
|
|
267
480
|
if (!this._db) return;
|
|
268
|
-
|
|
481
|
+
const results = await this._db.getAll(table);
|
|
482
|
+
return Array.isArray(results)
|
|
483
|
+
? results.map(r => this._deserializeRecord(r))
|
|
484
|
+
: results;
|
|
269
485
|
}
|
|
270
486
|
|
|
271
487
|
async dbUpdate(key, keyValue, setKey, item, table) {
|
|
@@ -308,14 +524,15 @@ class Saico {
|
|
|
308
524
|
return this._db.countItems(table);
|
|
309
525
|
}
|
|
310
526
|
|
|
311
|
-
// ----
|
|
527
|
+
// ---- DB deserialization hook ----
|
|
312
528
|
|
|
313
529
|
/**
|
|
314
|
-
* Override in subclasses to
|
|
315
|
-
*
|
|
316
|
-
* @
|
|
530
|
+
* Override in subclasses to transform raw DB records (e.g. restore class instances).
|
|
531
|
+
* Called by dbGetItem, dbQuery, dbGetAll.
|
|
532
|
+
* @param {Object} raw - Raw record from DB
|
|
533
|
+
* @returns {Object} Transformed record
|
|
317
534
|
*/
|
|
318
|
-
|
|
535
|
+
_deserializeRecord(raw) { return raw; }
|
|
319
536
|
|
|
320
537
|
// ---- Serialization ----
|
|
321
538
|
|
|
@@ -324,6 +541,10 @@ class Saico {
|
|
|
324
541
|
id: this._id,
|
|
325
542
|
name: this.name,
|
|
326
543
|
prompt: this.prompt,
|
|
544
|
+
userData: this.userData,
|
|
545
|
+
sessionConfig: this.sessionConfig,
|
|
546
|
+
tm_create: this.tm_create,
|
|
547
|
+
isolate: this._isolate,
|
|
327
548
|
};
|
|
328
549
|
if (this._task) {
|
|
329
550
|
data.task = {
|
|
@@ -340,6 +561,57 @@ class Saico {
|
|
|
340
561
|
}
|
|
341
562
|
return JSON.stringify(data);
|
|
342
563
|
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Restore a Saico instance from serialized data.
|
|
567
|
+
* @param {string|Object} data - Serialized data (JSON string or object)
|
|
568
|
+
* @param {Object} opt - Options (tool_handler, functions, store, states, etc.)
|
|
569
|
+
* @returns {Saico}
|
|
570
|
+
*/
|
|
571
|
+
static deserialize(data, opt = {}) {
|
|
572
|
+
const parsed = typeof data === 'string' ? JSON.parse(data) : data;
|
|
573
|
+
|
|
574
|
+
const instance = new Saico({
|
|
575
|
+
id: parsed.id,
|
|
576
|
+
name: parsed.name,
|
|
577
|
+
prompt: parsed.prompt,
|
|
578
|
+
userData: parsed.userData,
|
|
579
|
+
sessionConfig: parsed.sessionConfig,
|
|
580
|
+
isolate: parsed.isolate,
|
|
581
|
+
tool_handler: opt.tool_handler,
|
|
582
|
+
functions: opt.functions || parsed.task?.context?.functions,
|
|
583
|
+
store: opt.store,
|
|
584
|
+
redis: false, // No Redis proxy during deserialization
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
instance.tm_create = parsed.tm_create || instance.tm_create;
|
|
588
|
+
|
|
589
|
+
// Activate with restored context if task data exists
|
|
590
|
+
if (parsed.task) {
|
|
591
|
+
instance.activate({
|
|
592
|
+
createQ: !!parsed.task.context,
|
|
593
|
+
taskId: parsed.task.id,
|
|
594
|
+
tag: parsed.task.context?.tag,
|
|
595
|
+
chat_history: parsed.task.context?.chat_history,
|
|
596
|
+
tool_handler: opt.tool_handler,
|
|
597
|
+
functions: opt.functions || parsed.task.context?.functions,
|
|
598
|
+
states: opt.states || [],
|
|
599
|
+
...opt,
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
// Restore messages to context
|
|
603
|
+
if (parsed.task.context?.msgs && instance._task.context) {
|
|
604
|
+
instance._task.context._msgs = parsed.task.context.msgs;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Restore tool_digest
|
|
608
|
+
if (Array.isArray(parsed.task.context?.tool_digest) && instance._task.context) {
|
|
609
|
+
instance._task.context.tool_digest = parsed.task.context.tool_digest;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return instance;
|
|
614
|
+
}
|
|
343
615
|
}
|
|
344
616
|
|
|
345
617
|
module.exports = { Saico };
|