@ekairos/events 1.22.30-beta.development.0 → 1.22.31-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.
@@ -1,4 +1,5 @@
1
1
  import type { ContextEnvironment } from "./context.config.js";
2
+ import type { ContextSkillPackage } from "./context.skill.js";
2
3
  import { ContextEngine, type ContextModelInit, type ContextOptions, type ContextTool, type ShouldContinue, type ContextShouldContinueArgs, type ContextReactParams } from "./context.engine.js";
3
4
  import type { ContextReactor } from "./context.reactor.js";
4
5
  import type { ContextItem, StoredContext } from "./context.store.js";
@@ -7,6 +8,7 @@ export interface ContextConfig<Context, Env extends ContextEnvironment = Context
7
8
  context: (context: StoredContext<Context>, env: Env) => Promise<Context> | Context;
8
9
  expandEvents?: (events: ContextItem[], context: StoredContext<Context>, env: Env) => Promise<ContextItem[]> | ContextItem[];
9
10
  narrative: (context: StoredContext<Context>, env: Env) => Promise<string> | string;
11
+ skills?: (context: StoredContext<Context>, env: Env) => Promise<ContextSkillPackage[]> | ContextSkillPackage[];
10
12
  actions: (context: StoredContext<Context>, env: Env) => Promise<Record<string, ContextTool>> | Record<string, ContextTool>;
