@ekairos/events 1.22.34-beta.development.0 → 1.22.36-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.
package/README.md CHANGED
@@ -2,114 +2,87 @@
2
2
 
3
3
  Context-first durable execution runtime for Ekairos.
4
4
 
5
- ## Surface
5
+ ## What this package does
6
6
 
7
- - `createContext`, `context`, `ContextEngine`
8
- - `createAiSdkReactor`, `createScriptedReactor`
9
- - `useContext`
7
+ - creates durable contexts with `createContext(...)`
8
+ - persists executions, steps, parts, and items
9
+ - runs direct or durable `react(...)` loops
10
+ - adapts model/tool output into canonical `event_parts`
11
+
12
+ ## Main APIs
13
+
14
+ - `createContext`
15
+ - `ContextEngine`
16
+ - `createAiSdkReactor`
17
+ - `createScriptedReactor`
18
+ - `runContextReactionDirect`
10
19
  - `eventsDomain`
11
- - `getContextRuntime`, `getContextEnv`, `registerContextEnv`
12
20
 
13
21
  ## Runtime model
14
22
 
23
+ Canonical entities:
24
+
15
25
  - `event_contexts`
16
26
  - `event_items`
17
27
  - `event_executions`
18
28
  - `event_steps`
19
29
  - `event_parts`
20
- - `event_trace_events`
21
- - `event_trace_runs`
22
- - `event_trace_spans`
23
-
24
- The aggregate is `context`. Executions, steps, parts, and items are scoped to a context.
25
-
26
- ## Canonical Parts
30
+ - `event_trace_*`
27
31
 
28
- `event_parts` is the canonical content model for produced output.
32
+ `event_parts` is the source of truth for replay.
29
33
 
30
- Rules:
31
-
32
- - `event_parts.part` is the source of truth for replay and inspection.
33
- - `event_items.content.parts` on output items is maintained as a compatibility mirror and is deprecated as a replay source.
34
- - Provider/model-specific values must live under `metadata`, never as first-class semantic fields.
35
-
36
- Canonical part kinds:
37
-
38
- - `content`
39
- - `reasoning`
40
- - `source`
41
- - `tool-call`
42
- - `tool-result`
34
+ ## Example
43
35
 
44
- Each canonical part stores a `content` array. The entries inside that array define the payload type:
36
+ ```ts
37
+ import { createContext } from "@ekairos/events";
45
38
 
46
- - `text`
47
- - `file`
48
- - `json`
49
- - `source-url`
50
- - `source-document`
39
+ const supportContext = createContext<{ orgId: string }>("support.agent")
40
+ .context((stored, env) => ({
41
+ ...stored.content,
42
+ orgId: env.orgId,
43
+ }))
44
+ .narrative(() => "You are a precise assistant.")
45
+ .actions(() => ({}))
46
+ .build();
47
+ ```
51
48
 
52
- Example tool result:
49
+ Run directly:
53
50
 
54
51
  ```ts
55
- {
56
- type: "tool-result",
57
- toolCallId: "call_123",
58
- toolName: "inspectCanvasRegion",
59
- state: "output-available",
60
- content: [
61
- {
62
- type: "text",
63
- text: "Zoomed crop of the requested region.",
64
- },
65
- {
66
- type: "file",
67
- mediaType: "image/png",
68
- filename: "inspect-region.png",
69
- data: "iVBORw0KGgoAAAANSUhEUgAA...",
70
- },
71
- ],
72
- metadata: {
73
- provider: {
74
- itemId: "fc_041cb...",
75
- },
76
- },
77
- }
52
+ await supportContext.react(triggerEvent, {
53
+ runtime,
54
+ context: { key: "support:org_123" },
55
+ });
78
56
  ```
79
57
 
80
- The AI SDK bridge projects canonical parts to:
58
+ Run durably:
81
59
 
