@ekairos/thread 1.22.16-beta.feature-core-thread-registry-sync.0 → 1.22.18-beta.feature-core-thread-registry-sync.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 +7 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/react.d.ts +2 -18
- package/dist/react.js +2 -15
- package/dist/reactors/ai-sdk.chunk-map.d.ts +12 -0
- package/dist/reactors/ai-sdk.chunk-map.js +143 -0
- package/dist/reactors/ai-sdk.reactor.js +5 -1
- package/dist/reactors/scripted.reactor.d.ts +2 -2
- package/dist/reactors/scripted.reactor.js +3 -2
- package/dist/reactors/types.d.ts +5 -5
- package/dist/schema.js +10 -9
- package/dist/steps/do-thread-stream-step.d.ts +6 -2
- package/dist/steps/do-thread-stream-step.js +9 -5
- package/dist/steps/reaction.steps.js +32 -13
- package/dist/steps/store.steps.d.ts +10 -11
- package/dist/steps/store.steps.js +34 -77
- package/dist/steps/stream.steps.d.ts +3 -33
- package/dist/steps/stream.steps.js +7 -68
- package/dist/steps/trace.steps.js +1 -1
- package/dist/stores/instant.documents.d.ts +1 -1
- package/dist/stores/instant.documents.js +1 -1
- package/dist/stores/instant.store.d.ts +7 -3
- package/dist/stores/instant.store.js +32 -86
- package/dist/thread.contract.d.ts +16 -8
- package/dist/thread.contract.js +61 -19
- package/dist/thread.d.ts +1 -1
- package/dist/thread.engine.d.ts +13 -9
- package/dist/thread.engine.js +463 -75
- package/dist/thread.events.d.ts +3 -3
- package/dist/thread.events.js +11 -34
- package/dist/thread.reactor.d.ts +1 -1
- package/dist/thread.store.d.ts +9 -9
- package/dist/thread.stream.d.ts +100 -33
- package/dist/thread.stream.js +72 -63
- package/package.json +3 -2
package/dist/thread.engine.js
CHANGED
|
@@ -3,10 +3,65 @@ import { registerThreadEnv } from "./env.js";
|
|
|
3
3
|
import { applyToolExecutionResultToParts } from "./thread.toolcalls.js";
|
|
4
4
|
import { toolsToModelTools } from "./tools-to-model-tools.js";
|
|
5
5
|
import { createAiSdkReactor, } from "./thread.reactor.js";
|
|
6
|
-
import { closeThreadStream,
|
|
7
|
-
import { completeExecution, createThreadStep,
|
|
6
|
+
import { closeThreadStream, writeThreadEvents, } from "./steps/stream.steps.js";
|
|
7
|
+
import { completeExecution, createThreadStep, initializeContext, saveReactionItem, saveTriggerAndCreateExecution, saveThreadPartsStep, updateThreadStep, updateContextContent, updateItem, } from "./steps/store.steps.js";
|
|
8
8
|
import { getClientResumeHookUrl, toolApprovalHookToken, toolApprovalWebhookToken, } from "./thread.hooks.js";
|
|
9
9
|
export { toolApprovalHookToken, toolApprovalWebhookToken, getClientResumeHookUrl };
|
|
10
|
+
function nowIso() {
|
|
11
|
+
return new Date().toISOString();
|
|
12
|
+
}
|
|
13
|
+
function clipPreview(value, max = 240) {
|
|
14
|
+
if (value.length <= max)
|
|
15
|
+
return value;
|
|
16
|
+
return `${value.slice(0, max)}...`;
|
|
17
|
+
}
|
|
18
|
+
function summarizePartPreview(part) {
|
|
19
|
+
if (!part || typeof part !== "object")
|
|
20
|
+
return {};
|
|
21
|
+
const row = part;
|
|
22
|
+
const partType = typeof row.type === "string" ? row.type : "";
|
|
23
|
+
const partState = typeof row.state === "string" ? row.state : undefined;
|
|
24
|
+
const partToolCallId = typeof row.toolCallId === "string"
|
|
25
|
+
? row.toolCallId
|
|
26
|
+
: typeof row.id === "string"
|
|
27
|
+
? row.id
|
|
28
|
+
: undefined;
|
|
29
|
+
if (typeof row.text === "string" && row.text.trim().length > 0) {
|
|
30
|
+
return {
|
|
31
|
+
partPreview: clipPreview(row.text),
|
|
32
|
+
partState,
|
|
33
|
+
partToolCallId,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (partType.startsWith("tool-")) {
|
|
37
|
+
const payload = {
|
|
38
|
+
tool: partType,
|
|
39
|
+
state: partState,
|
|
40
|
+
input: row.input,
|
|
41
|
+
output: row.output,
|
|
42
|
+
errorText: row.errorText,
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
partPreview: clipPreview(JSON.stringify(payload)),
|
|
46
|
+
partState,
|
|
47
|
+
partToolCallId,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
partState,
|
|
52
|
+
partToolCallId,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function contextThreadIdOrNull(context) {
|
|
56
|
+
return typeof context.threadId === "string" && context.threadId.length > 0
|
|
57
|
+
? context.threadId
|
|
58
|
+
: null;
|
|
59
|
+
}
|
|
60
|
+
async function emitThreadEvents(params) {
|
|
61
|
+
if (params.silent || params.events.length === 0)
|
|
62
|
+
return;
|
|
63
|
+
await writeThreadEvents({ events: params.events, writable: params.writable });
|
|
64
|
+
}
|
|
10
65
|
export class Thread {
|
|
11
66
|
constructor(opts = {}, reactor) {
|
|
12
67
|
this.opts = opts;
|
|
@@ -90,15 +145,6 @@ export class Thread {
|
|
|
90
145
|
writable = getWritable({
|
|
91
146
|
namespace: `context:${String(currentContext.id)}`,
|
|
92
147
|
});
|
|
93
|
-
// If the context was created in `initializeContext` (which didn't have a writable yet),
|
|
94
|
-
// re-emit the context id chunk now so clients can subscribe to the right persisted thread.
|
|
95
|
-
if (ctxResult.isNew) {
|
|
96
|
-
await emitContextIdChunk({
|
|
97
|
-
env: params.env,
|
|
98
|
-
contextId: String(currentContext.id),
|
|
99
|
-
writable,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
148
|
}
|
|
103
149
|
// Reactor/steps always receive a stream argument.
|
|
104
150
|
// In silent mode (or when no workflow writable is available), we use an in-memory sink.
|
|
@@ -114,6 +160,27 @@ export class Thread {
|
|
|
114
160
|
: params.contextIdentifier?.key
|
|
115
161
|
? { key: params.contextIdentifier.key }
|
|
116
162
|
: { id: String(currentContext.id) };
|
|
163
|
+
const threadId = contextThreadIdOrNull(currentContext);
|
|
164
|
+
const resolvedThreadId = threadId ?? String(currentContext.id);
|
|
165
|
+
await emitThreadEvents({
|
|
166
|
+
silent,
|
|
167
|
+
writable,
|
|
168
|
+
events: [
|
|
169
|
+
{
|
|
170
|
+
type: ctxResult.isNew ? "context.created" : "context.resolved",
|
|
171
|
+
at: nowIso(),
|
|
172
|
+
contextId: String(currentContext.id),
|
|
173
|
+
threadId: resolvedThreadId,
|
|
174
|
+
status: String(currentContext.status ?? "open"),
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
type: ctxResult.isNew ? "thread.created" : "thread.resolved",
|
|
178
|
+
at: nowIso(),
|
|
179
|
+
threadId: resolvedThreadId,
|
|
180
|
+
status: "idle",
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
});
|
|
117
184
|
if (ctxResult.isNew) {
|
|
118
185
|
await story.opts.onContextCreated?.({ env: params.env, context: currentContext });
|
|
119
186
|
}
|
|
@@ -123,11 +190,36 @@ export class Thread {
|
|
|
123
190
|
contextIdentifier: contextSelector,
|
|
124
191
|
triggerEvent,
|
|
125
192
|
});
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
193
|
+
await emitThreadEvents({
|
|
194
|
+
silent,
|
|
195
|
+
writable,
|
|
196
|
+
events: [
|
|
197
|
+
{
|
|
198
|
+
type: "item.created",
|
|
199
|
+
at: nowIso(),
|
|
200
|
+
itemId: triggerEventId,
|
|
201
|
+
contextId: String(currentContext.id),
|
|
202
|
+
threadId: resolvedThreadId,
|
|
203
|
+
status: "stored",
|
|
204
|
+
itemType: "input",
|
|
205
|
+
executionId,
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
type: "thread.streaming_started",
|
|
209
|
+
at: nowIso(),
|
|
210
|
+
threadId: resolvedThreadId,
|
|
211
|
+
status: "streaming",
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
type: "execution.created",
|
|
215
|
+
at: nowIso(),
|
|
216
|
+
executionId,
|
|
217
|
+
contextId: String(currentContext.id),
|
|
218
|
+
threadId: resolvedThreadId,
|
|
219
|
+
status: "executing",
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
});
|
|
131
223
|
let reactionEvent = null;
|
|
132
224
|
// Latest persisted context state for this run (we keep it in memory; store is updated via steps).
|
|
133
225
|
let updatedContext = currentContext;
|
|
@@ -135,6 +227,33 @@ export class Thread {
|
|
|
135
227
|
const failExecution = async () => {
|
|
136
228
|
try {
|
|
137
229
|
await completeExecution(params.env, contextSelector, executionId, "failed");
|
|
230
|
+
await emitThreadEvents({
|
|
231
|
+
silent,
|
|
232
|
+
writable,
|
|
233
|
+
events: [
|
|
234
|
+
{
|
|
235
|
+
type: "execution.failed",
|
|
236
|
+
at: nowIso(),
|
|
237
|
+
executionId,
|
|
238
|
+
contextId: String(currentContext.id),
|
|
239
|
+
threadId: resolvedThreadId,
|
|
240
|
+
status: "failed",
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
type: "context.closed",
|
|
244
|
+
at: nowIso(),
|
|
245
|
+
contextId: String(currentContext.id),
|
|
246
|
+
threadId: resolvedThreadId,
|
|
247
|
+
status: "closed",
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
type: "thread.idle",
|
|
251
|
+
at: nowIso(),
|
|
252
|
+
threadId: resolvedThreadId,
|
|
253
|
+
status: "idle",
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
});
|
|
138
257
|
}
|
|
139
258
|
catch {
|
|
140
259
|
// noop
|
|
@@ -157,9 +276,35 @@ export class Thread {
|
|
|
157
276
|
iteration: iter,
|
|
158
277
|
});
|
|
159
278
|
currentStepId = stepCreate.stepId;
|
|
279
|
+
await emitThreadEvents({
|
|
280
|
+
silent,
|
|
281
|
+
writable,
|
|
282
|
+
events: [
|
|
283
|
+
{
|
|
284
|
+
type: "step.created",
|
|
285
|
+
at: nowIso(),
|
|
286
|
+
stepId: String(stepCreate.stepId),
|
|
287
|
+
executionId,
|
|
288
|
+
iteration: iter,
|
|
289
|
+
status: "running",
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
});
|
|
160
293
|
// Hook: Thread DSL `context()` (implemented by subclasses via `initialize()`)
|
|
161
294
|
const nextContent = await story.initialize(updatedContext, params.env);
|
|
162
295
|
updatedContext = await updateContextContent(params.env, contextSelector, nextContent);
|
|
296
|
+
await emitThreadEvents({
|
|
297
|
+
silent,
|
|
298
|
+
writable,
|
|
299
|
+
events: [
|
|
300
|
+
{
|
|
301
|
+
type: "context.content_updated",
|
|
302
|
+
at: nowIso(),
|
|
303
|
+
contextId: String(updatedContext.id),
|
|
304
|
+
threadId: String(contextThreadIdOrNull(updatedContext) ?? ""),
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
});
|
|
163
308
|
await story.opts.onContextUpdated?.({ env: params.env, context: updatedContext });
|
|
164
309
|
// Hook: Thread DSL `narrative()` (implemented by subclasses via `buildSystemPrompt()`)
|
|
165
310
|
const systemPrompt = await story.buildSystemPrompt(updatedContext, params.env);
|
|
@@ -176,7 +321,7 @@ export class Thread {
|
|
|
176
321
|
// (step id) and then a second persisted assistant message (reaction id) with the same
|
|
177
322
|
// content once InstantDB updates.
|
|
178
323
|
const reactor = story.getReactor(updatedContext, params.env);
|
|
179
|
-
const { assistantEvent,
|
|
324
|
+
const { assistantEvent, actionRequests, messagesForModel } = await reactor({
|
|
180
325
|
env: params.env,
|
|
181
326
|
context: updatedContext,
|
|
182
327
|
contextIdentifier: contextSelector,
|
|
@@ -196,17 +341,17 @@ export class Thread {
|
|
|
196
341
|
silent,
|
|
197
342
|
writable,
|
|
198
343
|
});
|
|
199
|
-
const reviewRequests =
|
|
200
|
-
?
|
|
201
|
-
const toolDef = toolsAll[
|
|
344
|
+
const reviewRequests = actionRequests.length > 0
|
|
345
|
+
? actionRequests.flatMap((actionRequest) => {
|
|
346
|
+
const toolDef = toolsAll[actionRequest.actionName];
|
|
202
347
|
const auto = toolDef?.auto !== false;
|
|
203
|
-
|
|
348
|
+
actionRequest.auto = auto;
|
|
204
349
|
if (auto)
|
|
205
350
|
return [];
|
|
206
351
|
return [
|
|
207
352
|
{
|
|
208
|
-
toolCallId: String(
|
|
209
|
-
toolName: String(
|
|
353
|
+
toolCallId: String(actionRequest.actionRef),
|
|
354
|
+
toolName: String(actionRequest.actionName ?? ""),
|
|
210
355
|
},
|
|
211
356
|
];
|
|
212
357
|
})
|
|
@@ -232,6 +377,21 @@ export class Thread {
|
|
|
232
377
|
contextId: String(currentContext.id),
|
|
233
378
|
iteration: iter,
|
|
234
379
|
});
|
|
380
|
+
await emitThreadEvents({
|
|
381
|
+
silent,
|
|
382
|
+
writable,
|
|
383
|
+
events: stepParts.map((part, idx) => ({
|
|
384
|
+
type: "part.created",
|
|
385
|
+
at: nowIso(),
|
|
386
|
+
partKey: `${String(stepCreate.stepId)}:${idx}`,
|
|
387
|
+
stepId: String(stepCreate.stepId),
|
|
388
|
+
idx,
|
|
389
|
+
partType: part && typeof part.type === "string"
|
|
390
|
+
? String(part.type)
|
|
391
|
+
: undefined,
|
|
392
|
+
...summarizePartPreview(part),
|
|
393
|
+
})),
|
|
394
|
+
});
|
|
235
395
|
// Persist/append the aggregated reaction event (stable `reactionEventId` for the execution).
|
|
236
396
|
if (!reactionEvent) {
|
|
237
397
|
const reactionPayload = {
|
|
@@ -243,6 +403,22 @@ export class Thread {
|
|
|
243
403
|
contextId: String(currentContext.id),
|
|
244
404
|
reviewRequests,
|
|
245
405
|
});
|
|
406
|
+
await emitThreadEvents({
|
|
407
|
+
silent,
|
|
408
|
+
writable,
|
|
409
|
+
events: [
|
|
410
|
+
{
|
|
411
|
+
type: "item.created",
|
|
412
|
+
at: nowIso(),
|
|
413
|
+
itemId: String(reactionEvent.id),
|
|
414
|
+
contextId: String(currentContext.id),
|
|
415
|
+
threadId: resolvedThreadId,
|
|
416
|
+
executionId,
|
|
417
|
+
status: "pending",
|
|
418
|
+
itemType: "output",
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
});
|
|
246
422
|
}
|
|
247
423
|
else {
|
|
248
424
|
const existingReactionParts = Array.isArray(reactionEvent.content?.parts)
|
|
@@ -260,10 +436,62 @@ export class Thread {
|
|
|
260
436
|
status: "pending",
|
|
261
437
|
};
|
|
262
438
|
reactionEvent = await updateItem(params.env, reactionEvent.id, nextReactionEvent, { executionId, contextId: String(currentContext.id) });
|
|
439
|
+
await emitThreadEvents({
|
|
440
|
+
silent,
|
|
441
|
+
writable,
|
|
442
|
+
events: [
|
|
443
|
+
{
|
|
444
|
+
type: "item.updated",
|
|
445
|
+
at: nowIso(),
|
|
446
|
+
itemId: String(reactionEvent.id),
|
|
447
|
+
contextId: String(currentContext.id),
|
|
448
|
+
threadId: resolvedThreadId,
|
|
449
|
+
executionId,
|
|
450
|
+
status: "pending",
|
|
451
|
+
},
|
|
452
|
+
],
|
|
453
|
+
});
|
|
263
454
|
}
|
|
264
455
|
story.opts.onEventCreated?.(assistantEventEffective);
|
|
456
|
+
const firstActionRequest = actionRequests?.[0];
|
|
457
|
+
await updateThreadStep({
|
|
458
|
+
env: params.env,
|
|
459
|
+
stepId: stepCreate.stepId,
|
|
460
|
+
patch: firstActionRequest
|
|
461
|
+
? {
|
|
462
|
+
kind: "action_execute",
|
|
463
|
+
actionName: typeof firstActionRequest.actionName === "string"
|
|
464
|
+
? firstActionRequest.actionName
|
|
465
|
+
: undefined,
|
|
466
|
+
actionInput: firstActionRequest.input,
|
|
467
|
+
}
|
|
468
|
+
: {
|
|
469
|
+
kind: "message",
|
|
470
|
+
},
|
|
471
|
+
executionId,
|
|
472
|
+
contextId: String(currentContext.id),
|
|
473
|
+
iteration: iter,
|
|
474
|
+
});
|
|
475
|
+
await emitThreadEvents({
|
|
476
|
+
silent,
|
|
477
|
+
writable,
|
|
478
|
+
events: [
|
|
479
|
+
{
|
|
480
|
+
type: "step.updated",
|
|
481
|
+
at: nowIso(),
|
|
482
|
+
stepId: String(stepCreate.stepId),
|
|
483
|
+
executionId,
|
|
484
|
+
iteration: iter,
|
|
485
|
+
status: "running",
|
|
486
|
+
kind: firstActionRequest ? "action_execute" : "message",
|
|
487
|
+
actionName: firstActionRequest && typeof firstActionRequest.actionName === "string"
|
|
488
|
+
? firstActionRequest.actionName
|
|
489
|
+
: undefined,
|
|
490
|
+
},
|
|
491
|
+
],
|
|
492
|
+
});
|
|
265
493
|
// Done: no tool calls requested by the model
|
|
266
|
-
if (!
|
|
494
|
+
if (!actionRequests.length) {
|
|
267
495
|
const endResult = await story.callOnEnd(assistantEventEffective);
|
|
268
496
|
if (endResult) {
|
|
269
497
|
// Mark iteration step completed (no tools)
|
|
@@ -272,20 +500,86 @@ export class Thread {
|
|
|
272
500
|
stepId: stepCreate.stepId,
|
|
273
501
|
patch: {
|
|
274
502
|
status: "completed",
|
|
275
|
-
|
|
276
|
-
|
|
503
|
+
kind: "message",
|
|
504
|
+
actionRequests: [],
|
|
505
|
+
actionResults: [],
|
|
277
506
|
continueLoop: false,
|
|
278
507
|
},
|
|
279
508
|
executionId,
|
|
280
509
|
contextId: String(currentContext.id),
|
|
281
510
|
iteration: iter,
|
|
282
511
|
});
|
|
512
|
+
await emitThreadEvents({
|
|
513
|
+
silent,
|
|
514
|
+
writable,
|
|
515
|
+
events: [
|
|
516
|
+
{
|
|
517
|
+
type: "step.updated",
|
|
518
|
+
at: nowIso(),
|
|
519
|
+
stepId: String(stepCreate.stepId),
|
|
520
|
+
executionId,
|
|
521
|
+
iteration: iter,
|
|
522
|
+
status: "completed",
|
|
523
|
+
kind: "message",
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
type: "step.completed",
|
|
527
|
+
at: nowIso(),
|
|
528
|
+
stepId: String(stepCreate.stepId),
|
|
529
|
+
executionId,
|
|
530
|
+
iteration: iter,
|
|
531
|
+
status: "completed",
|
|
532
|
+
},
|
|
533
|
+
],
|
|
534
|
+
});
|
|
283
535
|
// Mark reaction event completed
|
|
284
536
|
await updateItem(params.env, reactionEventId, {
|
|
285
537
|
...(reactionEvent ?? assistantEventEffective),
|
|
286
538
|
status: "completed",
|
|
287
539
|
}, { executionId, contextId: String(currentContext.id) });
|
|
540
|
+
await emitThreadEvents({
|
|
541
|
+
silent,
|
|
542
|
+
writable,
|
|
543
|
+
events: [
|
|
544
|
+
{
|
|
545
|
+
type: "item.completed",
|
|
546
|
+
at: nowIso(),
|
|
547
|
+
itemId: String(reactionEventId),
|
|
548
|
+
contextId: String(currentContext.id),
|
|
549
|
+
threadId: resolvedThreadId,
|
|
550
|
+
executionId,
|
|
551
|
+
status: "completed",
|
|
552
|
+
},
|
|
553
|
+
],
|
|
554
|
+
});
|
|
288
555
|
await completeExecution(params.env, contextSelector, executionId, "completed");
|
|
556
|
+
await emitThreadEvents({
|
|
557
|
+
silent,
|
|
558
|
+
writable,
|
|
559
|
+
events: [
|
|
560
|
+
{
|
|
561
|
+
type: "execution.completed",
|
|
562
|
+
at: nowIso(),
|
|
563
|
+
executionId,
|
|
564
|
+
contextId: String(currentContext.id),
|
|
565
|
+
threadId: resolvedThreadId,
|
|
566
|
+
status: "completed",
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
type: "context.closed",
|
|
570
|
+
at: nowIso(),
|
|
571
|
+
contextId: String(currentContext.id),
|
|
572
|
+
threadId: resolvedThreadId,
|
|
573
|
+
status: "closed",
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
type: "thread.idle",
|
|
577
|
+
at: nowIso(),
|
|
578
|
+
threadId: resolvedThreadId,
|
|
579
|
+
status: "idle",
|
|
580
|
+
},
|
|
581
|
+
],
|
|
582
|
+
});
|
|
289
583
|
if (!silent) {
|
|
290
584
|
await closeThreadStream({ preventClose, sendFinish, writable });
|
|
291
585
|
}
|
|
@@ -298,25 +592,22 @@ export class Thread {
|
|
|
298
592
|
};
|
|
299
593
|
}
|
|
300
594
|
}
|
|
301
|
-
// Execute
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
const executionResults = await Promise.all(toolCalls.map(async (tc) => {
|
|
306
|
-
const toolDef = toolsAll[tc.toolName];
|
|
595
|
+
// Execute actions (workflow context; action implementations decide step vs workflow)
|
|
596
|
+
const actionResults = await Promise.all(actionRequests.map(async (actionRequest) => {
|
|
597
|
+
const toolDef = toolsAll[actionRequest.actionName];
|
|
307
598
|
if (!toolDef || typeof toolDef.execute !== "function") {
|
|
308
599
|
return {
|
|
309
|
-
|
|
600
|
+
actionRequest,
|
|
310
601
|
success: false,
|
|
311
602
|
output: null,
|
|
312
|
-
errorText: `
|
|
603
|
+
errorText: `Action "${actionRequest.actionName}" not found or has no execute().`,
|
|
313
604
|
};
|
|
314
605
|
}
|
|
315
606
|
try {
|
|
316
|
-
let
|
|
607
|
+
let actionInput = actionRequest.input;
|
|
317
608
|
if (toolDef?.auto === false) {
|
|
318
609
|
const { createHook, createWebhook } = await import("workflow");
|
|
319
|
-
const toolCallId = String(
|
|
610
|
+
const toolCallId = String(actionRequest.actionRef);
|
|
320
611
|
const hookToken = toolApprovalHookToken({ executionId, toolCallId });
|
|
321
612
|
const webhookToken = toolApprovalWebhookToken({ executionId, toolCallId });
|
|
322
613
|
const hook = createHook({ token: hookToken });
|
|
@@ -330,67 +621,53 @@ export class Thread {
|
|
|
330
621
|
: await approvalOrRequest.request.json().catch(() => null);
|
|
331
622
|
if (!approval || approval.approved !== true) {
|
|
332
623
|
return {
|
|
333
|
-
|
|
624
|
+
actionRequest,
|
|
334
625
|
success: false,
|
|
335
626
|
output: null,
|
|
336
627
|
errorText: approval && "comment" in approval && approval.comment
|
|
337
|
-
? `
|
|
338
|
-
: "
|
|
628
|
+
? `Action execution not approved: ${approval.comment}`
|
|
629
|
+
: "Action execution not approved",
|
|
339
630
|
};
|
|
340
631
|
}
|
|
341
632
|
if ("args" in approval && approval.args !== undefined) {
|
|
342
|
-
|
|
633
|
+
actionInput = approval.args;
|
|
343
634
|
}
|
|
344
635
|
}
|
|
345
|
-
const output = await toolDef.execute(
|
|
346
|
-
toolCallId:
|
|
636
|
+
const output = await toolDef.execute(actionInput, {
|
|
637
|
+
toolCallId: actionRequest.actionRef,
|
|
347
638
|
messages: messagesForModel,
|
|
348
639
|
eventId: reactionEventId,
|
|
349
640
|
executionId,
|
|
350
641
|
triggerEventId,
|
|
351
642
|
contextId: currentContext.id,
|
|
352
643
|
});
|
|
353
|
-
return {
|
|
644
|
+
return { actionRequest, success: true, output };
|
|
354
645
|
}
|
|
355
646
|
catch (e) {
|
|
356
647
|
return {
|
|
357
|
-
|
|
648
|
+
actionRequest,
|
|
358
649
|
success: false,
|
|
359
650
|
output: null,
|
|
360
651
|
errorText: e instanceof Error ? e.message : String(e),
|
|
361
652
|
};
|
|
362
653
|
}
|
|
363
654
|
}));
|
|
364
|
-
//
|
|
365
|
-
if (!silent) {
|
|
366
|
-
await writeToolOutputs({
|
|
367
|
-
results: executionResults.map((r) => r.success
|
|
368
|
-
? { toolCallId: r.tc.toolCallId, success: true, output: r.output }
|
|
369
|
-
: {
|
|
370
|
-
toolCallId: r.tc.toolCallId,
|
|
371
|
-
success: false,
|
|
372
|
-
errorText: r.errorText,
|
|
373
|
-
}),
|
|
374
|
-
writable,
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
// Clear action status once tool execution results have been emitted.
|
|
378
|
-
if (!silent && toolCalls.length) {
|
|
379
|
-
await writeContextSubstate({ key: null, transient: true, writable });
|
|
380
|
-
}
|
|
381
|
-
// Merge tool results into persisted parts (so next LLM call can see them)
|
|
655
|
+
// Merge action results into persisted parts (so next LLM call can see them)
|
|
382
656
|
if (reactionEvent) {
|
|
383
657
|
let parts = Array.isArray(reactionEvent.content?.parts)
|
|
384
658
|
? [...reactionEvent.content.parts]
|
|
385
659
|
: [];
|
|
386
|
-
for (const r of
|
|
387
|
-
parts = applyToolExecutionResultToParts(parts,
|
|
660
|
+
for (const r of actionResults) {
|
|
661
|
+
parts = applyToolExecutionResultToParts(parts, {
|
|
662
|
+
toolCallId: r.actionRequest.actionRef,
|
|
663
|
+
toolName: r.actionRequest.actionName,
|
|
664
|
+
}, {
|
|
388
665
|
success: Boolean(r.success),
|
|
389
666
|
result: r.output,
|
|
390
667
|
message: r.errorText,
|
|
391
668
|
});
|
|
392
669
|
}
|
|
393
|
-
|
|
670
|
+
reactionEvent = {
|
|
394
671
|
...reactionEvent,
|
|
395
672
|
content: {
|
|
396
673
|
...reactionEvent.content,
|
|
@@ -398,12 +675,11 @@ export class Thread {
|
|
|
398
675
|
},
|
|
399
676
|
status: "pending",
|
|
400
677
|
};
|
|
401
|
-
reactionEvent = await updateItem(params.env, reactionEventId, nextReactionEvent, { executionId, contextId: String(currentContext.id) });
|
|
402
678
|
}
|
|
403
679
|
// Callback for observability/integration
|
|
404
|
-
for (const r of
|
|
405
|
-
await story.opts.
|
|
406
|
-
|
|
680
|
+
for (const r of actionResults) {
|
|
681
|
+
await story.opts.onActionExecuted?.({
|
|
682
|
+
actionRequest: r.actionRequest,
|
|
407
683
|
success: r.success,
|
|
408
684
|
output: r.output,
|
|
409
685
|
errorText: r.errorText,
|
|
@@ -419,8 +695,8 @@ export class Thread {
|
|
|
419
695
|
context: updatedContext,
|
|
420
696
|
reactionEvent: reactionEvent ?? assistantEventEffective,
|
|
421
697
|
assistantEvent: assistantEventEffective,
|
|
422
|
-
|
|
423
|
-
|
|
698
|
+
actionRequests,
|
|
699
|
+
actionResults: actionResults,
|
|
424
700
|
});
|
|
425
701
|
// Persist per-iteration step outcome (tools + continue signal)
|
|
426
702
|
await updateThreadStep({
|
|
@@ -428,20 +704,120 @@ export class Thread {
|
|
|
428
704
|
stepId: stepCreate.stepId,
|
|
429
705
|
patch: {
|
|
430
706
|
status: "completed",
|
|
431
|
-
|
|
432
|
-
|
|
707
|
+
kind: actionRequests?.length ? "action_result" : "message",
|
|
708
|
+
actionName: typeof actionResults?.[0]?.actionRequest?.actionName === "string"
|
|
709
|
+
? actionResults[0].actionRequest.actionName
|
|
710
|
+
: undefined,
|
|
711
|
+
actionInput: actionResults?.[0]?.actionRequest?.input,
|
|
712
|
+
actionOutput: actionResults?.[0]?.success === true
|
|
713
|
+
? actionResults[0]?.output
|
|
714
|
+
: undefined,
|
|
715
|
+
actionError: actionResults?.[0]?.success === false
|
|
716
|
+
? String(actionResults[0]?.errorText ?? "action_execution_failed")
|
|
717
|
+
: undefined,
|
|
718
|
+
actionRequests,
|
|
719
|
+
actionResults,
|
|
433
720
|
continueLoop: continueLoop !== false,
|
|
434
721
|
},
|
|
435
722
|
executionId,
|
|
436
723
|
contextId: String(currentContext.id),
|
|
437
724
|
iteration: iter,
|
|
438
725
|
});
|
|
726
|
+
await emitThreadEvents({
|
|
727
|
+
silent,
|
|
728
|
+
writable,
|
|
729
|
+
events: [
|
|
730
|
+
{
|
|
731
|
+
type: "step.updated",
|
|
732
|
+
at: nowIso(),
|
|
733
|
+
stepId: String(stepCreate.stepId),
|
|
734
|
+
executionId,
|
|
735
|
+
iteration: iter,
|
|
736
|
+
status: "completed",
|
|
737
|
+
kind: actionRequests?.length ? "action_result" : "message",
|
|
738
|
+
actionName: typeof actionResults?.[0]?.actionRequest?.actionName === "string"
|
|
739
|
+
? actionResults[0].actionRequest.actionName
|
|
740
|
+
: undefined,
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
type: "step.completed",
|
|
744
|
+
at: nowIso(),
|
|
745
|
+
stepId: String(stepCreate.stepId),
|
|
746
|
+
executionId,
|
|
747
|
+
iteration: iter,
|
|
748
|
+
status: "completed",
|
|
749
|
+
},
|
|
750
|
+
],
|
|
751
|
+
});
|
|
752
|
+
if (continueLoop !== false && reactionEvent) {
|
|
753
|
+
reactionEvent = await updateItem(params.env, reactionEventId, {
|
|
754
|
+
...reactionEvent,
|
|
755
|
+
status: "pending",
|
|
756
|
+
}, { executionId, contextId: String(currentContext.id) });
|
|
757
|
+
await emitThreadEvents({
|
|
758
|
+
silent,
|
|
759
|
+
writable,
|
|
760
|
+
events: [
|
|
761
|
+
{
|
|
762
|
+
type: "item.updated",
|
|
763
|
+
at: nowIso(),
|
|
764
|
+
itemId: String(reactionEventId),
|
|
765
|
+
contextId: String(currentContext.id),
|
|
766
|
+
threadId: resolvedThreadId,
|
|
767
|
+
executionId,
|
|
768
|
+
status: "pending",
|
|
769
|
+
},
|
|
770
|
+
],
|
|
771
|
+
});
|
|
772
|
+
}
|
|
439
773
|
if (continueLoop === false) {
|
|
440
774
|
await updateItem(params.env, reactionEventId, {
|
|
441
775
|
...(reactionEvent ?? assistantEventEffective),
|
|
442
776
|
status: "completed",
|
|
443
777
|
}, { executionId, contextId: String(currentContext.id) });
|
|
778
|
+
await emitThreadEvents({
|
|
779
|
+
silent,
|
|
780
|
+
writable,
|
|
781
|
+
events: [
|
|
782
|
+
{
|
|
783
|
+
type: "item.completed",
|
|
784
|
+
at: nowIso(),
|
|
785
|
+
itemId: String(reactionEventId),
|
|
786
|
+
contextId: String(currentContext.id),
|
|
787
|
+
threadId: resolvedThreadId,
|
|
788
|
+
executionId,
|
|
789
|
+
status: "completed",
|
|
790
|
+
},
|
|
791
|
+
],
|
|
792
|
+
});
|
|
444
793
|
await completeExecution(params.env, contextSelector, executionId, "completed");
|
|
794
|
+
await emitThreadEvents({
|
|
795
|
+
silent,
|
|
796
|
+
writable,
|
|
797
|
+
events: [
|
|
798
|
+
{
|
|
799
|
+
type: "execution.completed",
|
|
800
|
+
at: nowIso(),
|
|
801
|
+
executionId,
|
|
802
|
+
contextId: String(currentContext.id),
|
|
803
|
+
threadId: resolvedThreadId,
|
|
804
|
+
status: "completed",
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
type: "context.closed",
|
|
808
|
+
at: nowIso(),
|
|
809
|
+
contextId: String(currentContext.id),
|
|
810
|
+
threadId: resolvedThreadId,
|
|
811
|
+
status: "closed",
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
type: "thread.idle",
|
|
815
|
+
at: nowIso(),
|
|
816
|
+
threadId: resolvedThreadId,
|
|
817
|
+
status: "idle",
|
|
818
|
+
},
|
|
819
|
+
],
|
|
820
|
+
});
|
|
445
821
|
if (!silent) {
|
|
446
822
|
await closeThreadStream({ preventClose, sendFinish, writable });
|
|
447
823
|
}
|
|
@@ -470,6 +846,20 @@ export class Thread {
|
|
|
470
846
|
executionId,
|
|
471
847
|
contextId: String(currentContext.id),
|
|
472
848
|
});
|
|
849
|
+
await emitThreadEvents({
|
|
850
|
+
silent,
|
|
851
|
+
writable,
|
|
852
|
+
events: [
|
|
853
|
+
{
|
|
854
|
+
type: "step.failed",
|
|
855
|
+
at: nowIso(),
|
|
856
|
+
stepId: String(currentStepId),
|
|
857
|
+
executionId,
|
|
858
|
+
status: "failed",
|
|
859
|
+
errorText: error instanceof Error ? error.message : String(error),
|
|
860
|
+
},
|
|
861
|
+
],
|
|
862
|
+
});
|
|
473
863
|
}
|
|
474
864
|
catch {
|
|
475
865
|
// noop
|
|
@@ -491,8 +881,6 @@ export class Thread {
|
|
|
491
881
|
const result = await this.opts.onEnd(lastEvent);
|
|
492
882
|
if (typeof result === "boolean")
|
|
493
883
|
return result;
|
|
494
|
-
if (result && typeof result === "object" && "end" in result)
|
|
495
|
-
return Boolean(result.end);
|
|
496
884
|
return true;
|
|
497
885
|
}
|
|
498
886
|
}
|