11
13
  /**
12
14
  * @deprecated Use `actions()` instead.
@@ -25,6 +27,7 @@ export declare function context<Context, Env extends ContextEnvironment = Contex
25
27
  type AnyContextInitializer<Env extends ContextEnvironment> = (context: StoredContext<any>, env: Env) => Promise<any> | any;
26
28
  type InferContextFromInitializer<I extends AnyContextInitializer<any>> = Awaited<ReturnType<I>>;
27
29
  type BuilderSystemPrompt<Context, Env extends ContextEnvironment> = (context: StoredContext<Context>, env: Env) => Promise<string> | string;
30
+ type BuilderSkills<Context, Env extends ContextEnvironment> = (context: StoredContext<Context>, env: Env) => Promise<ContextSkillPackage[]> | ContextSkillPackage[];
28
31
  type BuilderTools<Context, Env extends ContextEnvironment> = (context: StoredContext<Context>, env: Env) => Promise<Record<string, ContextTool>> | Record<string, ContextTool>;
29
32
  type BuilderExpandEvents<Context, Env extends ContextEnvironment> = (events: ContextItem[], context: StoredContext<Context>, env: Env) => Promise<ContextItem[]> | ContextItem[];
30
33
  type BuilderShouldContinue<Context, Env extends ContextEnvironment> = (args: ContextShouldContinueArgs<Context, Env>) => Promise<ShouldContinue> | ShouldContinue;
@@ -38,6 +41,7 @@ type FluentContextBuilder<Context, Env extends ContextEnvironment> = {
38
41
  expandEvents(fn: BuilderExpandEvents<Context, Env>): FluentContextBuilder<Context, Env>;
39
42
  narrative(fn: BuilderSystemPrompt<Context, Env>): FluentContextBuilder<Context, Env>;
40
43
  system(fn: BuilderSystemPrompt<Context, Env>): FluentContextBuilder<Context, Env>;
44
+ skills(fn: BuilderSkills<Context, Env>): FluentContextBuilder<Context, Env>;
41
45
  actions(fn: BuilderTools<Context, Env>): FluentContextBuilder<Context, Env>;
42
46
  tools(fn: BuilderTools<Context, Env>): FluentContextBuilder<Context, Env>;
43
47
  model(model: BuilderModel<Context, Env>): FluentContextBuilder<Context, Env>;
@@ -22,6 +22,11 @@ export function context(config) {
22
22
  return config.narrative(contextValue, env);
23
23
  throw new Error("Context config is missing narrative()");
24
24
  }
25
+ async buildSkills(contextValue, env) {
26
+ if (config.skills)
27
+ return config.skills(contextValue, env);
28
+ return [];
29
+ }
25
30
  async buildTools(contextValue, env) {
26
31
  if (config.actions)
27
32
  return config.actions(contextValue, env);
@@ -84,6 +89,10 @@ export function createContext(key) {
84
89
  fluentState.narrative = system;
85
90
  return builder;
86
91
  },
92
+ skills(skillsFactory) {
93
+ fluentState.skills = skillsFactory;
94
+ return builder;
95
+ },
87
96
  actions(actionsFactory) {
88
97
  fluentState.actions = actionsFactory;
89
98
  return builder;
package/dist/context.d.ts CHANGED
@@ -1,3 +1,4 @@
1
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";
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
+ export type { ContextSkillPackage, ContextSkillPackageFile, } from "./context.skill.js";
@@ -1,6 +1,7 @@
1
1
  import type { Tool, UIMessageChunk } from "ai";
2
2
  import type { ContextEnvironment } from "./context.config.js";
3
3
  import type { ContextExecution, ContextItem, ContextIdentifier, StoredContext } from "./context.store.js";
4
+ import type { ContextSkillPackage } from "./context.skill.js";
4
5
  import { type ContextReactor } from "./context.reactor.js";
5
6
  import { getClientResumeHookUrl, toolApprovalHookToken, toolApprovalWebhookToken } from "./context.hooks.js";
6
7
  export interface ContextOptions<Context = any, Env extends ContextEnvironment = ContextEnvironment> {
@@ -168,6 +169,7 @@ export declare abstract class ContextEngine<Context, Env extends ContextEnvironm
168
169
  protected abstract initialize(context: StoredContext<Context>, env: Env): Promise<Context> | Context;
169
170
  protected abstract buildSystemPrompt(context: StoredContext<Context>, env: Env): Promise<string> | string;
170
171
  protected abstract buildTools(context: StoredContext<Context>, env: Env): Promise<Record<string, ContextTool>> | Record<string, ContextTool>;
172
+ protected buildSkills(_context: StoredContext<Context>, _env: Env): Promise<ContextSkillPackage[]>;
171
173
  /**
172
174
  * First-class event expansion stage (runs on every iteration of the durable loop).
173
175
  *
@@ -3,7 +3,7 @@ import { OUTPUT_ITEM_TYPE, WEB_CHANNEL } from "./context.events.js";
3
3
  import { applyToolExecutionResultToParts } from "./context.toolcalls.js";
4
4
  import { toolsToModelTools } from "./tools-to-model-tools.js";
5
5
  import { createAiSdkReactor, } from "./context.reactor.js";
6
- import { closeContextStream, } from "./steps/stream.steps.js";
6
+ import { abortPersistedContextStepStream, closePersistedContextStepStream, createPersistedContextStepStream, closeContextStream, } from "./steps/stream.steps.js";
7
7
  import { completeExecution, createContextStep, initializeContext, saveTriggerAndCreateExecution, saveContextPartsStep, updateContextContent, updateContextStatus, updateItem, updateContextStep, } from "./steps/store.steps.js";
8
8
  import { getClientResumeHookUrl, toolApprovalHookToken, toolApprovalWebhookToken, } from "./context.hooks.js";
9
9
  import { getContextDurableWorkflow } from "./context.durable.js";
@@ -267,6 +267,9 @@ export class ContextEngine {
267
267
  this.opts = opts;
268
268
  this.reactor = reactor ?? createAiSdkReactor();
269
269
  }
270
+ async buildSkills(_context, _env) {
271
+ return [];
272
+ }
270
273
  /**
271
274
  * First-class event expansion stage (runs on every iteration of the durable loop).
272
275
  *
@@ -422,6 +425,7 @@ export class ContextEngine {
422
425
  const executionId = execution.id;
423
426
  let updatedContext = { ...currentContext, status: "open_streaming" };
424
427
  let currentStepId = null;
428
+ let currentStepStream = null;
425
429
  const failExecution = async () => {
426
430
  try {
427
431
  await ops.completeExecution(activeContextSelector, executionId, "failed");
@@ -468,6 +472,11 @@ export class ContextEngine {
468
472
  iteration: iter,
469
473
  }));
470
474
  currentStepId = stepCreate.stepId;
475
+ currentStepStream = await createPersistedContextStepStream({
476
+ env: params.env,
477
+ executionId,
478
+ stepId: stepCreate.stepId,
479
+ });
471
480
  await emitContextEvents({
472
481
  silent,
473
482
  writable,
@@ -501,6 +510,7 @@ export class ContextEngine {
501
510
  const systemPrompt = await measureBenchmark(params.__benchmark, `${stagePrefix}.narrativeMs`, async () => await story.buildSystemPrompt(updatedContext, params.env));
502
511
  // Hook: Context DSL `actions()` (implemented by subclasses via `buildTools()`)
503
512
  const toolsAll = await measureBenchmark(params.__benchmark, `${stagePrefix}.actionsMs`, async () => await story.buildTools(updatedContext, params.env));
513
+ const skillsAll = await measureBenchmark(params.__benchmark, `${stagePrefix}.skillsMs`, async () => await story.buildSkills(updatedContext, params.env));
504
514
  // IMPORTANT: step args must be serializable.
505
515
  // Match DurableAgent behavior: convert tool input schemas to plain JSON Schema in workflow context.
506
516
  const toolsForModel = toolsToModelTools(toolsAll);
@@ -547,6 +557,7 @@ export class ContextEngine {
547
557
  systemPrompt,
548
558
  actions: toolsAll,
549
559
  toolsForModel,
560
+ skills: skillsAll,
550
561
  eventId: reactionEventId,
551
562
  executionId,
552
563
  contextId: String(currentContext.id),
@@ -556,6 +567,7 @@ export class ContextEngine {
556
567
  // Only emit a `start` chunk once per story turn.
557
568
  sendStart: !silent && iter === 0,
558
569
  silent,
570
+ contextStepStream: currentStepStream?.stream,
559
571
  writable,
560
572
  persistReactionParts,
561
573
  }));
@@ -622,6 +634,13 @@ export class ContextEngine {
622
634
  status: "pending",
623
635
  };
624
636
  reactionEvent = await measureBenchmark(params.__benchmark, `${stagePrefix}.persistAssistantReactionMs`, async () => await ops.updateItem(reactionEvent.id, nextReactionEvent, { executionId, contextId: String(currentContext.id) }));
637
+ if (currentStepStream) {
638
+ await closePersistedContextStepStream({
639
+ env: params.env,
640
+ session: currentStepStream,
641
+ });
642
+ currentStepStream = null;
643
+ }
625
644
  story.opts.onEventCreated?.(assistantEventEffective);
626
645
  const firstActionRequest = actionRequests?.[0];
627
646
  await measureBenchmark(params.__benchmark, `${stagePrefix}.markStepRunningMs`, async () => await ops.updateContextStep({
@@ -989,6 +1008,21 @@ export class ContextEngine {
989
1008
  throw new Error(`ContextEngine: maxIterations reached (${maxIterations}) without completion`);
990
1009
  }
991
1010
  catch (error) {
1011
+ if (currentStepStream) {
1012
+ try {
1013
+ await abortPersistedContextStepStream({
1014
+ env: params.env,
1015
+ session: currentStepStream,
1016
+ reason: error instanceof Error ? error.message : String(error),
1017
+ });
1018
+ }
1019
+ catch {
1020
+ // noop
1021
+ }
1022
+ finally {
1023
+ currentStepStream = null;
1024
+ }
1025
+ }
992
1026
  // Best-effort: persist failure on the current iteration step (if any)
993
1027
  if (currentStepId) {
994
1028
  const failedStepId = currentStepId;
@@ -0,0 +1,9 @@
1
+ export type ContextSkillPackageFile = {
2
+ path: string;
3
+ contentBase64: string;
4
+ };
5
+ export type ContextSkillPackage = {
6
+ name: string;
7
+ description?: string;
8
+ files: ContextSkillPackageFile[];
9
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ import { type ContextStreamChunkType } from "./context.contract.js";
2
+ export declare const CONTEXT_STEP_STREAM_VERSION: 1;
3
+ export type ContextStepStreamChunk = {
4
+ version: typeof CONTEXT_STEP_STREAM_VERSION;
5
+ at: string;
6
+ sequence: number;
7
+ chunkType: ContextStreamChunkType;
8
+ provider?: string;
9
+ providerChunkType?: string;
10
+ actionRef?: string;
11
+ data?: unknown;
12
+ raw?: unknown;
13
+ };
14
+ export declare function createContextStepStreamChunk(params: {
15
+ at?: string;
16
+ sequence: number;
17
+ chunkType: ContextStreamChunkType;
18
+ provider?: string;
19
+ providerChunkType?: string;
20
+ actionRef?: string;
21
+ data?: unknown;
22
+ raw?: unknown;
23
+ }): ContextStepStreamChunk;
24
+ export declare function parseContextStepStreamChunk(value: string | unknown): ContextStepStreamChunk;
25
+ export declare function encodeContextStepStreamChunk(chunk: ContextStepStreamChunk): string;
26
+ export declare function contextStreamByteLength(value: string): number;
@@ -0,0 +1,59 @@
1
+ import { isContextStreamChunkType, } from "./context.contract.js";
2
+ export const CONTEXT_STEP_STREAM_VERSION = 1;
3
+ function assertObject(value, label) {
4
+ if (!value || typeof value !== "object") {
5
+ throw new Error(`Invalid ${label}: expected object.`);
6
+ }
7
+ }
8
+ function assertString(value, label) {
9
+ if (typeof value !== "string" || value.length === 0) {
10
+ throw new Error(`Invalid ${label}: expected non-empty string.`);
11
+ }
12
+ }
13
+ function assertNumber(value, label) {
14
+ if (typeof value !== "number" || Number.isNaN(value)) {
15
+ throw new Error(`Invalid ${label}: expected number.`);
16
+ }
17
+ }
18
+ function assertOptionalString(value, label) {
19
+ if (typeof value === "undefined")
20
+ return;
21
+ assertString(value, label);
22
+ }
23
+ export function createContextStepStreamChunk(params) {
24
+ return {
25
+ version: CONTEXT_STEP_STREAM_VERSION,
26
+ at: params.at ?? new Date().toISOString(),
27
+ sequence: params.sequence,
28
+ chunkType: params.chunkType,
29
+ provider: params.provider,
30
+ providerChunkType: params.providerChunkType,
31
+ actionRef: params.actionRef,
32
+ data: params.data,
33
+ raw: params.raw,
34
+ };
35
+ }
36
+ export function parseContextStepStreamChunk(value) {
37
+ const parsed = typeof value === "string" ? JSON.parse(value) : value;
38
+ assertObject(parsed, "context step stream chunk");
39
+ assertNumber(parsed.version, "context step stream chunk.version");
40
+ if (parsed.version !== CONTEXT_STEP_STREAM_VERSION) {
41
+ throw new Error(`Unsupported context step stream chunk version: ${String(parsed.version)}`);
42
+ }
43
+ assertString(parsed.at, "context step stream chunk.at");
44
+ assertNumber(parsed.sequence, "context step stream chunk.sequence");
45
+ assertString(parsed.chunkType, "context step stream chunk.chunkType");
46
+ if (!isContextStreamChunkType(parsed.chunkType)) {
47
+ throw new Error(`Invalid context step stream chunk.chunkType: ${String(parsed.chunkType)}`);
48
+ }
49
+ assertOptionalString(parsed.provider, "context step stream chunk.provider");
50
+ assertOptionalString(parsed.providerChunkType, "context step stream chunk.providerChunkType");
51
+ assertOptionalString(parsed.actionRef, "context step stream chunk.actionRef");
52
+ return parsed;
53
+ }
54
+ export function encodeContextStepStreamChunk(chunk) {
55
+ return `${JSON.stringify(chunk)}\n`;
56
+ }
57
+ export function contextStreamByteLength(value) {
58
+ return new TextEncoder().encode(value).length;
59
+ }
package/dist/index.d.ts CHANGED
@@ -10,4 +10,7 @@ export type { Transition, ContextStatus, ExecutionStatus, StepStatus, StepKind,
10
10
  export { DEFAULT_CODEX_TOOL_NAME, DEFAULT_CODEX_MODEL, codexToolInputSchema, buildDefaultCodexNarrative, didCodexToolExecute, createCodexContextBuilder, type CodexContextRuntimeMode, type CodexContextReasoningLevel, type CodexContextRuntime, type CodexContextEnv, type CodexToolInput, type CodexToolOutput, type CodexExecuteArgs, type CodexContextBuilderConfig, type CodexContextBuilder, } from "./codex.js";
11
11
  export { useContext, type ContextSnapshot, type ContextStreamChunk, type UseContextOptions, } from "./react.js";
12
12
  export { parseContextStreamEvent, assertContextStreamTransitions, validateContextStreamTimeline, } from "./context.stream.js";
13
+ export { CONTEXT_STEP_STREAM_VERSION, createContextStepStreamChunk, parseContextStepStreamChunk, encodeContextStepStreamChunk, } from "./context.step-stream.js";
13
14
  export type { ContextStreamEvent, ContextCreatedEvent, ContextResolvedEvent, ContextStatusChangedEvent, ContextContentUpdatedEvent, ExecutionCreatedEvent, ExecutionCompletedEvent, ExecutionFailedEvent, ItemCreatedEvent, ItemUpdatedEvent, ItemPendingEvent, ItemCompletedEvent, StepCreatedEvent, StepUpdatedEvent, StepCompletedEvent, StepFailedEvent, PartCreatedEvent, PartUpdatedEvent, ChunkEmittedEvent, } from "./context.stream.js";
15
+ export type { ContextStepStreamChunk } from "./context.step-stream.js";
16
+ export type { ContextSkillPackage, ContextSkillPackageFile } from "./context.skill.js";
package/dist/index.js CHANGED
@@ -7,3 +7,4 @@ export { CONTEXT_STATUSES, EXECUTION_STATUSES, STEP_STATUSES, STEP_KINDS, ITEM_S
7
7
  export { DEFAULT_CODEX_TOOL_NAME, DEFAULT_CODEX_MODEL, codexToolInputSchema, buildDefaultCodexNarrative, didCodexToolExecute, createCodexContextBuilder, } from "./codex.js";
8
8
  export { useContext, } from "./react.js";
9
9
  export { parseContextStreamEvent, assertContextStreamTransitions, validateContextStreamTimeline, } from "./context.stream.js";
10
+ export { CONTEXT_STEP_STREAM_VERSION, createContextStepStreamChunk, parseContextStepStreamChunk, encodeContextStepStreamChunk, } from "./context.step-stream.js";
@@ -1,4 +1,4 @@
1
- import { executeReaction } from "../steps/reaction.steps.js";
1
+ import { executeAiSdkReaction } from "./ai-sdk.step.js";
2
2
  export function createAiSdkReactor(options) {
3
3
  return async (params) => {
4
4
  let config;
@@ -34,7 +34,7 @@ export function createAiSdkReactor(options) {
34
34
  config,
35
35
  })
36
36
  : params.maxModelSteps;
37
- const result = await executeReaction({
37
+ const result = await executeAiSdkReaction({
38
38
  env: params.env,
39
39
  contextIdentifier: params.contextIdentifier,
40
40
  model,
@@ -45,6 +45,7 @@ export function createAiSdkReactor(options) {
45
45
  maxSteps,
46
46
  sendStart: params.sendStart,
47
47
  silent: params.silent,
48
+ contextStepStream: params.contextStepStream,
48
49
  writable: params.writable,
49
50
  executionId: params.executionId,
50
51
  contextId: params.contextId,
@@ -4,13 +4,16 @@ import type { ContextModelInit } from "../context.engine.js";
4
4
  import type { ContextItem, ContextIdentifier } from "../context.store.js";
5
5
  import type { SerializableToolForModel } from "../tools-to-model-tools.js";
6
6
  /**
7
- * Executes a full "reaction" inside a single workflow step:
8
- * - load events from store
9
- * - convert events to model messages
10
- * - run the streaming model call and emit chunks
11
- * - extract tool calls from the resulting assistant event
7
+ * AI SDK-backed reaction execution inside a single workflow step.
8
+ *
9
+ * Provider-specific responsibilities live here:
10
+ * - load items from store
11
+ * - build model messages
12
+ * - stream through the AI SDK
13
+ * - map provider chunks to the Context stream contract
14
+ * - emit UI chunks and persist step stream chunks
12
15
  */