82
- - assistant messages with text/file/reasoning/source/tool-call parts
83
- - tool messages with `tool-result` or `tool-error`
84
-
85
- That means multipart tool outputs are replayed from `event_parts` instead of relying on the deprecated output-item mirror.
86
-
87
- ## Install
60
+ ```ts
61
+ const shell = await supportContext.react(triggerEvent, {
62
+ runtime,
63
+ context: { key: "support:org_123" },
64
+ durable: true,
65
+ });
88
66
 
89
- ```bash
90
- pnpm add @ekairos/events
67
+ const final = await shell.run?.returnValue;
91
68
  ```
92
69
 
93
- ## Example
70
+ ## Tool execution model
94
71
 
95
- ```ts
96
- import { createContext, createAiSdkReactor } from "@ekairos/events";
97
-
98
- type Env = { orgId: string };
72
+ Context tools now receive runtime-aware execution context.
73
+ That lets a tool do this inside `"use step"`:
99
74
 
100
- export const supportContext = createContext<Env>("support.agent")
101
- .context((stored, env) => ({
102
- orgId: env.orgId,
103
- ...stored.content,
104
- }))
105
- .narrative(() => "You are a precise assistant.")
106
- .actions(() => ({}))
107
- .reactor(createAiSdkReactor())
108
- .build();
75
+ ```ts
76
+ async function execute(input, ctx) {
77
+ "use step";
78
+ const domain = await ctx.runtime.use(myDomain);
79
+ return await domain.actions.doSomething(input);
80
+ }
109
81
  ```
110
82
 
111
- ## Notes
83
+ ## Tests
112
84
 
113
- - Public continuity is context-based.
114
- - Provider-specific IDs such as `providerContextId` may still exist when an upstream provider requires them.
115
- - Runtime wiring for stores lives under `@ekairos/events/runtime`.
85
+ ```bash
86
+ pnpm --filter @ekairos/events test
87
+ pnpm --filter @ekairos/events test:workflow
88
+ ```
package/dist/context.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { ContextEngine, type ContextOptions, type ContextStreamOptions, type ShouldContinue, type ContextShouldContinueArgs, type ContextReactParams, type ContextReactResult, type ContextDurableWorkflowPayload, type ContextDurableWorkflowFunction, type ContextModelInit, type ContextTool, runContextReactionDirect, } from "./context.engine.js";
1
+ export { ContextEngine, type ContextOptions, type ContextStreamOptions, type ShouldContinue, type ContextShouldContinueArgs, type ContextReactParams, type ContextReactResult, type ContextWorkflowRun, type ContextDurableWorkflowPayload, type ContextDurableWorkflowFunction, type ContextModelInit, type ContextTool, type ContextToolExecuteContext, runContextReactionDirect, } from "./context.engine.js";
2
2
  export { context, createContext, type ContextConfig, type ContextInstance, type RegistrableContextBuilder, } from "./context.builder.js";
3
3
  export { createAiSdkReactor, createScriptedReactor, type CreateAiSdkReactorOptions, type CreateScriptedReactorOptions, type ScriptedReactorStep, type ContextReactor, type ContextReactorParams, type ContextReactionResult, type ContextActionRequest, type ContextReactionLLM, } from "./context.reactor.js";
4
4
  export type { ContextSkillPackage, ContextSkillPackageFile, } from "./context.skill.js";
@@ -1,5 +1,6 @@
1
- import type { Tool, UIMessageChunk } from "ai";
1
+ import type { ModelMessage, Tool, UIMessageChunk } from "ai";
2
2
  import type { ContextEnvironment } from "./context.config.js";
3
+ import type { ContextRuntime } from "./context.runtime.js";
3
4
  import type { ContextExecution, ContextItem, ContextIdentifier, StoredContext } from "./context.store.js";
4
5
  import type { ContextSkillPackage } from "./context.skill.js";
5
6
  import { type ContextReactor } from "./context.reactor.js";
