@ekairos/events 1.22.4-beta.development.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.
Files changed (84) hide show
  1. package/README.md +115 -0
  2. package/dist/codex.d.ts +95 -0
  3. package/dist/codex.js +91 -0
  4. package/dist/context.builder.d.ts +62 -0
  5. package/dist/context.builder.js +143 -0
  6. package/dist/context.config.d.ts +9 -0
  7. package/dist/context.config.js +30 -0
  8. package/dist/context.contract.d.ts +47 -0
  9. package/dist/context.contract.js +132 -0
  10. package/dist/context.d.ts +4 -0
  11. package/dist/context.durable.d.ts +5 -0
  12. package/dist/context.durable.js +13 -0
  13. package/dist/context.engine.d.ts +216 -0
  14. package/dist/context.engine.js +1098 -0
  15. package/dist/context.events.d.ts +55 -0
  16. package/dist/context.events.js +431 -0
  17. package/dist/context.hooks.d.ts +21 -0
  18. package/dist/context.hooks.js +31 -0
  19. package/dist/context.js +3 -0
  20. package/dist/context.parts.d.ts +241 -0
  21. package/dist/context.parts.js +360 -0
  22. package/dist/context.reactor.d.ts +3 -0
  23. package/dist/context.reactor.js +2 -0
  24. package/dist/context.registry.d.ts +13 -0
  25. package/dist/context.registry.js +30 -0
  26. package/dist/context.skill.d.ts +9 -0
  27. package/dist/context.skill.js +1 -0
  28. package/dist/context.step-stream.d.ts +26 -0
  29. package/dist/context.step-stream.js +59 -0
  30. package/dist/context.store.d.ts +85 -0
  31. package/dist/context.store.js +1 -0
  32. package/dist/context.stream.d.ts +148 -0
  33. package/dist/context.stream.js +141 -0
  34. package/dist/context.toolcalls.d.ts +60 -0
  35. package/dist/context.toolcalls.js +117 -0
  36. package/dist/env.d.ts +3 -0
  37. package/dist/env.js +53 -0
  38. package/dist/index.d.ts +18 -0
  39. package/dist/index.js +11 -0
  40. package/dist/mcp.d.ts +1 -0
  41. package/dist/mcp.js +1 -0
  42. package/dist/mirror.d.ts +41 -0
  43. package/dist/mirror.js +1 -0
  44. package/dist/oidc.d.ts +7 -0
  45. package/dist/oidc.js +25 -0
  46. package/dist/polyfills/dom-events.d.ts +1 -0
  47. package/dist/polyfills/dom-events.js +89 -0
  48. package/dist/react.d.ts +42 -0
  49. package/dist/react.js +88 -0
  50. package/dist/reactors/ai-sdk.chunk-map.d.ts +12 -0
  51. package/dist/reactors/ai-sdk.chunk-map.js +143 -0
  52. package/dist/reactors/ai-sdk.reactor.d.ts +33 -0
  53. package/dist/reactors/ai-sdk.reactor.js +65 -0
  54. package/dist/reactors/ai-sdk.step.d.ts +48 -0
  55. package/dist/reactors/ai-sdk.step.js +343 -0
  56. package/dist/reactors/scripted.reactor.d.ts +17 -0
  57. package/dist/reactors/scripted.reactor.js +51 -0
  58. package/dist/reactors/types.d.ts +52 -0
  59. package/dist/reactors/types.js +1 -0
  60. package/dist/runtime.d.ts +19 -0
  61. package/dist/runtime.js +26 -0
  62. package/dist/runtime.step.d.ts +9 -0
  63. package/dist/runtime.step.js +7 -0
  64. package/dist/schema.d.ts +2 -0
  65. package/dist/schema.js +191 -0
  66. package/dist/steps/do-context-stream-step.d.ts +34 -0
  67. package/dist/steps/do-context-stream-step.js +96 -0
  68. package/dist/steps/mirror.steps.d.ts +6 -0
  69. package/dist/steps/mirror.steps.js +48 -0
  70. package/dist/steps/store.steps.d.ts +96 -0
  71. package/dist/steps/store.steps.js +595 -0
  72. package/dist/steps/stream.steps.d.ts +86 -0
  73. package/dist/steps/stream.steps.js +270 -0
  74. package/dist/steps/trace.steps.d.ts +38 -0
  75. package/dist/steps/trace.steps.js +270 -0
  76. package/dist/stores/instant.document-parser.d.ts +6 -0
  77. package/dist/stores/instant.document-parser.js +210 -0
  78. package/dist/stores/instant.documents.d.ts +16 -0
  79. package/dist/stores/instant.documents.js +152 -0
  80. package/dist/stores/instant.store.d.ts +66 -0
  81. package/dist/stores/instant.store.js +575 -0
  82. package/dist/tools-to-model-tools.d.ts +19 -0
  83. package/dist/tools-to-model-tools.js +21 -0
  84. package/package.json +142 -0