13
- export declare function executeReaction(params: {
16
+ export declare function executeAiSdkReaction(params: {
14
17
  env: ContextEnvironment;
15
18
  contextIdentifier: ContextIdentifier;
16
19
  model: ContextModelInit;
@@ -21,6 +24,7 @@ export declare function executeReaction(params: {
21
24
  maxSteps: number;
22
25
  sendStart?: boolean;
23
26
  silent?: boolean;
27
+ contextStepStream?: WritableStream<string>;
24
28
  writable?: WritableStream<UIMessageChunk>;
25
29
  executionId?: string;
26
30
  contextId?: string;
@@ -1,6 +1,7 @@
1
1
  import { OUTPUT_ITEM_TYPE } from "../context.events.js";
2
- import { mapAiSdkChunkToContextEvent } from "../reactors/ai-sdk.chunk-map.js";
3
- import { writeContextTraceEvents } from "./trace.steps.js";
2
+ import { createContextStepStreamChunk, encodeContextStepStreamChunk, } from "../context.step-stream.js";
3
+ import { mapAiSdkChunkToContextEvent } from "./ai-sdk.chunk-map.js";
4
+ import { writeContextTraceEvents } from "../steps/trace.steps.js";
4
5
  async function readWorkflowMetadata() {
5
6
  try {
6
7
  const { getWorkflowMetadata } = await import("workflow");
@@ -70,13 +71,16 @@ function safeErrorJson(error) {
70
71
  }
71
72
  }
72
73
  /**
73
- * Executes a full "reaction" inside a single workflow step:
74
- * - load events from store
75
- * - convert events to model messages
76
- * - run the streaming model call and emit chunks
77
- * - extract tool calls from the resulting assistant event
74
+ * AI SDK-backed reaction execution inside a single workflow step.
75
+ *
76
+ * Provider-specific responsibilities live here:
77
+ * - load items from store
78
+ * - build model messages
79
+ * - stream through the AI SDK
80
+ * - map provider chunks to the Context stream contract
81
+ * - emit UI chunks and persist step stream chunks
78
82
  */
79
- export async function executeReaction(params) {
83
+ export async function executeAiSdkReaction(params) {
80
84
  "use step";
81
85
  const { getContextRuntime } = await import("../runtime.js");
82
86
  const { store } = await getContextRuntime(params.env);
@@ -85,7 +89,7 @@ export async function executeReaction(params) {
85
89
  events = await store.getItems(params.contextIdentifier);
86
90
  }
87
91
  catch (error) {
88
- console.error("[ekairos/story] reaction.step store.getItems failed");
92
+ console.error("[ekairos/story] ai-sdk.step store.getItems failed");
89
93
  throw error;
90
94
  }
91
95
  let messagesForModel;
@@ -93,20 +97,18 @@ export async function executeReaction(params) {
93
97
  messagesForModel = (await store.itemsToModelMessages(events));
94
98
  }
95
99
  catch (error) {
96
- console.error("[ekairos/story] reaction.step store.itemsToModelMessages failed", safeErrorJson(error));
100
+ console.error("[ekairos/story] ai-sdk.step store.itemsToModelMessages failed", safeErrorJson(error));
97
101
  throw error;
98
102
  }
99
103
  const { jsonSchema, gateway, smoothStream, stepCountIs, streamText } = await import("ai");
100
104
  const { extractToolCallsFromParts } = await import("../context.toolcalls.js");
101
- // Match DurableAgent-style model init behavior:
102
105
  const resolvedModel = typeof params.model === "string"
103
106
  ? gateway(params.model)
104
107
  : typeof params.model === "function"
105
108
  ? await params.model()
106
109
  : (() => {
107
- throw new Error("Invalid model init passed to executeReaction. Expected a model id string or an async model factory.");
110
+ throw new Error("Invalid model init passed to executeAiSdkReaction. Expected a model id string or an async model factory.");
108
111
  })();
109
- // Wrap plain JSON Schema objects so the AI SDK doesn't attempt Zod conversion at runtime.
110
112
  const toolsForStreamText = {};
111
113
  for (const [name, t] of Object.entries(params.tools)) {
112
114
  toolsForStreamText[name] = {
@@ -124,7 +126,6 @@ export async function executeReaction(params) {
124
126
  stopWhen: stepCountIs(params.maxSteps),
125
127
  experimental_transform: smoothStream({ delayInMs: 30, chunking: "word" }),
126
128
  });
127
- // Ensure the underlying stream is consumed (AI SDK requirement)
128
129
  result.consumeStream();
129
130
  let resolveFinish;
130
131
  let rejectFinish;
@@ -135,76 +136,96 @@ export async function executeReaction(params) {
135
136
  });
136
137
  const modelId = typeof params.model === "string" ? params.model : "";
137
138
  const mappedProvider = modelId.includes("/") ? modelId.split("/")[0] : undefined;
138
- const uiStream = result
139
- .toUIMessageStream({
140
- sendStart: Boolean(params.sendStart),
141
- generateMessageId: () => params.eventId,
142
- messageMetadata() {
143
- return { eventId: params.eventId };
144
- },
145
- onFinish: ({ messages }) => {
146
- const lastMessage = messages[messages.length - 1];
147
- const event = {
148
- id: params.eventId,
149
- type: OUTPUT_ITEM_TYPE,
150
- channel: "web",
151
- createdAt: new Date().toISOString(),
152
- content: { parts: lastMessage?.parts ?? [] },
153
- };
154
- resolveFinish(event);
155
- },
156
- onError: (e) => {
157
- rejectFinish(e);
158
- return e instanceof Error ? e.message : String(e);
159
- },
160
- })
161
- // Filter out per-step finish boundary. Workflow will emit a single finish.
162
- .pipeThrough(new TransformStream({
163
- transform(chunk, controller) {
164
- const contextId = typeof params.contextId === "string" && params.contextId.length > 0
165
- ? params.contextId
166
- : undefined;
167
- if (contextId) {
168
- const mapped = mapAiSdkChunkToContextEvent({
169
- chunk,
170
- contextId,
171
- executionId: params.executionId,
172
- stepId: params.stepId,
173
- itemId: params.eventId,
174
- provider: mappedProvider,
175
- sequence: ++chunkSequence,
176
- });
177
- controller.enqueue({
178
- type: "data-chunk.emitted",
179
- data: mapped,
180
- });
139
+ const contextStepStreamWriter = params.contextStepStream?.getWriter();
140
+ try {
141
+ const uiStream = result
142
+ .toUIMessageStream({
143
+ sendStart: Boolean(params.sendStart),
144
+ generateMessageId: () => params.eventId,
145
+ messageMetadata() {
146
+ return { eventId: params.eventId };
147
+ },
148
+ onFinish: ({ messages }) => {
149
+ const lastMessage = messages[messages.length - 1];
150
+ const event = {
151
+ id: params.eventId,
152
+ type: OUTPUT_ITEM_TYPE,
153
+ channel: "web",
154
+ createdAt: new Date().toISOString(),
155
+ content: { parts: lastMessage?.parts ?? [] },
156
+ };
157
+ resolveFinish(event);
158
+ },
159
+ onError: (e) => {
160
+ rejectFinish(e);
161
+ return e instanceof Error ? e.message : String(e);
162
+ },
163
+ })
164
+ .pipeThrough(new TransformStream({
165
+ async transform(chunk, controller) {
166
+ const contextId = typeof params.contextId === "string" && params.contextId.length > 0
167
+ ? params.contextId
168
+ : undefined;
169
+ if (contextId) {
170
+ const mapped = mapAiSdkChunkToContextEvent({
171
+ chunk,
172
+ contextId,
173
+ executionId: params.executionId,
174
+ stepId: params.stepId,
175
+ itemId: params.eventId,
176
+ provider: mappedProvider,
177
+ sequence: ++chunkSequence,
178
+ });
179
+ const persistedChunk = createContextStepStreamChunk({
180
+ at: mapped.at,
181
+ sequence: mapped.sequence,
182
+ chunkType: mapped.chunkType,
183
+ provider: mapped.provider,
184
+ providerChunkType: mapped.providerChunkType,
185
+ actionRef: mapped.actionRef,
186
+ data: mapped.data && typeof mapped.data === "object"
187
+ ? mapped.data
188
+ : undefined,
189
+ raw: mapped.raw && typeof mapped.raw === "object"
190
+ ? mapped.raw
191
+ : undefined,
192
+ });
193
+ if (contextStepStreamWriter) {
194
+ await contextStepStreamWriter.write(encodeContextStepStreamChunk(persistedChunk));
195
+ }
196
+ controller.enqueue({
197
+ type: "data-chunk.emitted",
198
+ data: mapped,
199
+ });
200
+ }
201
+ if (chunk.type === "finish")
202
+ return;
203
+ controller.enqueue(chunk);
204
+ },
205
+ }));
206
+ if (params.writable) {
207
+ await uiStream.pipeTo(params.writable, { preventClose: true });
208
+ }
209
+ else {
210
+ const reader = uiStream.getReader();
211
+ try {
212
+ while (true) {
213
+ const { done } = await reader.read();
214
+ if (done)
215
+ break;
216
+ }
181
217
  }
182
- if (chunk.type === "finish")
183
- return;
184
- controller.enqueue(chunk);
185
- },
186
- }));
187
- if (params.writable) {
188
- await uiStream.pipeTo(params.writable, { preventClose: true });
189
- }
190
- else {
191
- const reader = uiStream.getReader();
192
- try {
193
- while (true) {
194
- const { done } = await reader.read();
195
- if (done)
196
- break;
218
+ finally {
219
+ reader.releaseLock();
197
220
  }
198
221
  }
199
- finally {
200
- reader.releaseLock();
201
- }
222
+ }
223
+ finally {
224
+ contextStepStreamWriter?.releaseLock();
202
225
  }
203
226
  const assistantEvent = await finishPromise;
204
227
  const finishedAtMs = Date.now();
205
228
  const toolCalls = extractToolCallsFromParts(assistantEvent?.content?.parts);
206
- // Best-effort usage extraction (AI SDK provider dependent).
207
- // We keep this loose because providers differ and SDK evolves quickly.
208
229
  const latencyMs = Math.max(0, finishedAtMs - startedAtMs);
209
230
  let usage = undefined;
210
231
  let providerMetadata = undefined;
@@ -226,8 +247,6 @@ export async function executeReaction(params) {
226
247
  catch {
227
248
  providerMetadata = undefined;
228
249
  }
229
- // Workflow steps must return serializable values. Provider SDKs may include
230
- // classes/streams/etc in metadata, so we defensively sanitize.
231
250
  function toPlainJson(value) {
232
251
  if (typeof value === "undefined")
233
252
  return undefined;
@@ -240,10 +259,8 @@ export async function executeReaction(params) {
240
259
  }
241
260
  const usageJson = toPlainJson(usage);
242
261
  const providerMetadataJson = toPlainJson(providerMetadata);
243
- // Derive provider/model from gateway id when available.
244
262
  const provider = modelId.includes("/") ? modelId.split("/")[0] : providerMetadata?.provider;
245
263
  const model = modelId.includes("/") ? modelId.split("/").slice(1).join("/") : providerMetadata?.model;
246
- // Token accounting: attempt to read cached prompt tokens from OpenAI-like usage shapes.
247
264
  const promptTokens = Number(usage?.promptTokens ?? usage?.prompt_tokens ?? usage?.inputTokens ?? 0) || 0;
248
265
  const completionTokens = Number(usage?.completionTokens ?? usage?.completion_tokens ?? usage?.outputTokens ?? 0) || 0;
249
266
  const totalTokens = Number(usage?.totalTokens ?? usage?.total_tokens ?? 0) || (promptTokens + completionTokens);
@@ -2,6 +2,7 @@ import type { ModelMessage, UIMessageChunk } from "ai";
2
2
  import type { ContextEnvironment } from "../context.config.js";
3
3
  import type { ContextModelInit } from "../context.engine.js";
4
4
  import type { ContextIdentifier, StoredContext, ContextItem } from "../context.store.js";
5
+ import type { ContextSkillPackage } from "../context.skill.js";
5
6
  import type { SerializableToolForModel } from "../tools-to-model-tools.js";
6
7
  export type ContextActionRequest = {
7
8
  actionRef: string;
@@ -35,6 +36,7 @@ export type ContextReactorParams<Context = unknown, Env extends ContextEnvironme
35
36
  systemPrompt: string;
36
37
  actions: Record<string, unknown>;
37
38
  toolsForModel: Record<string, SerializableToolForModel>;
39
+ skills: ContextSkillPackage[];
38
40
  eventId: string;
39
41
  executionId: string;
40
42
  contextId: string;
@@ -43,6 +45,7 @@ export type ContextReactorParams<Context = unknown, Env extends ContextEnvironme
43
45
  maxModelSteps: number;
44
46
  sendStart: boolean;
45
47
  silent: boolean;
48
+ contextStepStream?: WritableStream<string>;
46
49
  writable?: WritableStream<UIMessageChunk>;
47
50
  persistReactionParts?: (parts: any[]) => Promise<void>;
48
51
  };
package/dist/runtime.d.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  * - Keep `@ekairos/events` main entrypoint safe to import from schema/domain modules.
10
10
  */
11
11
  export { getContextRuntime, } from "./runtime.step.js";
12
+ export { createContextStepStreamClientId, createPersistedContextStepStream, readPersistedContextStepStream, resolveContextExecutionStreamPointer, waitForContextExecutionStreamPointer, } from "./steps/stream.steps.js";
12
13
  export type { ContextEnvironment, ContextRuntime } from "./context.config.js";
13
14
  export { registerContextEnv, getContextEnv } from "./env.js";
14
15
  export { configureContextDurableWorkflow } from "./context.durable.js";
package/dist/runtime.js CHANGED
@@ -21,5 +21,6 @@ if (typeof globalThis.Event === "undefined") {
21
21
  globalThis.Event = NodeEvent;
22
22
  }
23
23
  export { getContextRuntime, } from "./runtime.step.js";
24
+ export { createContextStepStreamClientId, createPersistedContextStepStream, readPersistedContextStepStream, resolveContextExecutionStreamPointer, waitForContextExecutionStreamPointer, } from "./steps/stream.steps.js";
24
25
  export { registerContextEnv, getContextEnv } from "./env.js";
25
26
  export { configureContextDurableWorkflow } from "./context.durable.js";
@@ -1,7 +1,7 @@
1
1
  import { coerceContextRuntime } from "./context.config.js";
2
2
  import { eventsDomain } from "./schema.js";
3
3
  export async function getContextRuntime(env) {
4
- const { resolveRuntime } = await import("@ekairos/domain/runtime");
4
+ const { resolveRuntime } = await import("@ekairos/domain/runtime-step");
5
5
  const resolved = await resolveRuntime(eventsDomain, env);
6
6
  return await coerceContextRuntime(resolved);
7
7
  }
package/dist/schema.js CHANGED
@@ -23,6 +23,10 @@ export const eventsDomain = domain("events")
23
23
  updatedAt: i.date().optional(),
24
24
  status: i.string().optional().indexed(), // executing | completed | failed
25
25
  workflowRunId: i.string().optional().indexed(),
26
+ activeStreamId: i.string().optional().indexed(),
27
+ activeStreamClientId: i.string().optional().indexed(),
28
+ lastStreamId: i.string().optional().indexed(),
29
+ lastStreamClientId: i.string().optional().indexed(),
26
30
  }),
27
31
  event_steps: i.entity({
28
32
  createdAt: i.date().indexed(),
@@ -38,6 +42,11 @@ export const eventsDomain = domain("events")
38
42
  actionResults: i.any().optional(),
39
43
  continueLoop: i.boolean().optional(),
40
44
  errorText: i.string().optional(),
45
+ streamId: i.string().optional().indexed(),
46
+ streamClientId: i.string().optional().indexed(),
47
+ streamStartedAt: i.date().optional().indexed(),
48
+ streamFinishedAt: i.date().optional().indexed(),
49
+ streamAbortReason: i.string().optional(),
41
50
  }),
42
51
  event_parts: i.entity({
43
52
  key: i.string().unique().indexed(), // `${stepId}:${idx}`
@@ -152,6 +161,18 @@ export const eventsDomain = domain("events")
152
161
  forward: { on: "event_parts", has: "one", label: "step" },
153
162
  reverse: { on: "event_steps", has: "many", label: "parts" },
154
163
  },
164
+ contextStepStream: {
165
+ forward: { on: "event_steps", has: "one", label: "stream" },
166
+ reverse: { on: "$streams", has: "many", label: "step" },
167
+ },
168
+ contextExecutionActiveStream: {
169
+ forward: { on: "event_executions", has: "one", label: "activeStream" },
170
+ reverse: { on: "$streams", has: "many", label: "activeOf" },
171
+ },
172
+ contextExecutionLastStream: {
173
+ forward: { on: "event_executions", has: "one", label: "lastStream" },
174
+ reverse: { on: "$streams", has: "many", label: "lastOf" },
175
+ },
155
176
  documentFile: {
156
177
  forward: {
157
178
  on: "document_documents",
@@ -1,4 +1,6 @@
1
1
  import type { UIMessageChunk } from "ai";
2
+ import type { ContextEnvironment } from "../context.config.js";
3
+ import { type ContextStepStreamChunk } from "../context.step-stream.js";
2
4
  import type { ContextStreamEvent } from "../context.stream.js";
3
5
  export declare function writeContextEvents(params: {
4
6
  events: ContextStreamEvent[];
@@ -9,3 +11,76 @@ export declare function closeContextStream(params: {
9
11
  sendFinish?: boolean;
10
12
  writable?: WritableStream<UIMessageChunk>;
11
13
  }): Promise<void>;
14
+ export declare function createContextStepStreamClientId(stepId: string): string;
15
+ export type PersistedContextStepStreamSession = {
16
+ stream: WritableStream<string>;
17
+ streamId: string;
18
+ clientId: string;
19
+ executionId: string;
20
+ stepId: string;
21
+ };
22
+ export declare function createPersistedContextStepStream(params: {
23
+ env: ContextEnvironment;
24
+ executionId: string;
25
+ stepId: string;
26
+ clientId?: string;
27
+ }): Promise<PersistedContextStepStreamSession>;
28
+ export declare function closePersistedContextStepStream(params: {
29
+ env: ContextEnvironment;
30
+ session: PersistedContextStepStreamSession;
31
+ }): Promise<void>;
32
+ export declare function abortPersistedContextStepStream(params: {
33
+ env: ContextEnvironment;
34
+ session: PersistedContextStepStreamSession;
35
+ reason?: string | null;
36
+ }): Promise<void>;
37
+ export declare function readPersistedContextStepStream(params: {
38
+ db: any;
39
+ clientId?: string;
40
+ streamId?: string;
41
+ byteOffset?: number;
42
+ onChunk?: (chunk: ContextStepStreamChunk) => Promise<void> | void;
43
+ }): Promise<{
44
+ chunks: ContextStepStreamChunk[];
45
+ byteOffset: number;
46
+ }>;
47
+ export declare function resolveContextExecutionStreamPointer(params: {
48
+ db: any;
49
+ contextId: string;
50
+ }): Promise<{
51
+ executionId: string;
52
+ status: string | null;
53
+ source: "active";
54
+ clientId: string | null;
55
+ streamId: string | null;
56
+ } | {
57
+ executionId: string;
58
+ status: string | null;
59
+ source: "last";
60
+ clientId: string | null;
61
+ streamId: string | null;
62
+ } | {
63
+ executionId: string;
64
+ status: string | null;
65
+ source: "none";
66
+ clientId: null;
67
+ streamId: null;
68
+ } | null>;
69
+ export declare function waitForContextExecutionStreamPointer(params: {
70
+ db: any;
71
+ contextId: string;
72
+ timeoutMs?: number;
73
+ pollMs?: number;
74
+ }): Promise<{
75
+ executionId: string;
76
+ status: string | null;
77
+ source: "active";
78
+ clientId: string | null;
79
+ streamId: string | null;
80
+ } | {
81
+ executionId: string;
82
+ status: string | null;
83
+ source: "last";
84
+ clientId: string | null;
85
+ streamId: string | null;
86
+ } | null>;
@@ -1,3 +1,4 @@
1
+ import { contextStreamByteLength, parseContextStepStreamChunk, } from "../context.step-stream.js";
1
2
  export async function writeContextEvents(params) {
2
3
  "use step";
3
4
  const writable = params.writable;
@@ -36,3 +37,244 @@ export async function closeContextStream(params) {
36
37
  await writable.close();
37
38
  }
38
39
  }
40
+ function readWaitUntil(env) {
41
+ const candidate = typeof env?.waitUntil === "function"
42
+ ? env.waitUntil
43
+ : typeof env?.after === "function"
44
+ ? env.after
45
+ : undefined;
46
+ return typeof candidate === "function" ? candidate : undefined;
47
+ }
48
+ function asRecord(value) {
49
+ return value && typeof value === "object" ? value : {};
50
+ }
51
+ function asString(value) {
52
+ return typeof value === "string" ? value.trim() : "";
53
+ }
54
+ function createUnsetStreamLinkTx(db, executionId, label, streamId) {
55
+ try {
56
+ return db.tx.event_executions[executionId].unlink({ [label]: streamId });
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ export function createContextStepStreamClientId(stepId) {
63
+ const normalized = String(stepId ?? "").trim();
64
+ if (!normalized) {
65
+ throw new Error("createContextStepStreamClientId: stepId is required.");
66
+ }
67
+ return `event-step:${normalized}`;
68
+ }
69
+ export async function createPersistedContextStepStream(params) {
70
+ "use step";
71
+ const { getContextRuntime } = await import("../runtime.js");
72
+ const runtime = await getContextRuntime(params.env);
73
+ const db = runtime?.db;
74
+ if (!db?.streams?.createWriteStream) {
75
+ throw new Error("InstantDB streams are not available on the configured runtime. Upgrade @instantdb/admin to a streams-capable version.");
76
+ }
77
+ const clientId = asString(params.clientId) || createContextStepStreamClientId(params.stepId);
78
+ const startedAt = new Date();
79
+ const waitUntil = readWaitUntil(params.env);
80
+ const writeStream = db.streams.createWriteStream({
81
+ clientId,
82
+ waitUntil,
83
+ });
84
+ const streamId = await writeStream.streamId();
85
+ await db.transact([
86
+ db.tx.event_steps[params.stepId]
87
+ .update({
88
+ streamId,
89
+ streamClientId: clientId,
90
+ streamStartedAt: startedAt,
91
+ streamFinishedAt: null,
92
+ streamAbortReason: null,
93
+ updatedAt: new Date(),
94
+ })
95
+ .link({ stream: streamId }),
96
+ db.tx.event_executions[params.executionId]
97
+ .update({
98
+ activeStreamId: streamId,
99
+ activeStreamClientId: clientId,
100
+ lastStreamId: streamId,
101
+ lastStreamClientId: clientId,
102
+ updatedAt: new Date(),
103
+ })
104
+ .link({ activeStream: streamId, lastStream: streamId }),
105
+ ]);
106
+ return {
107
+ stream: writeStream,
108
+ streamId,
109
+ clientId,
110
+ executionId: params.executionId,
111
+ stepId: params.stepId,
112
+ };
113
+ }
114
+ async function finalizePersistedContextStepStream(params) {
115
+ "use step";
116
+ const { getContextRuntime } = await import("../runtime.js");
117
+ const runtime = await getContextRuntime(params.env);
118
+ const db = runtime?.db;
119
+ const writer = params.session.stream.getWriter();
120
+ try {
121
+ if (params.mode === "abort") {
122
+ await writer.abort(params.abortReason ?? "aborted");
123
+ }
124
+ else {
125
+ await writer.close();
126
+ }
127
+ }
128
+ finally {
129
+ writer.releaseLock();
130
+ }
131
+ const now = new Date();
132
+ const txs = [
133
+ db.tx.event_steps[params.session.stepId].update({
134
+ streamFinishedAt: now,
135
+ streamAbortReason: params.mode === "abort" ? params.abortReason ?? "aborted" : null,
136
+ updatedAt: now,
137
+ }),
138
+ db.tx.event_executions[params.session.executionId].update({
139
+ activeStreamId: null,
140
+ activeStreamClientId: null,
141
+ lastStreamId: params.session.streamId,
142
+ lastStreamClientId: params.session.clientId,
143
+ updatedAt: now,
144
+ }),
145
+ ];
146
+ const unsetActive = createUnsetStreamLinkTx(db, params.session.executionId, "activeStream", params.session.streamId);
147
+ if (unsetActive)
148
+ txs.push(unsetActive);
149
+ await db.transact(txs);
150
+ }
151
+ export async function closePersistedContextStepStream(params) {
152
+ return await finalizePersistedContextStepStream({
153
+ env: params.env,
154
+ session: params.session,
155
+ mode: "close",
156
+ });
157
+ }
158
+ export async function abortPersistedContextStepStream(params) {
159
+ return await finalizePersistedContextStepStream({
160
+ env: params.env,
161
+ session: params.session,
162
+ mode: "abort",
163
+ abortReason: params.reason,
164
+ });
165
+ }
166
+ export async function readPersistedContextStepStream(params) {
167
+ if (!params.db?.streams?.createReadStream) {
168
+ throw new Error("InstantDB streams are not available on the provided runtime.");
169
+ }
170
+ const clientId = asString(params.clientId);
171
+ const streamId = asString(params.streamId);
172
+ if (!clientId && !streamId) {
173
+ throw new Error("readPersistedContextStepStream requires clientId or streamId.");
174
+ }
175
+ const startOffset = typeof params.byteOffset === "number" && Number.isFinite(params.byteOffset)
176
+ ? Math.max(0, params.byteOffset)
177
+ : 0;
178
+ const stream = params.db.streams.createReadStream({
179
+ clientId: clientId || undefined,
180
+ streamId: streamId || undefined,
181
+ byteOffset: startOffset,
182
+ });
183
+ const chunks = [];
184
+ let byteOffset = startOffset;
185
+ let buffer = "";
186
+ for await (const rawChunk of stream) {
187
+ const encoded = typeof rawChunk === "string" ? rawChunk : String(rawChunk ?? "");
188
+ if (!encoded)
189
+ continue;
190
+ byteOffset += contextStreamByteLength(encoded);
191
+ buffer += encoded;
192
+ const lines = buffer.split("\n");
193
+ buffer = lines.pop() ?? "";
194
+ for (const line of lines) {
195
+ const trimmed = line.trim();
196
+ if (!trimmed)
197
+ continue;
198
+ const parsed = parseContextStepStreamChunk(trimmed);
199
+ chunks.push(parsed);
200
+ await params.onChunk?.(parsed);
201
+ }
202
+ }
203
+ const trailing = buffer.trim();
204
+ if (trailing) {
205
+ const parsed = parseContextStepStreamChunk(trailing);
206
+ chunks.push(parsed);
207
+ await params.onChunk?.(parsed);
208
+ }
209
+ return {
210
+ chunks,
211
+ byteOffset,
212
+ };
213
+ }
214
+ export async function resolveContextExecutionStreamPointer(params) {
215
+ const snapshot = await params.db.query({
216
+ event_contexts: {
217
+ $: {
218
+ where: { id: params.contextId },
219
+ limit: 1,
220
+ },
221
+ currentExecution: {},
222
+ },
223
+ });
224
+ const contextRow = Array.isArray(snapshot?.event_contexts)
225
+ ? snapshot.event_contexts[0]
226
+ : null;
227
+ const executionRow = asRecord(contextRow?.currentExecution);
228
+ const executionId = asString(executionRow.id);
229
+ if (!executionId)
230
+ return null;
231
+ const activeStreamClientId = asString(executionRow.activeStreamClientId);
232
+ const activeStreamId = asString(executionRow.activeStreamId);
233
+ if (activeStreamClientId || activeStreamId) {
234
+ return {
235
+ executionId,
236
+ status: asString(executionRow.status) || null,
237
+ source: "active",
238
+ clientId: activeStreamClientId || null,
239
+ streamId: activeStreamId || null,
240
+ };
241
+ }
242
+ const lastStreamClientId = asString(executionRow.lastStreamClientId);
243
+ const lastStreamId = asString(executionRow.lastStreamId);
244
+ if (lastStreamClientId || lastStreamId) {
245
+ return {
246
+ executionId,
247
+ status: asString(executionRow.status) || null,
248
+ source: "last",
249
+ clientId: lastStreamClientId || null,
250
+ streamId: lastStreamId || null,
251
+ };
252
+ }
253
+ return {
254
+ executionId,
255
+ status: asString(executionRow.status) || null,
256
+ source: "none",
257
+ clientId: null,
258
+ streamId: null,
259
+ };
260
+ }
261
+ export async function waitForContextExecutionStreamPointer(params) {
262
+ const timeoutMs = typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs)
263
+ ? Math.max(0, params.timeoutMs)
264
+ : 15000;
265
+ const pollMs = typeof params.pollMs === "number" && Number.isFinite(params.pollMs)
266
+ ? Math.max(10, params.pollMs)
267
+ : 125;
268
+ const deadline = Date.now() + timeoutMs;
269
+ while (Date.now() <= deadline) {
270
+ const pointer = await resolveContextExecutionStreamPointer({
271
+ db: params.db,
272
+ contextId: params.contextId,
273
+ });
274
+ if (pointer && (pointer.clientId || pointer.streamId)) {
275
+ return pointer;
276
+ }
277
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
278
+ }
279
+ return null;
280
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekairos/events",
3
- "version": "1.22.30-beta.development.0",
3
+ "version": "1.22.31-beta.development.0",
4
4
  "description": "Ekairos Events - Context-first workflow runtime",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -108,15 +108,17 @@
108
108
  "clean": "node -e \"require('fs').rmSync('dist', {recursive:true, force:true})\"",
109
109
  "typecheck": "tsc --noEmit",
110
110
  "test": "vitest run -c vitest.config.mts",
111
+ "pretest:ai-sdk-reactor": "pnpm --filter @ekairos/domain build",
111
112
  "test:ai-sdk-reactor": "vitest run -c vitest.config.mts src/tests/context.ai-sdk-reactor.instant.test.ts",
112
113
  "test:ai-sdk-reactor:real": "vitest run -c vitest.config.mts src/tests/context.ai-sdk-reactor.real.instant.test.ts",
114
+ "pretest:scripted-reactor": "pnpm --filter @ekairos/domain build",
113
115
  "test:scripted-reactor": "vitest run -c vitest.config.mts src/tests/context.scripted-reactor.instant.test.ts"
114
116
  },
115
117
  "dependencies": {
116
118
  "@ai-sdk/openai": "^2.0.52",
117
- "@ekairos/domain": "^1.22.30-beta.development.0",
118
- "@instantdb/admin": "0.22.126",
119
- "@instantdb/core": "0.22.126",
119
+ "@ekairos/domain": "^1.22.31-beta.development.0",
120
+ "@instantdb/admin": "0.22.158",
121
+ "@instantdb/core": "0.22.142",
120
122
  "@vercel/mcp-adapter": "^1.0.0",
121
123
  "@vercel/sandbox": "^0.0.23",
122
124
  "ai": "^5.0.95",