@@ -68,7 +69,11 @@ export interface ContextStreamOptions {
68
69
  */
69
70
  export type ContextModelInit = string | (() => Promise<any>);
70
71
  export type ContextReactParams<Env extends ContextEnvironment = ContextEnvironment> = {
71
- env: Env;
72
+ runtime?: ContextRuntime<Env>;
73
+ /**
74
+ * Backward-compatible runtime selector. New callers should pass `runtime`.
75
+ */
76
+ env?: Env;
72
77
  /**
73
78
  * Context selector (exclusive: `{ id }` OR `{ key }`).
74
79
  * - `{ id }` resolves a concrete context id.
@@ -95,16 +100,36 @@ export type ContextReactResult<Context = any> = {
95
100
  trigger: ContextItem;
96
101
  reaction: ContextItem;
97
102
  execution: ContextExecution;
103
+ run?: ContextWorkflowRun<Context>;
104
+ };
105
+ export type ContextWorkflowRun<Context = any> = {
106
+ runId: string;
107
+ status: Promise<"pending" | "running" | "completed" | "failed" | "cancelled">;
108
+ returnValue: Promise<ContextReactResult<Context>>;
98
109
  };
99
110
  export type ContextDurableWorkflowPayload<Env extends ContextEnvironment = ContextEnvironment> = {
100
111
  contextKey: string;
101
- env: Env;
112
+ runtime: ContextRuntime<Env>;
102
113
  context?: ContextIdentifier | null;
103
114
  triggerEvent: ContextItem;
104
115
  options?: Omit<ContextStreamOptions, "writable">;
105
116
  bootstrap: NonNullable<ContextReactParams<Env>["__bootstrap"]>;
106
117
  };
107
118
  export type ContextDurableWorkflowFunction<Context = any, Env extends ContextEnvironment = ContextEnvironment> = (payload: ContextDurableWorkflowPayload<Env>) => Promise<ContextReactResult<Context>>;
119
+ export type ContextToolExecuteContext<Context = any, Env extends ContextEnvironment = ContextEnvironment> = {
120
+ runtime: ContextRuntime<Env>;
121
+ env: Env;
122
+ context: StoredContext<Context>;
123
+ contextIdentifier: ContextIdentifier;
124
+ toolCallId: string;
125
+ messages: ModelMessage[];
126
+ eventId: string;
127
+ executionId: string;
128
+ triggerEventId: string;
129
+ contextId: string;
130
+ stepId: string;
131
+ iteration: number;
132
+ };
108
133
  export { toolApprovalHookToken, toolApprovalWebhookToken, getClientResumeHookUrl };
109
134
  /**
110
135
  * Context-level tool type.
@@ -1,16 +1,40 @@
1
- import { registerContextEnv } from "./env.js";
1
+ import { getContextRuntimeServices } from "./context.runtime.js";
2
2
  import { OUTPUT_ITEM_TYPE, WEB_CHANNEL } from "./context.events.js";
3
3
  import { applyToolExecutionResultToParts } from "./context.toolcalls.js";
4
4
  import { isContextPartEnvelope, normalizePartsForPersistence, } from "./context.parts.js";
5
- import { toolsToModelTools } from "./tools-to-model-tools.js";
5
+ import { actionsToActionSpecs } from "./tools-to-model-tools.js";
6
6
  import { createAiSdkReactor, } from "./context.reactor.js";
7
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";
8
+ import { completeExecution, createContextStep, initializeContext, saveTriggerAndCreateExecution, saveContextPartsStep, updateContextContent, updateContextReactor, updateContextStatus, updateItem, updateContextStep, } from "./steps/store.steps.js";
9
9
  import { getClientResumeHookUrl, toolApprovalHookToken, toolApprovalWebhookToken, } from "./context.hooks.js";
10
10
  import { getContextDurableWorkflow } from "./context.durable.js";
11
11
  export async function runContextReactionDirect(context, triggerEvent, params) {
12
12
  return await ContextEngine.runDirect(context, triggerEvent, params);
13
13
  }
14
+ async function legacyRuntimeFromEnv(env) {
15
+ const { getContextRuntime } = await import("./runtime.step.js");
16
+ const legacy = (await getContextRuntime(env));
17
+ return {
18
+ env,
19
+ db: async () => legacy.db,
20
+ resolve: async () => ({
21
+ db: legacy.db,
22
+ meta: () => ({
23
+ domain: legacy.domain,
24
+ }),
25
+ }),
26
+ meta: () => ({
27
+ domain: legacy.domain,
28
+ }),
29
+ };
30
+ }
31
+ async function resolveReactRuntime(params) {
32
+ if (params.runtime)
33
+ return params.runtime;
34
+ if (params.env)
35
+ return await legacyRuntimeFromEnv(params.env);
36
+ throw new Error("ContextEngine.react requires either runtime or env.");
37
+ }
14
38
  export { toolApprovalHookToken, toolApprovalWebhookToken, getClientResumeHookUrl };
15
39
  function nowIso() {
16
40
  return new Date().toISOString();
@@ -89,9 +113,8 @@ async function readActiveWorkflowRunId() {
89
113
  return null;
90
114
  }
91
115
  }
92
- async function createRuntimeOps(env, benchmark) {
93
- const { getContextRuntime } = await import("./runtime.js");
94
- const runtime = await getContextRuntime(env);
116
+ async function createRuntimeOps(runtimeHandle, benchmark) {
117
+ const runtime = await getContextRuntimeServices(runtimeHandle);
95
118
  const { db } = runtime;
96
119
  const { InstantStore } = await import("./stores/instant.store.js");
97
120
  const requireContextId = (contextIdentifier) => {
@@ -145,6 +168,7 @@ async function createRuntimeOps(env, benchmark) {
145
168
  return { context, isNew: true };
146
169
  },
147
170
  updateContextContent: async (contextIdentifier, content) => await store.updateContextContent(contextIdentifier, content),
171
+ updateContextReactor: async (contextIdentifier, reactor) => await store.updateContextReactor(contextIdentifier, reactor),
148
172
  updateContextStatus: async (contextIdentifier, status) => await instrumentedDb.transact([
149
173
  instrumentedDb.tx.event_contexts[requireContextId(contextIdentifier)].update({
150
174
  status,
@@ -255,27 +279,28 @@ async function createRuntimeOps(env, benchmark) {
255
279
  },
256
280
  };
257
281
  }
258
- async function createWorkflowOps(env) {
282
+ async function createWorkflowOps(runtime) {
283
+ const env = runtime.env;
259
284
  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),
285
+ initializeContext: async (contextIdentifier, opts) => await initializeContext({ runtime, contextIdentifier, opts }),
286
+ updateContextContent: async (contextIdentifier, content) => await updateContextContent({ runtime, contextIdentifier, content }),
287
+ updateContextReactor: async (contextIdentifier, reactor) => await updateContextReactor({ runtime, contextIdentifier, reactor }),
288
+ updateContextStatus: async (contextIdentifier, status) => await updateContextStatus({ runtime, contextIdentifier, status }),
289
+ saveTriggerAndCreateExecution: async ({ contextIdentifier, triggerEvent }) => await saveTriggerAndCreateExecution({ runtime, contextIdentifier, triggerEvent }),
290
+ createContextStep: async ({ executionId, iteration }) => await createContextStep({ runtime, executionId, iteration }),
291
+ updateContextStep: async (params) => await updateContextStep({ runtime, ...params }),
292
+ saveContextPartsStep: async (params) => await saveContextPartsStep({ runtime, ...params }),
293
+ updateItem: async (itemId, item, opts) => await updateItem({ runtime, eventId: itemId, event: item, opts }),
294
+ completeExecution: async (contextIdentifier, executionId, status) => await completeExecution({ runtime, contextIdentifier, executionId, status }),
269
295
  };
270
296
  }
271
- async function getContextEngineOps(env, benchmark) {
297
+ async function getContextEngineOps(runtime, benchmark) {
298
+ const env = runtime.env;
272
299
  const workflowRunId = await readActiveWorkflowRunId();
273
300
  if (workflowRunId) {
274
- registerContextEnv(env, workflowRunId);
275
- return await createWorkflowOps(env);
301
+ return await createWorkflowOps(runtime);
276
302
  }
277
- registerContextEnv(env);
278
- return await createRuntimeOps(env, benchmark);
303
+ return await createRuntimeOps(runtime, benchmark);
279
304
  }
280
305
  export class ContextEngine {
281
306
  constructor(opts = {}, reactor) {
@@ -329,13 +354,15 @@ export class ContextEngine {
329
354
  return await ContextEngine.runDirect(this, triggerEvent, params);
330
355
  }
331
356
  static async prepareExecutionShell(story, triggerEvent, params) {
332
- const ops = await measureBenchmark(params.__benchmark, "react.resolveOpsMs", async () => await getContextEngineOps(params.env, params.__benchmark));
357
+ const runtimeHandle = await resolveReactRuntime(params);
358
+ const env = runtimeHandle.env;
359
+ const ops = await measureBenchmark(params.__benchmark, "react.resolveOpsMs", async () => await getContextEngineOps(runtimeHandle, params.__benchmark));
333
360
  const silent = params.options?.silent ?? false;
334
361
  const ctxResult = await measureBenchmark(params.__benchmark, "react.initializeContextMs", async () => await ops.initializeContext(params.context ?? null, { silent }));
335
362
  let currentContext = ctxResult.context;
336
363
  const contextSelector = { id: String(currentContext.id) };
337
364
  if (ctxResult.isNew) {
338
- await story.opts.onContextCreated?.({ env: params.env, context: currentContext });
365
+ await story.opts.onContextCreated?.({ env, context: currentContext });
339
366
  }
340
367
  if (currentContext.status === "closed") {
341
368
  await measureBenchmark(params.__benchmark, "react.reopenClosedContextMs", async () => await ops.updateContextStatus(contextSelector, "open_idle"));
@@ -355,6 +382,8 @@ export class ContextEngine {
355
382
  };
356
383
  }
357
384
  static async startDurable(story, triggerEvent, params) {
385
+ const runtimeHandle = await resolveReactRuntime(params);
386
+ const env = runtimeHandle.env;
358
387
  if (params.options?.writable) {
359
388
  throw new Error("ContextEngine.react: durable runs manage their own workflow stream");
360
389
  }
@@ -367,14 +396,15 @@ export class ContextEngine {
367
396
  throw new Error("ContextEngine.react: durable workflow is not configured. Call configureContextDurableWorkflow(...) in runtime bootstrap.");
368
397
  }
369
398
  const shell = await ContextEngine.prepareExecutionShell(story, triggerEvent, params);
399
+ let run;
370
400
  try {
371
401
  const [{ start }] = await Promise.all([
372
402
  import("workflow/api"),
373
403
  ]);
374
- const run = await start(workflow, [
404
+ const startedRun = await start(workflow, [
375
405
  {
376
406
  contextKey,
377
- env: params.env,
407
+ runtime: runtimeHandle,
378
408
  context: params.context ?? null,
379
409
  triggerEvent,
380
410
  options: {
@@ -392,16 +422,21 @@ export class ContextEngine {
392
422
  },
393
423
  },
394
424
  ]);
395
- const runtime = await createRuntimeOps(params.env);
425
+ run = {
426
+ runId: String(startedRun.runId),
427
+ status: startedRun.status,
428
+ returnValue: startedRun.returnValue,
429
+ };
430
+ const runtime = await createRuntimeOps(runtimeHandle);
396
431
  await runtime.db.transact([
397
432
  runtime.db.tx.event_executions[shell.execution.id].update({
398
- workflowRunId: run.runId,
433
+ workflowRunId: startedRun.runId,
399
434
  updatedAt: new Date(),
400
435
  }),
401
436
  ]);
402
437
  }
403
438
  catch (error) {
404
- const ops = await getContextEngineOps(params.env, params.__benchmark);
439
+ const ops = await getContextEngineOps(runtimeHandle, params.__benchmark);
405
440
  await ops.completeExecution(shell.contextSelector, shell.execution.id, "failed").catch(() => null);
406
441
  throw error;
407
442
  }
@@ -410,10 +445,13 @@ export class ContextEngine {
410
445
  trigger: shell.trigger,
411
446
  reaction: shell.reaction,
412
447
  execution: shell.execution,
448
+ run,
413
449
  };
414
450
  }
415
451
  static async runDirect(story, triggerEvent, params) {
416
- const ops = await measureBenchmark(params.__benchmark, "react.resolveOpsMs", async () => await getContextEngineOps(params.env, params.__benchmark));
452
+ const runtimeHandle = await resolveReactRuntime(params);
453
+ const env = runtimeHandle.env;
454
+ const ops = await measureBenchmark(params.__benchmark, "react.resolveOpsMs", async () => await getContextEngineOps(runtimeHandle, params.__benchmark));
417
455
  const maxIterations = params.options?.maxIterations ?? 20;
418
456
  const maxModelSteps = params.options?.maxModelSteps ?? 1;
419
457
  const preventClose = params.options?.preventClose ?? false;
@@ -488,7 +526,7 @@ export class ContextEngine {
488
526
  }));
489
527
  currentStepId = stepCreate.stepId;
490
528
  currentStepStream = await createPersistedContextStepStream({
491
- env: params.env,
529
+ runtime: runtimeHandle,
492
530
  executionId,
493
531
  stepId: stepCreate.stepId,
494
532
  });
@@ -507,7 +545,7 @@ export class ContextEngine {
507
545
  ],
508
546
  });
509
547
  // 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));
548
+ const nextContent = await measureBenchmark(params.__benchmark, `${stagePrefix}.contextMs`, async () => await story.initialize(updatedContext, env));
511
549
  updatedContext = await measureBenchmark(params.__benchmark, `${stagePrefix}.persistContextMs`, async () => await ops.updateContextContent(activeContextSelector, nextContent));
512
550
  await emitContextEvents({
513
551
  silent,
@@ -520,15 +558,15 @@ export class ContextEngine {
520
558
  },
521
559
  ],
522
560
  });
523
- await story.opts.onContextUpdated?.({ env: params.env, context: updatedContext });
561
+ await story.opts.onContextUpdated?.({ env, context: updatedContext });
524
562
  // 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));
563
+ const systemPrompt = await measureBenchmark(params.__benchmark, `${stagePrefix}.narrativeMs`, async () => await story.buildSystemPrompt(updatedContext, env));
526
564
  // 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));
565
+ const toolsAll = await measureBenchmark(params.__benchmark, `${stagePrefix}.actionsMs`, async () => await story.buildTools(updatedContext, env));
566
+ const skillsAll = await measureBenchmark(params.__benchmark, `${stagePrefix}.skillsMs`, async () => await story.buildSkills(updatedContext, env));
529
567
  // IMPORTANT: step args must be serializable.
530
568
  // Match DurableAgent behavior: convert tool input schemas to plain JSON Schema in workflow context.
531
- const toolsForModel = toolsToModelTools(toolsAll);
569
+ const actionSpecs = actionsToActionSpecs(toolsAll);
532
570
  // Execute model reaction for this iteration using the stable reaction event id.
533
571
  //
534
572
  // IMPORTANT:
@@ -536,7 +574,7 @@ export class ContextEngine {
536
574
  // If we stream with a per-step id, the UI will render an optimistic assistant message
537
575
  // (step id) and then a second persisted assistant message (reaction id) with the same
538
576
  // content once InstantDB updates.
539
- const reactor = story.getReactor(updatedContext, params.env);
577
+ const reactor = story.getReactor(updatedContext, env);
540
578
  const reactionPartsBeforeStep = Array.isArray(reactionEvent.content?.parts)
541
579
  ? [...reactionEvent.content.parts]
542
580
  : [];
@@ -563,15 +601,16 @@ export class ContextEngine {
563
601
  status: "pending",
564
602
  }, { executionId, contextId: String(currentContext.id) });
565
603
  };
566
- const { assistantEvent, actionRequests, messagesForModel } = await measureBenchmark(params.__benchmark, `${stagePrefix}.reactorMs`, async () => await reactor({
567
- env: params.env,
604
+ const reactionResult = await measureBenchmark(params.__benchmark, `${stagePrefix}.reactorMs`, async () => await reactor({
605
+ runtime: runtimeHandle,
606
+ env,
568
607
  context: updatedContext,
569
608
  contextIdentifier: activeContextSelector,
570
609
  triggerEvent,
571
- model: story.getModel(updatedContext, params.env),
610
+ model: story.getModel(updatedContext, env),
572
611
  systemPrompt,
573
612
  actions: toolsAll,
574
- toolsForModel,
613
+ actionSpecs,
575
614
  skills: skillsAll,
576
615
  eventId: reactionEventId,
577
616
  executionId,
@@ -586,6 +625,7 @@ export class ContextEngine {
586
625
  writable,
587
626
  persistReactionParts,
588
627
  }));
628
+ const { assistantEvent, actionRequests, messagesForModel } = reactionResult;
589
629
  const reviewRequests = actionRequests.length > 0
590
630
  ? actionRequests.flatMap((actionRequest) => {
591
631
  const toolDef = toolsAll[actionRequest.actionName];
@@ -649,9 +689,18 @@ export class ContextEngine {
649
689
  status: "pending",
650
690
  };
651
691
  reactionEvent = await measureBenchmark(params.__benchmark, `${stagePrefix}.persistAssistantReactionMs`, async () => await ops.updateItem(reactionEvent.id, nextReactionEvent, { executionId, contextId: String(currentContext.id) }));
692
+ if (reactionResult.reactor?.kind) {
693
+ updatedContext = await measureBenchmark(params.__benchmark, `${stagePrefix}.persistReactorStateMs`, async () => await ops.updateContextReactor(activeContextSelector, {
694
+ kind: reactionResult.reactor.kind,
695
+ state: {
696
+ ...(reactionResult.reactor.state ?? {}),
697
+ updatedAt: nowIso(),
698
+ },
699
+ }));
700
+ }
652
701
  if (currentStepStream) {
653
702
  await closePersistedContextStepStream({
654
- env: params.env,
703
+ runtime: runtimeHandle,
655
704
  session: currentStepStream,
656
705
  });
657
706
  currentStepStream = null;
@@ -807,9 +856,8 @@ export class ContextEngine {
807
856
  const { createHook, createWebhook } = await import("workflow");
808
857
  const toolCallId = String(actionRequest.actionRef);
809
858
  const hookToken = toolApprovalHookToken({ executionId, toolCallId });
810
- const webhookToken = toolApprovalWebhookToken({ executionId, toolCallId });
811
859
  const hook = createHook({ token: hookToken });
812
- const webhook = createWebhook({ token: webhookToken });
860
+ const webhook = createWebhook();
813
861
  const approvalOrRequest = await Promise.race([
814
862
  hook.then((approval) => ({ source: "hook", approval })),
815
863
  webhook.then((request) => ({ source: "webhook", request })),
@@ -832,12 +880,18 @@ export class ContextEngine {
832
880
  }
833
881
  }
834
882
  const output = await toolDef.execute(actionInput, {
883
+ runtime: runtimeHandle,
884
+ env,
885
+ context: updatedContext,
886
+ contextIdentifier: activeContextSelector,
835
887
  toolCallId: actionRequest.actionRef,
836
888
  messages: messagesForModel,
837
889
  eventId: reactionEventId,
838
890
  executionId,
839
891
  triggerEventId,
840
892
  contextId: currentContext.id,
893
+ stepId: String(stepCreate.stepId),
894
+ iteration: iter,
841
895
  });
842
896
  return { actionRequest, success: true, output };
843
897
  }
@@ -894,7 +948,7 @@ export class ContextEngine {
894
948
  // IMPORTANT: we call this after tool results have been merged into the persisted `reactionEvent`,
895
949
  // so stories can inspect `reactionEvent.content.parts` deterministically.
896
950
  const continueLoop = await measureBenchmark(params.__benchmark, `${stagePrefix}.shouldContinueMs`, async () => await story.shouldContinue({
897
- env: params.env,
951
+ env,
898
952
  context: updatedContext,
899
953
  reactionEvent,
900
954
  assistantEvent: assistantEventEffective,
@@ -1033,7 +1087,7 @@ export class ContextEngine {
1033
1087
  if (currentStepStream) {
1034
1088
  try {
1035
1089
  await abortPersistedContextStepStream({
1036
- env: params.env,
1090
+ runtime: runtimeHandle,
1037
1091
  session: currentStepStream,
1038
1092
  reason: error instanceof Error ? error.message : String(error),
1039
1093
  });
@@ -0,0 +1,11 @@
1
+ import type { ConcreteDomain } from "@ekairos/domain";
2
+ import type { ExplicitRuntimeLike } from "@ekairos/domain/runtime";
3
+ import type { ContextEnvironment } from "./context.config.js";
4
+ import type { ContextStore } from "./context.store.js";
5
+ export type ContextRuntime<Env extends ContextEnvironment = ContextEnvironment> = ExplicitRuntimeLike<Env, any, any>;
6
+ export type ContextRuntimeServices = {
7
+ db: any;
8
+ store: ContextStore;
9
+ domain?: ConcreteDomain<any, any>;
10
+ };
11
+ export declare function getContextRuntimeServices(runtime: ContextRuntime<any>): Promise<ContextRuntimeServices>;
@@ -0,0 +1,21 @@
1
+ const storeByDb = new WeakMap();
2
+ export async function getContextRuntimeServices(runtime) {
3
+ const db = await runtime.db();
4
+ if (!db) {
5
+ throw new Error("Context runtime did not provide a database instance.");
6
+ }
7
+ let store = typeof db === "object" && db !== null ? storeByDb.get(db) : undefined;
8
+ if (!store) {
9
+ const { InstantStore } = await import("./stores/instant.store.js");
10
+ store = new InstantStore(db);
11
+ if (typeof db === "object" && db !== null) {
12
+ storeByDb.set(db, store);
13
+ }
14
+ }
15
+ const resolved = await runtime.resolve();
16
+ return {
17
+ db,
18
+ store,
19
+ domain: typeof resolved === "object" && resolved !== null ? resolved.meta?.()?.domain : undefined,
20
+ };
21
+ }
@@ -16,6 +16,10 @@ export type StoredContext<Context> = {
16
16
  createdAt: Date;
17
17
  updatedAt?: Date;
18
18
  content: Context | null;
19
+ reactor?: {
20
+ kind: string;
21
+ state?: Record<string, unknown> | null;
22
+ } | null;
19
23
  };
20
24
  export type ContextItem = {
21
25
  id: string;
@@ -57,6 +61,10 @@ export interface ContextStore {
57
61
  getOrCreateContext<C>(contextIdentifier: ContextIdentifier | null): Promise<StoredContext<C>>;
58
62
  getContext<C>(contextIdentifier: ContextIdentifier): Promise<StoredContext<C> | null>;
59
63
  updateContextContent<C>(contextIdentifier: ContextIdentifier, content: C): Promise<StoredContext<C>>;
64
+ updateContextReactor<C>(contextIdentifier: ContextIdentifier, reactor: {
65
+ kind: string;
66
+ state?: Record<string, unknown> | null;
67
+ }): Promise<StoredContext<C>>;
60
68
  updateContextStatus(contextIdentifier: ContextIdentifier, status: ContextStatus): Promise<void>;
61
69
  saveItem(contextIdentifier: ContextIdentifier, item: ContextItem): Promise<ContextItem>;
62
70
  updateItem(itemId: string, item: ContextItem): Promise<ContextItem>;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { context, createContext, createAiSdkReactor, createScriptedReactor, type CreateAiSdkReactorOptions, type CreateScriptedReactorOptions, type ScriptedReactorStep, type ContextConfig, type ContextInstance, type ContextOptions, type ContextStreamOptions, type ContextReactor, type ContextReactorParams, type ContextReactionResult, type ContextActionRequest, type ContextReactionLLM, ContextEngine, type RegistrableContextBuilder, type ContextReactParams, type ContextReactResult, type ContextDurableWorkflowPayload, type ContextDurableWorkflowFunction, type ContextModelInit, type ContextTool, runContextReactionDirect, } from "./context.js";
1
+ export { context, createContext, createAiSdkReactor, createScriptedReactor, type CreateAiSdkReactorOptions, type CreateScriptedReactorOptions, type ScriptedReactorStep, type ContextConfig, type ContextInstance, type ContextOptions, type ContextStreamOptions, type ContextReactor, type ContextReactorParams, type ContextReactionResult, type ContextActionRequest, type ContextReactionLLM, ContextEngine, type RegistrableContextBuilder, type ContextReactParams, type ContextReactResult, type ContextWorkflowRun, type ContextDurableWorkflowPayload, type ContextDurableWorkflowFunction, type ContextModelInit, type ContextTool, type ContextToolExecuteContext, runContextReactionDirect, } from "./context.js";
2
2
  export type { ContextStore, ContextIdentifier, StoredContext, ContextItem, ContextExecution, } from "./context.store.js";
3
3
  export type { WireDate, ContextMirrorContext, ContextMirrorExecution, ContextMirrorWrite, ContextMirrorRequest, } from "./mirror.js";
4
4
  export { registerContext, getContext, getContextFactory, hasContext, listContexts, type ContextKey, } from "./context.registry.js";