@@ -0,0 +1,1098 @@
1
+ import { registerContextEnv } from "./env.js";
2
+ import { OUTPUT_ITEM_TYPE, WEB_CHANNEL } from "./context.events.js";
3
+ import { applyToolExecutionResultToParts } from "./context.toolcalls.js";
4
+ import { isContextPartEnvelope, normalizePartsForPersistence, } from "./context.parts.js";
5
+ import { toolsToModelTools } from "./tools-to-model-tools.js";
6
+ import { createAiSdkReactor, } from "./context.reactor.js";
7
+ import { abortPersistedContextStepStream, closePersistedContextStepStream, createPersistedContextStepStream, closeContextStream, } from "./steps/stream.steps.js";
8
+ import { completeExecution, createContextStep, initializeContext, saveTriggerAndCreateExecution, saveContextPartsStep, updateContextContent, updateContextStatus, updateItem, updateContextStep, } from "./steps/store.steps.js";
9
+ import { getClientResumeHookUrl, toolApprovalHookToken, toolApprovalWebhookToken, } from "./context.hooks.js";
10
+ import { getContextDurableWorkflow } from "./context.durable.js";
11
+ export async function runContextReactionDirect(context, triggerEvent, params) {
12
+ return await ContextEngine.runDirect(context, triggerEvent, params);
13
+ }
14
+ export { toolApprovalHookToken, toolApprovalWebhookToken, getClientResumeHookUrl };
15
+ function nowIso() {
16
+ return new Date().toISOString();
17
+ }
18
+ function clipPreview(value, max = 240) {
19
+ if (value.length <= max)
20
+ return value;
21
+ return `${value.slice(0, max)}...`;
22
+ }
23
+ function summarizePartPreview(part) {
24
+ if (!part || typeof part !== "object")
25
+ return {};
26
+ if (isContextPartEnvelope(part)) {
27
+ const preview = part.content[0]?.type === "text"
28
+ ? part.content[0].text
29
+ : JSON.stringify(part.content[0] ?? part);
30
+ const state = "state" in part && typeof part.state === "string" ? part.state : undefined;
31
+ const toolCallId = "toolCallId" in part && typeof part.toolCallId === "string"
32
+ ? part.toolCallId
33
+ : undefined;
34
+ return {
35
+ partPreview: preview ? clipPreview(preview) : undefined,
36
+ partState: state,
37
+ partToolCallId: toolCallId,
38
+ };
39
+ }
40
+ const row = part;
41
+ const partType = typeof row.type === "string" ? row.type : "";
42
+ const partState = typeof row.state === "string" ? row.state : undefined;
43
+ const partToolCallId = typeof row.toolCallId === "string"
44
+ ? row.toolCallId
45
+ : typeof row.id === "string"
46
+ ? row.id
47
+ : undefined;
48
+ if (typeof row.text === "string" && row.text.trim().length > 0) {
49
+ return {
50
+ partPreview: clipPreview(row.text),
51
+ partState,
52
+ partToolCallId,
53
+ };
54
+ }
55
+ if (partType.startsWith("tool-")) {
56
+ const payload = {
57
+ tool: partType,
58
+ state: partState,
59
+ input: row.input,
60
+ output: row.output,
61
+ errorText: row.errorText,
62
+ };
63
+ return {
64
+ partPreview: clipPreview(JSON.stringify(payload)),
65
+ partState,
66
+ partToolCallId,
67
+ };
68
+ }
69
+ return {
70
+ partState,
71
+ partToolCallId,
72
+ };
73
+ }
74
+ async function emitContextEvents(params) {
75
+ void params;
76
+ }
77
+ async function measureBenchmark(benchmark, name, run) {
78
+ if (!benchmark)
79
+ return await run();
80
+ return await benchmark.measure(name, run);
81
+ }
82
+ async function readActiveWorkflowRunId() {
83
+ try {
84
+ const { getWorkflowMetadata } = await import("workflow");
85
+ const runId = getWorkflowMetadata?.()?.workflowRunId;
86
+ return runId ? String(runId) : null;
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ }
92
+ async function createRuntimeOps(env, benchmark) {
93
+ const { getContextRuntime } = await import("./runtime.js");
94
+ const runtime = await getContextRuntime(env);
95
+ const { db } = runtime;
96
+ const { InstantStore } = await import("./stores/instant.store.js");
97
+ const requireContextId = (contextIdentifier) => {
98
+ if ("id" in contextIdentifier && typeof contextIdentifier.id === "string" && contextIdentifier.id) {
99
+ return String(contextIdentifier.id);
100
+ }
101
+ throw new Error("ContextEngine direct runtime requires resolved context ids.");
102
+ };
103
+ const makeRuntimeId = () => globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(16).slice(2)}`;
104
+ const instrumentAsync = async (kind, run) => {
105
+ const startedAt = Date.now();
106
+ try {
107
+ return await run();
108
+ }
109
+ finally {
110
+ const elapsedMs = Date.now() - startedAt;
111
+ benchmark?.add?.("react.network.totalMs", elapsedMs);
112
+ benchmark?.add?.(`react.network.${kind}Ms`, elapsedMs);
113
+ benchmark?.add?.(`react.network.${kind}Count`, 1);
114
+ const currentStage = benchmark?.getCurrentStage?.();
115
+ if (currentStage) {
116
+ benchmark?.add?.(`${currentStage}.networkMs`, elapsedMs);
117
+ benchmark?.add?.(`${currentStage}.${kind}Count`, 1);
118
+ }
119
+ }
120
+ };
121
+ const instrumentedDb = new Proxy(db, {
122
+ get(target, prop, receiver) {
123
+ if (prop === "query") {
124
+ return async (...args) => await instrumentAsync("query", async () => await target.query(...args));
125
+ }
126
+ if (prop === "transact") {
127
+ return async (...args) => await instrumentAsync("transact", async () => await target.transact(...args));
128
+ }
129
+ return Reflect.get(target, prop, receiver);
130
+ },
131
+ });
132
+ const store = new InstantStore(instrumentedDb);
133
+ return {
134
+ db: instrumentedDb,
135
+ initializeContext: async (contextIdentifier) => {
136
+ if (!contextIdentifier) {
137
+ const context = await store.getOrCreateContext(null);
138
+ return { context, isNew: true };
139
+ }
140
+ const existing = await store.getContext(contextIdentifier);
141
+ if (existing) {
142
+ return { context: existing, isNew: false };
143
+ }
144
+ const context = await store.getOrCreateContext(contextIdentifier);
145
+ return { context, isNew: true };
146
+ },
147
+ updateContextContent: async (contextIdentifier, content) => await store.updateContextContent(contextIdentifier, content),
148
+ updateContextStatus: async (contextIdentifier, status) => await instrumentedDb.transact([
149
+ instrumentedDb.tx.event_contexts[requireContextId(contextIdentifier)].update({
150
+ status,
151
+ updatedAt: new Date(),
152
+ }),
153
+ ]),
154
+ saveTriggerAndCreateExecution: async ({ contextIdentifier, triggerEvent }) => {
155
+ const contextId = requireContextId(contextIdentifier);
156
+ const triggerId = String(triggerEvent.id);
157
+ const reactionId = makeRuntimeId();
158
+ const executionId = makeRuntimeId();
159
+ const reactionEvent = {
160
+ id: reactionId,
161
+ type: OUTPUT_ITEM_TYPE,
162
+ channel: typeof triggerEvent.channel === "string"
163
+ ? triggerEvent.channel
164
+ : WEB_CHANNEL,
165
+ createdAt: new Date().toISOString(),
166
+ status: "pending",
167
+ content: { parts: [] },
168
+ };
169
+ const now = new Date();
170
+ await instrumentedDb.transact([
171
+ instrumentedDb.tx.event_items[triggerId].update({
172
+ ...triggerEvent,
173
+ id: triggerId,
174
+ status: "stored",
175
+ }),
176
+ instrumentedDb.tx.event_items[triggerId].link({ context: contextId }),
177
+ instrumentedDb.tx.event_items[reactionId].update({
178
+ ...reactionEvent,
179
+ id: reactionId,
180
+ status: "pending",
181
+ }),
182
+ instrumentedDb.tx.event_items[reactionId].link({ context: contextId }),
183
+ instrumentedDb.tx.event_executions[executionId].create({
184
+ createdAt: now,
185
+ updatedAt: now,
186
+ status: "executing",
187
+ }),
188
+ instrumentedDb.tx.event_executions[executionId].link({ context: contextId }),
189
+ instrumentedDb.tx.event_executions[executionId].link({ trigger: triggerId }),
190
+ instrumentedDb.tx.event_executions[executionId].link({ reaction: reactionId }),
191
+ instrumentedDb.tx.event_items[triggerId].link({ execution: executionId }),
192
+ instrumentedDb.tx.event_items[reactionId].link({ execution: executionId }),
193
+ instrumentedDb.tx.event_contexts[contextId].update({
194
+ status: "open_streaming",
195
+ updatedAt: now,
196
+ }),
197
+ instrumentedDb.tx.event_contexts[contextId].link({ currentExecution: executionId }),
198
+ ]);
199
+ return {
200
+ triggerEvent: {
201
+ ...triggerEvent,
202
+ id: triggerId,
203
+ status: "stored",
204
+ },
205
+ reactionEvent,
206
+ execution: {
207
+ id: executionId,
208
+ status: "executing",
209
+ },
210
+ };
211
+ },
212
+ createContextStep: async ({ executionId, iteration }) => {
213
+ const stepId = makeRuntimeId();
214
+ await instrumentedDb.transact([
215
+ instrumentedDb.tx.event_steps[stepId].create({
216
+ createdAt: new Date(),
217
+ updatedAt: new Date(),
218
+ status: "running",
219
+ iteration,
220
+ }),
221
+ instrumentedDb.tx.event_steps[stepId].link({ execution: executionId }),
222
+ ]);
223
+ return { stepId };
224
+ },
225
+ updateContextStep: async (params) => {
226
+ await instrumentedDb.transact([
227
+ instrumentedDb.tx.event_steps[params.stepId].update({
228
+ ...params.patch,
229
+ updatedAt: new Date(),
230
+ }),
231
+ ]);
232
+ },
233
+ saveContextPartsStep: async (params) => {
234
+ await store.saveStepParts({ stepId: params.stepId, parts: params.parts });
235
+ },
236
+ updateItem: async (itemId, item) => {
237
+ await instrumentedDb.transact([instrumentedDb.tx.event_items[itemId].update(item)]);
238
+ return {
239
+ ...item,
240
+ id: itemId,
241
+ };
242
+ },
243
+ completeExecution: async (contextIdentifier, executionId, status) => {
244
+ const contextId = requireContextId(contextIdentifier);
245
+ await instrumentedDb.transact([
246
+ instrumentedDb.tx.event_executions[executionId].update({
247
+ status,
248
+ updatedAt: new Date(),
249
+ }),
250
+ instrumentedDb.tx.event_contexts[contextId].update({
251
+ status: "closed",
252
+ updatedAt: new Date(),
253
+ }),
254
+ ]);
255
+ },
256
+ };
257
+ }
258
+ async function createWorkflowOps(env) {
259
+ return {
260
+ initializeContext: async (contextIdentifier, opts) => await initializeContext(env, contextIdentifier, opts),
261
+ updateContextContent: async (contextIdentifier, content) => await updateContextContent(env, contextIdentifier, content),
262
+ updateContextStatus: async (contextIdentifier, status) => await updateContextStatus(env, contextIdentifier, status),
263
+ saveTriggerAndCreateExecution: async ({ contextIdentifier, triggerEvent }) => await saveTriggerAndCreateExecution({ env, contextIdentifier, triggerEvent }),
264
+ createContextStep: async ({ executionId, iteration }) => await createContextStep({ env, executionId, iteration }),
265
+ updateContextStep: async (params) => await updateContextStep({ env, ...params }),
266
+ saveContextPartsStep: async (params) => await saveContextPartsStep({ env, ...params }),
267
+ updateItem: async (itemId, item, opts) => await updateItem(env, itemId, item, opts),
268
+ completeExecution: async (contextIdentifier, executionId, status) => await completeExecution(env, contextIdentifier, executionId, status),
269
+ };
270
+ }
271
+ async function getContextEngineOps(env, benchmark) {
272
+ const workflowRunId = await readActiveWorkflowRunId();
273
+ if (workflowRunId) {
274
+ registerContextEnv(env, workflowRunId);
275
+ return await createWorkflowOps(env);
276
+ }
277
+ registerContextEnv(env);
278
+ return await createRuntimeOps(env, benchmark);
279
+ }
280
+ export class ContextEngine {
281
+ constructor(opts = {}, reactor) {
282
+ this.opts = opts;
283
+ this.reactor = reactor ?? createAiSdkReactor();
284
+ }
285
+ async buildSkills(_context, _env) {
286
+ return [];
287
+ }
288
+ /**
289
+ * First-class event expansion stage (runs on every iteration of the durable loop).
290
+ *
291
+ * Use this to expand/normalize events before they are converted into model messages.
292
+ * Typical use-cases:
293
+ * - Expand file/document references into text (LlamaCloud/Reducto/…)
294
+ * - Token compaction / summarization of older parts
295
+ * - Attaching derived context snippets to the next model call
296
+ *
297
+ * IMPORTANT:
298
+ * - This stage is ALWAYS executed by the engine.
299
+ * - If you don't provide an implementation, the default behavior is an identity transform
300
+ * (events pass through unchanged).
301
+ * - If your implementation performs I/O, implement it as a `"use-step"` function (provided via
302
+ * the builder) so results are durable and replay-safe.
303
+ * - If it’s pure/deterministic, it can run in workflow context.
304
+ */
305
+ async expandEvents(events, _context, _env) {
306
+ return events;
307
+ }
308
+ getModel(_context, _env) {
309
+ return "openai/gpt-5";
310
+ }
311
+ getReactor(_context, _env) {
312
+ return this.reactor;
313
+ }
314
+ /**
315
+ * Context stop/continue hook.
316
+ *
317
+ * After the model streamed and tools executed, the story can decide whether the loop should
318
+ * continue.
319
+ *
320
+ * Default: `true` (continue).
321
+ */
322
+ async shouldContinue(_args) {
323
+ return true;
324
+ }
325
+ async react(triggerEvent, params) {
326
+ if (params.durable) {
327
+ return await ContextEngine.startDurable(this, triggerEvent, params);
328
+ }
329
+ return await ContextEngine.runDirect(this, triggerEvent, params);
330
+ }
331
+ static async prepareExecutionShell(story, triggerEvent, params) {
332
+ const ops = await measureBenchmark(params.__benchmark, "react.resolveOpsMs", async () => await getContextEngineOps(params.env, params.__benchmark));
333
+ const silent = params.options?.silent ?? false;
334
+ const ctxResult = await measureBenchmark(params.__benchmark, "react.initializeContextMs", async () => await ops.initializeContext(params.context ?? null, { silent }));
335
+ let currentContext = ctxResult.context;
336
+ const contextSelector = { id: String(currentContext.id) };
337
+ if (ctxResult.isNew) {
338
+ await story.opts.onContextCreated?.({ env: params.env, context: currentContext });
339
+ }
340
+ if (currentContext.status === "closed") {
341
+ await measureBenchmark(params.__benchmark, "react.reopenClosedContextMs", async () => await ops.updateContextStatus(contextSelector, "open_idle"));
342
+ currentContext = { ...currentContext, status: "open_idle" };
343
+ }
344
+ const shell = await measureBenchmark(params.__benchmark, "react.bootstrapShellMs", async () => await ops.saveTriggerAndCreateExecution({
345
+ contextIdentifier: contextSelector,
346
+ triggerEvent,
347
+ }));
348
+ currentContext = { ...currentContext, status: "open_streaming" };
349
+ return {
350
+ contextSelector,
351
+ currentContext,
352
+ trigger: shell.triggerEvent,
353
+ reaction: shell.reactionEvent,
354
+ execution: shell.execution,
355
+ };
356
+ }
357
+ static async startDurable(story, triggerEvent, params) {
358
+ if (params.options?.writable) {
359
+ throw new Error("ContextEngine.react: durable runs manage their own workflow stream");
360
+ }
361
+ const contextKey = typeof story.__contextKey === "string" ? String(story.__contextKey) : "";
362
+ if (!contextKey) {
363
+ throw new Error("ContextEngine.react: durable mode requires a context built with createContext(...).build().");
364
+ }
365
+ const workflow = getContextDurableWorkflow();
366
+ if (typeof workflow !== "function") {
367
+ throw new Error("ContextEngine.react: durable workflow is not configured. Call configureContextDurableWorkflow(...) in runtime bootstrap.");
368
+ }
369
+ const shell = await ContextEngine.prepareExecutionShell(story, triggerEvent, params);
370
+ try {
371
+ const [{ start }] = await Promise.all([
372
+ import("workflow/api"),
373
+ ]);
374
+ const run = await start(workflow, [
375
+ {
376
+ contextKey,
377
+ env: params.env,
378
+ context: params.context ?? null,
379
+ triggerEvent,
380
+ options: {
381
+ maxIterations: params.options?.maxIterations,
382
+ maxModelSteps: params.options?.maxModelSteps,
383
+ preventClose: params.options?.preventClose,
384
+ sendFinish: params.options?.sendFinish,
385
+ silent: params.options?.silent,
386
+ },
387
+ bootstrap: {
388
+ contextId: shell.currentContext.id,
389
+ trigger: shell.trigger,
390
+ reaction: shell.reaction,
391
+ execution: shell.execution,
392
+ },
393
+ },
394
+ ]);
395
+ const runtime = await createRuntimeOps(params.env);
396
+ await runtime.db.transact([
397
+ runtime.db.tx.event_executions[shell.execution.id].update({
398
+ workflowRunId: run.runId,
399
+ updatedAt: new Date(),
400
+ }),
401
+ ]);
402
+ }
403
+ catch (error) {
404
+ const ops = await getContextEngineOps(params.env, params.__benchmark);
405
+ await ops.completeExecution(shell.contextSelector, shell.execution.id, "failed").catch(() => null);
406
+ throw error;
407
+ }
408
+ return {
409
+ context: shell.currentContext,
410
+ trigger: shell.trigger,
411
+ reaction: shell.reaction,
412
+ execution: shell.execution,
413
+ };
414
+ }
415
+ static async runDirect(story, triggerEvent, params) {
416
+ const ops = await measureBenchmark(params.__benchmark, "react.resolveOpsMs", async () => await getContextEngineOps(params.env, params.__benchmark));
417
+ const maxIterations = params.options?.maxIterations ?? 20;
418
+ const maxModelSteps = params.options?.maxModelSteps ?? 1;
419
+ const preventClose = params.options?.preventClose ?? false;
420
+ const sendFinish = params.options?.sendFinish ?? true;
421
+ const silent = params.options?.silent ?? false;
422
+ const writable = params.options?.writable;
423
+ const bootstrapped = params.__bootstrap;
424
+ const shell = bootstrapped
425
+ ? {
426
+ contextSelector: { id: String(bootstrapped.contextId) },
427
+ currentContext: (await measureBenchmark(params.__benchmark, "react.bootstrapContextLookupMs", async () => await ops.initializeContext({ id: String(bootstrapped.contextId) }, { silent }))).context,
428
+ trigger: bootstrapped.trigger,
429
+ reaction: bootstrapped.reaction,
430
+ execution: bootstrapped.execution,
431
+ }
432
+ : await ContextEngine.prepareExecutionShell(story, triggerEvent, params);
433
+ let currentContext = shell.currentContext;
434
+ let trigger = shell.trigger;
435
+ let reactionEvent = shell.reaction;
436
+ let execution = shell.execution;
437
+ const activeContextSelector = shell.contextSelector;
438
+ const triggerEventId = trigger.id;
439
+ const reactionEventId = reactionEvent.id;
440
+ const executionId = execution.id;
441
+ let updatedContext = { ...currentContext, status: "open_streaming" };
442
+ let currentStepId = null;
443
+ let currentStepStream = null;
444
+ const failExecution = async () => {
445
+ try {
446
+ await ops.completeExecution(activeContextSelector, executionId, "failed");
447
+ execution = { ...execution, status: "failed" };
448
+ updatedContext = { ...updatedContext, status: "closed" };
449
+ await emitContextEvents({
450
+ silent,
451
+ writable,
452
+ events: [
453
+ {
454
+ type: "execution.failed",
455
+ at: nowIso(),
456
+ executionId,
457
+ contextId: String(currentContext.id),
458
+ status: "failed",
459
+ },
460
+ {
461
+ type: "context.status_changed",
462
+ at: nowIso(),
463
+ contextId: String(currentContext.id),
464
+ status: "closed",
465
+ },
466
+ ],
467
+ });
468
+ }
469
+ catch {
470
+ // noop
471
+ }
472
+ try {
473
+ if (!silent) {
474
+ await closeContextStream({ preventClose, sendFinish, writable });
475
+ }
476
+ }
477
+ catch {
478
+ // noop
479
+ }
480
+ };
481
+ try {
482
+ for (let iter = 0; iter < maxIterations; iter++) {
483
+ // Create a persisted step per iteration (IDs generated in step runtime for replay safety)
484
+ const stagePrefix = `react.iteration.${iter}`;
485
+ const stepCreate = await measureBenchmark(params.__benchmark, `${stagePrefix}.createStepMs`, async () => await ops.createContextStep({
486
+ executionId,
487
+ iteration: iter,
488
+ }));
489
+ currentStepId = stepCreate.stepId;
490
+ currentStepStream = await createPersistedContextStepStream({
491
+ env: params.env,
492
+ executionId,
493
+ stepId: stepCreate.stepId,
494
+ });
495
+ await emitContextEvents({
496
+ silent,
497
+ writable,
498
+ events: [
499
+ {
500
+ type: "step.created",
501
+ at: nowIso(),
502
+ stepId: String(stepCreate.stepId),
503
+ executionId,
504
+ iteration: iter,
505
+ status: "running",
506
+ },
507
+ ],
508
+ });
509
+ // Hook: Context DSL `context()` (implemented by subclasses via `initialize()`)
510
+ const nextContent = await measureBenchmark(params.__benchmark, `${stagePrefix}.contextMs`, async () => await story.initialize(updatedContext, params.env));
511
+ updatedContext = await measureBenchmark(params.__benchmark, `${stagePrefix}.persistContextMs`, async () => await ops.updateContextContent(activeContextSelector, nextContent));
512
+ await emitContextEvents({
513
+ silent,
514
+ writable,
515
+ events: [
516
+ {
517
+ type: "context.content_updated",
518
+ at: nowIso(),
519
+ contextId: String(updatedContext.id),
520
+ },
521
+ ],
522
+ });
523
+ await story.opts.onContextUpdated?.({ env: params.env, context: updatedContext });
524
+ // Hook: Context DSL `narrative()` (implemented by subclasses via `buildSystemPrompt()`)
525
+ const systemPrompt = await measureBenchmark(params.__benchmark, `${stagePrefix}.narrativeMs`, async () => await story.buildSystemPrompt(updatedContext, params.env));
526
+ // Hook: Context DSL `actions()` (implemented by subclasses via `buildTools()`)
527
+ const toolsAll = await measureBenchmark(params.__benchmark, `${stagePrefix}.actionsMs`, async () => await story.buildTools(updatedContext, params.env));
528
+ const skillsAll = await measureBenchmark(params.__benchmark, `${stagePrefix}.skillsMs`, async () => await story.buildSkills(updatedContext, params.env));
529
+ // IMPORTANT: step args must be serializable.
530
+ // Match DurableAgent behavior: convert tool input schemas to plain JSON Schema in workflow context.
531
+ const toolsForModel = toolsToModelTools(toolsAll);
532
+ // Execute model reaction for this iteration using the stable reaction event id.
533
+ //
534
+ // IMPORTANT:
535
+ // We expose a single visible `context_event` per story turn (`reactionEventId`).
536
+ // If we stream with a per-step id, the UI will render an optimistic assistant message
537
+ // (step id) and then a second persisted assistant message (reaction id) with the same
538
+ // content once InstantDB updates.
539
+ const reactor = story.getReactor(updatedContext, params.env);
540
+ const reactionPartsBeforeStep = Array.isArray(reactionEvent.content?.parts)
541
+ ? [...reactionEvent.content.parts]
542
+ : [];
543
+ let persistedReactionPartsSignature = "";
544
+ const persistReactionParts = async (nextParts) => {
545
+ const normalizedParts = normalizePartsForPersistence(Array.isArray(nextParts) ? nextParts : []);
546
+ const nextSignature = JSON.stringify(normalizedParts);
547
+ if (nextSignature === persistedReactionPartsSignature)
548
+ return;
549
+ persistedReactionPartsSignature = nextSignature;
550
+ await ops.saveContextPartsStep({
551
+ stepId: stepCreate.stepId,
552
+ parts: normalizedParts,
553
+ executionId,
554
+ contextId: String(currentContext.id),
555
+ iteration: iter,
556
+ });
557
+ reactionEvent = await ops.updateItem(reactionEvent.id, {
558
+ ...reactionEvent,
559
+ content: {
560
+ ...reactionEvent.content,
561
+ parts: [...reactionPartsBeforeStep, ...normalizedParts],
562
+ },
563
+ status: "pending",
564
+ }, { executionId, contextId: String(currentContext.id) });
565
+ };
566
+ const { assistantEvent, actionRequests, messagesForModel } = await measureBenchmark(params.__benchmark, `${stagePrefix}.reactorMs`, async () => await reactor({
567
+ env: params.env,
568
+ context: updatedContext,
569
+ contextIdentifier: activeContextSelector,
570
+ triggerEvent,
571
+ model: story.getModel(updatedContext, params.env),
572
+ systemPrompt,
573
+ actions: toolsAll,
574
+ toolsForModel,
575
+ skills: skillsAll,
576
+ eventId: reactionEventId,
577
+ executionId,
578
+ contextId: String(currentContext.id),
579
+ stepId: String(stepCreate.stepId),
580
+ iteration: iter,
581
+ maxModelSteps,
582
+ // Only emit a `start` chunk once per story turn.
583
+ sendStart: !silent && iter === 0,
584
+ silent,
585
+ contextStepStream: currentStepStream?.stream,
586
+ writable,
587
+ persistReactionParts,
588
+ }));
589
+ const reviewRequests = actionRequests.length > 0
590
+ ? actionRequests.flatMap((actionRequest) => {
591
+ const toolDef = toolsAll[actionRequest.actionName];
592
+ const auto = toolDef?.auto !== false;
593
+ actionRequest.auto = auto;
594
+ if (auto)
595
+ return [];
596
+ return [
597
+ {
598
+ toolCallId: String(actionRequest.actionRef),
599
+ toolName: String(actionRequest.actionName ?? ""),
600
+ },
601
+ ];
602
+ })
603
+ : [];
604
+ // Persist normalized parts hanging off the producing step (event_parts).
605
+ // IMPORTANT:
606
+ // We intentionally do NOT persist the per-step LLM assistant event as a `context_event`.
607
+ // The story exposes a single visible `context_event` per turn (`reactionEventId`) so the UI
608
+ // doesn't render duplicate assistant messages (LLM-step + aggregated reaction).
609
+ const stepParts = normalizePartsForPersistence((assistantEvent?.content?.parts ?? []));
610
+ const assistantEventEffective = {
611
+ ...assistantEvent,
612
+ content: {
613
+ ...(assistantEvent?.content ?? {}),
614
+ parts: stepParts,
615
+ },
616
+ };
617
+ await measureBenchmark(params.__benchmark, `${stagePrefix}.saveStepPartsMs`, async () => await ops.saveContextPartsStep({
618
+ stepId: stepCreate.stepId,
619
+ parts: stepParts,
620
+ executionId,
621
+ contextId: String(currentContext.id),
622
+ iteration: iter,
623
+ }));
624
+ await emitContextEvents({
625
+ silent,
626
+ writable,
627
+ events: stepParts.map((part, idx) => ({
628
+ type: "part.created",
629
+ at: nowIso(),
630
+ partKey: `${String(stepCreate.stepId)}:${idx}`,
631
+ stepId: String(stepCreate.stepId),
632
+ idx,
633
+ partType: part && typeof part.type === "string"
634
+ ? String(part.type)
635
+ : undefined,
636
+ ...summarizePartPreview(part),
637
+ })),
638
+ });
639
+ // Persist/append the aggregated reaction event (stable `reactionEventId` for the execution).
640
+ const nextAssistantParts = Array.isArray(assistantEventEffective.content?.parts)
641
+ ? assistantEventEffective.content.parts
642
+ : [];
643
+ const nextReactionEvent = {
644
+ ...reactionEvent,
645
+ content: {
646
+ ...reactionEvent.content,
647
+ parts: [...reactionPartsBeforeStep, ...nextAssistantParts],
648
+ },
649
+ status: "pending",
650
+ };
651
+ reactionEvent = await measureBenchmark(params.__benchmark, `${stagePrefix}.persistAssistantReactionMs`, async () => await ops.updateItem(reactionEvent.id, nextReactionEvent, { executionId, contextId: String(currentContext.id) }));
652
+ if (currentStepStream) {
653
+ await closePersistedContextStepStream({
654
+ env: params.env,
655
+ session: currentStepStream,
656
+ });
657
+ currentStepStream = null;
658
+ }
659
+ story.opts.onEventCreated?.(assistantEventEffective);
660
+ const firstActionRequest = actionRequests?.[0];
661
+ await measureBenchmark(params.__benchmark, `${stagePrefix}.markStepRunningMs`, async () => await ops.updateContextStep({
662
+ stepId: stepCreate.stepId,
663
+ patch: firstActionRequest
664
+ ? {
665
+ kind: "action_execute",
666
+ actionName: typeof firstActionRequest.actionName === "string"
667
+ ? firstActionRequest.actionName
668
+ : undefined,
669
+ actionInput: firstActionRequest.input,
670
+ }
671
+ : {
672
+ kind: "message",
673
+ },
674
+ executionId,
675
+ contextId: String(currentContext.id),
676
+ iteration: iter,
677
+ }));
678
+ await emitContextEvents({
679
+ silent,
680
+ writable,
681
+ events: [
682
+ {
683
+ type: "step.updated",
684
+ at: nowIso(),
685
+ stepId: String(stepCreate.stepId),
686
+ executionId,
687
+ iteration: iter,
688
+ status: "running",
689
+ kind: firstActionRequest ? "action_execute" : "message",
690
+ actionName: firstActionRequest && typeof firstActionRequest.actionName === "string"
691
+ ? firstActionRequest.actionName
692
+ : undefined,
693
+ },
694
+ ],
695
+ });
696
+ // Done: no tool calls requested by the model
697
+ if (!actionRequests.length) {
698
+ const endResult = await story.callOnEnd(assistantEventEffective);
699
+ if (endResult) {
700
+ // Mark iteration step completed (no tools)
701
+ await measureBenchmark(params.__benchmark, `${stagePrefix}.finalizeMessageStepMs`, async () => await ops.updateContextStep({
702
+ stepId: stepCreate.stepId,
703
+ patch: {
704
+ status: "completed",
705
+ kind: "message",
706
+ actionRequests: [],
707
+ actionResults: [],
708
+ continueLoop: false,
709
+ },
710
+ executionId,
711
+ contextId: String(currentContext.id),
712
+ iteration: iter,
713
+ }));
714
+ await emitContextEvents({
715
+ silent,
716
+ writable,
717
+ events: [
718
+ {
719
+ type: "step.updated",
720
+ at: nowIso(),
721
+ stepId: String(stepCreate.stepId),
722
+ executionId,
723
+ iteration: iter,
724
+ status: "completed",
725
+ kind: "message",
726
+ },
727
+ {
728
+ type: "step.completed",
729
+ at: nowIso(),
730
+ stepId: String(stepCreate.stepId),
731
+ executionId,
732
+ iteration: iter,
733
+ status: "completed",
734
+ },
735
+ ],
736
+ });
737
+ // Mark reaction event completed
738
+ await measureBenchmark(params.__benchmark, `${stagePrefix}.completeReactionMs`, async () => await ops.updateItem(reactionEventId, {
739
+ ...reactionEvent,
740
+ status: "completed",
741
+ }, { executionId, contextId: String(currentContext.id) }));
742
+ await emitContextEvents({
743
+ silent,
744
+ writable,
745
+ events: [
746
+ {
747
+ type: "item.completed",
748
+ at: nowIso(),
749
+ itemId: String(reactionEventId),
750
+ contextId: String(currentContext.id),
751
+ executionId,
752
+ status: "completed",
753
+ },
754
+ ],
755
+ });
756
+ await measureBenchmark(params.__benchmark, `${stagePrefix}.completeExecutionMs`, async () => await ops.completeExecution(activeContextSelector, executionId, "completed"));
757
+ execution = { ...execution, status: "completed" };
758
+ updatedContext = { ...updatedContext, status: "closed" };
759
+ await emitContextEvents({
760
+ silent,
761
+ writable,
762
+ events: [
763
+ {
764
+ type: "execution.completed",
765
+ at: nowIso(),
766
+ executionId,
767
+ contextId: String(currentContext.id),
768
+ status: "completed",
769
+ },
770
+ {
771
+ type: "context.status_changed",
772
+ at: nowIso(),
773
+ contextId: String(currentContext.id),
774
+ status: "closed",
775
+ },
776
+ ],
777
+ });
778
+ if (!silent) {
779
+ await closeContextStream({ preventClose, sendFinish, writable });
780
+ }
781
+ reactionEvent = {
782
+ ...reactionEvent,
783
+ status: "completed",
784
+ };
785
+ return {
786
+ context: updatedContext,
787
+ trigger,
788
+ reaction: reactionEvent,
789
+ execution,
790
+ };
791
+ }
792
+ }
793
+ // Execute actions (workflow context; action implementations decide step vs workflow)
794
+ const actionResults = await measureBenchmark(params.__benchmark, `${stagePrefix}.actionExecutionMs`, async () => await Promise.all(actionRequests.map(async (actionRequest) => {
795
+ const toolDef = toolsAll[actionRequest.actionName];
796
+ if (!toolDef || typeof toolDef.execute !== "function") {
797
+ return {
798
+ actionRequest,
799
+ success: false,
800
+ output: null,
801
+ errorText: `Action "${actionRequest.actionName}" not found or has no execute().`,
802
+ };
803
+ }
804
+ try {
805
+ let actionInput = actionRequest.input;
806
+ if (toolDef?.auto === false) {
807
+ const { createHook, createWebhook } = await import("workflow");
808
+ const toolCallId = String(actionRequest.actionRef);
809
+ const hookToken = toolApprovalHookToken({ executionId, toolCallId });
810
+ const webhookToken = toolApprovalWebhookToken({ executionId, toolCallId });
811
+ const hook = createHook({ token: hookToken });
812
+ const webhook = createWebhook({ token: webhookToken });
813
+ const approvalOrRequest = await Promise.race([
814
+ hook.then((approval) => ({ source: "hook", approval })),
815
+ webhook.then((request) => ({ source: "webhook", request })),
816
+ ]);
817
+ const approval = approvalOrRequest.source === "hook"
818
+ ? approvalOrRequest.approval
819
+ : await approvalOrRequest.request.json().catch(() => null);
820
+ if (!approval || approval.approved !== true) {
821
+ return {
822
+ actionRequest,
823
+ success: false,
824
+ output: null,
825
+ errorText: approval && "comment" in approval && approval.comment
826
+ ? `Action execution not approved: ${approval.comment}`
827
+ : "Action execution not approved",
828
+ };
829
+ }
830
+ if ("args" in approval && approval.args !== undefined) {
831
+ actionInput = approval.args;
832
+ }
833
+ }
834
+ const output = await toolDef.execute(actionInput, {
835
+ toolCallId: actionRequest.actionRef,
836
+ messages: messagesForModel,
837
+ eventId: reactionEventId,
838
+ executionId,
839
+ triggerEventId,
840
+ contextId: currentContext.id,
841
+ });
842
+ return { actionRequest, success: true, output };
843
+ }
844
+ catch (e) {
845
+ return {
846
+ actionRequest,
847
+ success: false,
848
+ output: null,
849
+ errorText: e instanceof Error ? e.message : String(e),
850
+ };
851
+ }
852
+ })));
853
+ // Merge action results into persisted parts (so next LLM call can see them)
854
+ let finalizedStepParts = Array.isArray(stepParts) ? [...stepParts] : [];
855
+ for (const r of actionResults) {
856
+ finalizedStepParts = applyToolExecutionResultToParts(finalizedStepParts, {
857
+ toolCallId: r.actionRequest.actionRef,
858
+ toolName: r.actionRequest.actionName,
859
+ }, {
860
+ success: Boolean(r.success),
861
+ result: r.output,
862
+ message: r.errorText,
863
+ });
864
+ }
865
+ await measureBenchmark(params.__benchmark, `${stagePrefix}.saveFinalStepPartsMs`, async () => await ops.saveContextPartsStep({
866
+ stepId: stepCreate.stepId,
867
+ parts: finalizedStepParts,
868
+ executionId,
869
+ contextId: String(currentContext.id),
870
+ iteration: iter,
871
+ }));
872
+ reactionEvent = {
873
+ ...reactionEvent,
874
+ content: {
875
+ ...reactionEvent.content,
876
+ // Deprecated mirror for compatibility. `event_parts` are the
877
+ // source of truth for replay and step inspection.
878
+ parts: [...reactionPartsBeforeStep, ...finalizedStepParts],
879
+ },
880
+ status: "pending",
881
+ };
882
+ // Callback for observability/integration
883
+ for (const r of actionResults) {
884
+ await story.opts.onActionExecuted?.({
885
+ actionRequest: r.actionRequest,
886
+ success: r.success,
887
+ output: r.output,
888
+ errorText: r.errorText,
889
+ eventId: reactionEventId,
890
+ executionId,
891
+ });
892
+ }
893
+ // Stop/continue boundary: allow the Context to decide if the loop should continue.
894
+ // IMPORTANT: we call this after tool results have been merged into the persisted `reactionEvent`,
895
+ // so stories can inspect `reactionEvent.content.parts` deterministically.
896
+ const continueLoop = await measureBenchmark(params.__benchmark, `${stagePrefix}.shouldContinueMs`, async () => await story.shouldContinue({
897
+ env: params.env,
898
+ context: updatedContext,
899
+ reactionEvent,
900
+ assistantEvent: assistantEventEffective,
901
+ actionRequests,
902
+ actionResults: actionResults,
903
+ }));
904
+ // Persist per-iteration step outcome (tools + continue signal)
905
+ await measureBenchmark(params.__benchmark, `${stagePrefix}.completeStepMs`, async () => await ops.updateContextStep({
906
+ stepId: stepCreate.stepId,
907
+ patch: {
908
+ status: "completed",
909
+ kind: actionRequests?.length ? "action_result" : "message",
910
+ actionName: typeof actionResults?.[0]?.actionRequest?.actionName === "string"
911
+ ? actionResults[0].actionRequest.actionName
912
+ : undefined,
913
+ actionInput: actionResults?.[0]?.actionRequest?.input,
914
+ actionOutput: actionResults?.[0]?.success === true
915
+ ? actionResults[0]?.output
916
+ : undefined,
917
+ actionError: actionResults?.[0]?.success === false
918
+ ? String(actionResults[0]?.errorText ?? "action_execution_failed")
919
+ : undefined,
920
+ actionRequests,
921
+ actionResults,
922
+ continueLoop: continueLoop !== false,
923
+ },
924
+ executionId,
925
+ contextId: String(currentContext.id),
926
+ iteration: iter,
927
+ }));
928
+ await emitContextEvents({
929
+ silent,
930
+ writable,
931
+ events: [
932
+ {
933
+ type: "step.updated",
934
+ at: nowIso(),
935
+ stepId: String(stepCreate.stepId),
936
+ executionId,
937
+ iteration: iter,
938
+ status: "completed",
939
+ kind: actionRequests?.length ? "action_result" : "message",
940
+ actionName: typeof actionResults?.[0]?.actionRequest?.actionName === "string"
941
+ ? actionResults[0].actionRequest.actionName
942
+ : undefined,
943
+ },
944
+ {
945
+ type: "step.completed",
946
+ at: nowIso(),
947
+ stepId: String(stepCreate.stepId),
948
+ executionId,
949
+ iteration: iter,
950
+ status: "completed",
951
+ },
952
+ ],
953
+ });
954
+ if (continueLoop !== false) {
955
+ reactionEvent = await measureBenchmark(params.__benchmark, `${stagePrefix}.persistPendingReactionMs`, async () => await ops.updateItem(reactionEventId, {
956
+ ...reactionEvent,
957
+ status: "pending",
958
+ }, { executionId, contextId: String(currentContext.id) }));
959
+ await emitContextEvents({
960
+ silent,
961
+ writable,
962
+ events: [
963
+ {
964
+ type: "item.updated",
965
+ at: nowIso(),
966
+ itemId: String(reactionEventId),
967
+ contextId: String(currentContext.id),
968
+ executionId,
969
+ status: "pending",
970
+ },
971
+ ],
972
+ });
973
+ }
974
+ if (continueLoop === false) {
975
+ await measureBenchmark(params.__benchmark, `${stagePrefix}.completeReactionMs`, async () => await ops.updateItem(reactionEventId, {
976
+ ...reactionEvent,
977
+ status: "completed",
978
+ }, { executionId, contextId: String(currentContext.id) }));
979
+ await emitContextEvents({
980
+ silent,
981
+ writable,
982
+ events: [
983
+ {
984
+ type: "item.completed",
985
+ at: nowIso(),
986
+ itemId: String(reactionEventId),
987
+ contextId: String(currentContext.id),
988
+ executionId,
989
+ status: "completed",
990
+ },
991
+ ],
992
+ });
993
+ await measureBenchmark(params.__benchmark, `${stagePrefix}.completeExecutionMs`, async () => await ops.completeExecution(activeContextSelector, executionId, "completed"));
994
+ execution = { ...execution, status: "completed" };
995
+ updatedContext = { ...updatedContext, status: "closed" };
996
+ await emitContextEvents({
997
+ silent,
998
+ writable,
999
+ events: [
1000
+ {
1001
+ type: "execution.completed",
1002
+ at: nowIso(),
1003
+ executionId,
1004
+ contextId: String(currentContext.id),
1005
+ status: "completed",
1006
+ },
1007
+ {
1008
+ type: "context.status_changed",
1009
+ at: nowIso(),
1010
+ contextId: String(currentContext.id),
1011
+ status: "closed",
1012
+ },
1013
+ ],
1014
+ });
1015
+ if (!silent) {
1016
+ await closeContextStream({ preventClose, sendFinish, writable });
1017
+ }
1018
+ reactionEvent = {
1019
+ ...reactionEvent,
1020
+ status: "completed",
1021
+ };
1022
+ return {
1023
+ context: updatedContext,
1024
+ trigger,
1025
+ reaction: reactionEvent,
1026
+ execution,
1027
+ };
1028
+ }
1029
+ }
1030
+ throw new Error(`ContextEngine: maxIterations reached (${maxIterations}) without completion`);
1031
+ }
1032
+ catch (error) {
1033
+ if (currentStepStream) {
1034
+ try {
1035
+ await abortPersistedContextStepStream({
1036
+ env: params.env,
1037
+ session: currentStepStream,
1038
+ reason: error instanceof Error ? error.message : String(error),
1039
+ });
1040
+ }
1041
+ catch {
1042
+ // noop
1043
+ }
1044
+ finally {
1045
+ currentStepStream = null;
1046
+ }
1047
+ }
1048
+ // Best-effort: persist failure on the current iteration step (if any)
1049
+ if (currentStepId) {
1050
+ const failedStepId = currentStepId;
1051
+ try {
1052
+ await measureBenchmark(params.__benchmark, "react.failureStepPersistMs", async () => await ops.updateContextStep({
1053
+ stepId: failedStepId,
1054
+ patch: {
1055
+ status: "failed",
1056
+ errorText: error instanceof Error ? error.message : String(error),
1057
+ },
1058
+ executionId,
1059
+ contextId: String(currentContext.id),
1060
+ }));
1061
+ await emitContextEvents({
1062
+ silent,
1063
+ writable,
1064
+ events: [
1065
+ {
1066
+ type: "step.failed",
1067
+ at: nowIso(),
1068
+ stepId: String(failedStepId),
1069
+ executionId,
1070
+ status: "failed",
1071
+ errorText: error instanceof Error ? error.message : String(error),
1072
+ },
1073
+ ],
1074
+ });
1075
+ }
1076
+ catch {
1077
+ // noop
1078
+ }
1079
+ }
1080
+ await failExecution();
1081
+ throw error;
1082
+ }
1083
+ }
1084
+ /**
1085
+ * @deprecated Use `react()` instead. Kept for backwards compatibility.
1086
+ */
1087
+ async stream(triggerEvent, params) {
1088
+ return await this.react(triggerEvent, params);
1089
+ }
1090
+ async callOnEnd(lastEvent) {
1091
+ if (!this.opts.onEnd)
1092
+ return true;
1093
+ const result = await this.opts.onEnd(lastEvent);
1094
+ if (typeof result === "boolean")
1095
+ return result;
1096
+ return true;
1097
+ }
1098
+ }