@ekairos/story 1.21.54-beta.0 → 1.21.57-beta.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/dist/next.js CHANGED
@@ -4,8 +4,12 @@ import { dirname, relative, resolve } from "node:path";
4
4
  function patchWorkflowStepRouteToImportBootstrap(bootstrapModule) {
5
5
  const cwd = process.cwd();
6
6
  const candidates = [
7
+ // legacy app-dir without /src
7
8
  resolve(cwd, "app/.well-known/workflow/v1/step/route.js"),
9
+ resolve(cwd, "app/.well-known/workflow/v1/step/route.ts"),
10
+ // app-dir under /src
8
11
  resolve(cwd, "src/app/.well-known/workflow/v1/step/route.js"),
12
+ resolve(cwd, "src/app/.well-known/workflow/v1/step/route.ts"),
9
13
  ];
10
14
  const bootstrapAbs = resolve(cwd, bootstrapModule);
11
15
  for (const routeFile of candidates) {
@@ -24,10 +28,14 @@ function patchWorkflowStepRouteToImportBootstrap(bootstrapModule) {
24
28
  if (contents.includes(importLine))
25
29
  continue;
26
30
  const lines = contents.split(/\r?\n/);
31
+ // Insert the import above the first real statement (skip comments/empty lines).
27
32
  let insertAt = 0;
28
33
  for (let i = 0; i < lines.length; i++) {
29
34
  const t = lines[i].trim();
30
- const isComment = t.startsWith("//") || t.startsWith("/*") || t.startsWith("*") || t.startsWith("*/");
35
+ const isComment = t.startsWith("//") ||
36
+ t.startsWith("/*") ||
37
+ t.startsWith("*") ||
38
+ t.startsWith("*/");
31
39
  if (t === "" || isComment)
32
40
  continue;
33
41
  insertAt = i;
@@ -90,9 +98,7 @@ export function withEkairosRuntime(nextConfigOrFn, opts) {
90
98
  const req = createRequire(import.meta.url);
91
99
  const contextDir = (out && out.context) || process.cwd();
92
100
  // Resolve relative to the app project (webpack context), not to this package.
93
- const resolvedBootstrap = req.resolve(bootstrapModule, {
94
- paths: [contextDir],
95
- });
101
+ const resolvedBootstrap = req.resolve(bootstrapModule, { paths: [contextDir] });
96
102
  const originalEntry = out.entry;
97
103
  out.entry = async () => {
98
104
  const entries = typeof originalEntry === "function" ? await originalEntry() : originalEntry;
@@ -104,7 +110,7 @@ export function withEkairosRuntime(nextConfigOrFn, opts) {
104
110
  };
105
111
  };
106
112
  // Critical path for Vercel/Turbopack:
107
- // `@workflow/next` generates `src/app/.well-known/workflow/v1/step/route.js` inside its config function.
113
+ // `@workflow/next` generates `.well-known/workflow/.../route.(ts|js)` inside its config function.
108
114
  // So we must patch AFTER that function runs (not inside webpack).
109
115
  if (typeof nextConfigOrFn === "function") {
110
116
  return async (phase, ctx) => {
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Internal helper to resolve Story runtime from within workflow steps.
3
+ *
4
+ * Why dynamic import?
5
+ * - Some bundlers (notably Turbopack step bundles) can drop/hoist static imports in "use-step" modules,
6
+ * causing `ReferenceError: resolveStoryRuntime is not defined`.
7
+ * - Using a dynamic import keeps the symbol resolution local to the step runtime.
8
+ */
9
+ import type { StoryEnvironment, StoryRuntime } from "./story.config";
10
+ export declare function getStoryRuntime(env: StoryEnvironment): Promise<StoryRuntime>;
@@ -0,0 +1,4 @@
1
+ export async function getStoryRuntime(env) {
2
+ const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
3
+ return await resolveStoryRuntime(env);
4
+ }
@@ -1,4 +1,4 @@
1
- import { type ModelMessage } from "ai";
1
+ import type { ModelMessage } from "ai";
2
2
  import type { ContextEvent } from "../story.store";
3
3
  import type { SerializableToolForModel } from "../tools-to-model-tools";
4
4
  /**
@@ -1,6 +1,3 @@
1
- import { jsonSchema, gateway, smoothStream, stepCountIs, streamText, } from "ai";
2
- import { getWritable } from "workflow";
3
- import { extractToolCallsFromParts } from "../story.toolcalls";
4
1
  /**
5
2
  * Runs a single LLM streaming step as a Workflow step.
6
3
  *
@@ -10,7 +7,10 @@ import { extractToolCallsFromParts } from "../story.toolcalls";
10
7
  */
11
8
  export async function doStoryStreamStep(params) {
12
9
  "use step";
10
+ const { getWritable } = await import("workflow");
13
11
  const writable = getWritable();
12
+ const { jsonSchema, gateway, smoothStream, stepCountIs, streamText } = await import("ai");
13
+ const { extractToolCallsFromParts } = await import("../story.toolcalls");
14
14
  // Match DurableAgent's model init behavior:
15
15
  // - string => AI Gateway model id, resolved via `gateway(...)` in the step runtime
16
16
  // - function => model factory (should be a `"use step"` function for workflow serialization)
@@ -1,4 +1,4 @@
1
- import { type ModelMessage } from "ai";
1
+ import type { ModelMessage } from "ai";
2
2
  import type { StoryEnvironment } from "../story.config";
3
3
  import type { ContextEvent, ContextIdentifier } from "../story.store";
4
4
  import type { SerializableToolForModel } from "../tools-to-model-tools";
@@ -1,7 +1,3 @@
1
- import { jsonSchema, gateway, smoothStream, stepCountIs, streamText, } from "ai";
2
- import { getWritable } from "workflow";
3
- import { resolveStoryRuntime } from "../story.config";
4
- import { extractToolCallsFromParts } from "../story.toolcalls";
5
1
  function safeErrorJson(error) {
6
2
  const seen = new WeakSet();
7
3
  const redactKey = (k) => /token|authorization|cookie|secret|api[_-]?key|password/i.test(k);
@@ -41,6 +37,7 @@ function safeErrorJson(error) {
41
37
  */
42
38
  export async function executeReaction(params) {
43
39
  "use step";
40
+ const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
44
41
  const { store } = await resolveStoryRuntime(params.env);
45
42
  console.log("[ekairos/story] reaction.step executeReaction begin");
46
43
  let events;
@@ -65,7 +62,9 @@ export async function executeReaction(params) {
65
62
  }
66
63
  const writable = params.silent
67
64
  ? new WritableStream({ write() { } })
68
- : getWritable();
65
+ : (await import("workflow")).getWritable();
66
+ const { jsonSchema, gateway, smoothStream, stepCountIs, streamText } = await import("ai");
67
+ const { extractToolCallsFromParts } = await import("../story.toolcalls");
69
68
  // Match DurableAgent-style model init behavior:
70
69
  const resolvedModel = typeof params.model === "string"
71
70
  ? gateway(params.model)
@@ -1,4 +1,4 @@
1
- import { type StoryEnvironment } from "../story.config";
1
+ import type { StoryEnvironment } from "../story.config";
2
2
  import type { ContextEvent, ContextIdentifier, StoredContext } from "../story.store";
3
3
  /**
4
4
  * Initializes/ensures the story context exists and emits a single `data-context-id` chunk.
@@ -1,5 +1,3 @@
1
- import { getWritable } from "workflow";
2
- import { resolveStoryRuntime } from "../story.config";
3
1
  /**
4
2
  * Initializes/ensures the story context exists and emits a single `data-context-id` chunk.
5
3
  *
@@ -7,6 +5,7 @@ import { resolveStoryRuntime } from "../story.config";
7
5
  */
8
6
  export async function initializeContext(env, contextIdentifier, opts) {
9
7
  "use step";
8
+ const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
10
9
  const { store } = await resolveStoryRuntime(env);
11
10
  // Detect creation explicitly so the engine can run onContextCreated hooks.
12
11
  let result;
@@ -27,6 +26,7 @@ export async function initializeContext(env, contextIdentifier, opts) {
27
26
  // If we're running in a non-streaming context (e.g. tests or headless usage),
28
27
  // we skip writing stream chunks entirely.
29
28
  if (!opts?.silent) {
29
+ const { getWritable } = await import("workflow");
30
30
  const writable = getWritable();
31
31
  const writer = writable.getWriter();
32
32
  try {
@@ -44,36 +44,43 @@ export async function initializeContext(env, contextIdentifier, opts) {
44
44
  }
45
45
  export async function updateContextContent(env, contextIdentifier, content) {
46
46
  "use step";
47
+ const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
47
48
  const { store } = await resolveStoryRuntime(env);
48
49
  return await store.updateContextContent(contextIdentifier, content);
49
50
  }
50
51
  export async function updateContextStatus(env, contextIdentifier, status) {
51
52
  "use step";
53
+ const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
52
54
  const { store } = await resolveStoryRuntime(env);
53
55
  await store.updateContextStatus(contextIdentifier, status);
54
56
  }
55
57
  export async function saveTriggerEvent(env, contextIdentifier, event) {
56
58
  "use step";
59
+ const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
57
60
  const { store } = await resolveStoryRuntime(env);
58
61
  return await store.saveEvent(contextIdentifier, event);
59
62
  }
60
63
  export async function saveReactionEvent(env, contextIdentifier, event) {
61
64
  "use step";
65
+ const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
62
66
  const { store } = await resolveStoryRuntime(env);
63
67
  return await store.saveEvent(contextIdentifier, event);
64
68
  }
65
69
  export async function updateEvent(env, eventId, event) {
66
70
  "use step";
71
+ const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
67
72
  const { store } = await resolveStoryRuntime(env);
68
73
  return await store.updateEvent(eventId, event);
69
74
  }
70
75
  export async function createExecution(env, contextIdentifier, triggerEventId, reactionEventId) {
71
76
  "use step";
77
+ const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
72
78
  const { store } = await resolveStoryRuntime(env);
73
79
  return await store.createExecution(contextIdentifier, triggerEventId, reactionEventId);
74
80
  }
75
81
  export async function createReactionEvent(params) {
76
82
  "use step";
83
+ const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
77
84
  const { store } = await resolveStoryRuntime(params.env);
78
85
  // Generate a new reaction event id inside the step boundary.
79
86
  const uuid = globalThis.crypto?.randomUUID?.();
@@ -86,6 +93,7 @@ export async function createReactionEvent(params) {
86
93
  }
87
94
  export async function completeExecution(env, contextIdentifier, executionId, status) {
88
95
  "use step";
96
+ const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
89
97
  const { store } = await resolveStoryRuntime(env);
90
98
  await store.completeExecution(contextIdentifier, executionId, status);
91
99
  }
@@ -1,3 +1,8 @@
1
+ export declare function writeStoryLog(params: {
2
+ level?: "info" | "debug" | "warn" | "error";
3
+ message: string;
4
+ args?: Array<string | number | boolean | null>;
5
+ }): Promise<void>;
1
6
  export declare function writeContextSubstate(params: {
2
7
  /**
3
8
  * Ephemeral substate key for the UI (story engine internal state).
@@ -1,6 +1,18 @@
1
- import { getWritable } from "workflow";
1
+ export async function writeStoryLog(params) {
2
+ "use step";
3
+ const level = params.level ?? "info";
4
+ const fn = level === "debug"
5
+ ? console.debug
6
+ : level === "warn"
7
+ ? console.warn
8
+ : level === "error"
9
+ ? console.error
10
+ : console.log;
11
+ fn(params.message, ...(params.args ?? []));
12
+ }
2
13
  export async function writeContextSubstate(params) {
3
14
  "use step";
15
+ const { getWritable } = await import("workflow");
4
16
  const writable = getWritable();
5
17
  const writer = writable.getWriter();
6
18
  try {
@@ -16,6 +28,7 @@ export async function writeContextSubstate(params) {
16
28
  }
17
29
  export async function writeContextIdChunk(params) {
18
30
  "use step";
31
+ const { getWritable } = await import("workflow");
19
32
  const writable = getWritable();
20
33
  const writer = writable.getWriter();
21
34
  try {
@@ -31,6 +44,7 @@ export async function writeContextIdChunk(params) {
31
44
  }
32
45
  export async function writeStoryPing(params) {
33
46
  "use step";
47
+ const { getWritable } = await import("workflow");
34
48
  const writable = getWritable();
35
49
  const writer = writable.getWriter();
36
50
  try {
@@ -46,6 +60,7 @@ export async function writeStoryPing(params) {
46
60
  }
47
61
  export async function writeToolOutputs(params) {
48
62
  "use step";
63
+ const { getWritable } = await import("workflow");
49
64
  const writable = getWritable();
50
65
  const writer = writable.getWriter();
51
66
  try {
@@ -74,6 +89,7 @@ export async function closeStoryStream(params) {
74
89
  "use step";
75
90
  const sendFinish = params.sendFinish ?? true;
76
91
  const preventClose = params.preventClose ?? false;
92
+ const { getWritable } = await import("workflow");
77
93
  const writable = getWritable();
78
94
  if (sendFinish) {
79
95
  const writer = writable.getWriter();
@@ -17,7 +17,7 @@ export interface StoryConfig<Context, Env extends StoryEnvironment = StoryEnviro
17
17
  * If you do not provide an implementation, the default is an identity transform
18
18
  * (events pass through unchanged).
19
19
  *
20
- * If you do I/O here, implement it as a `"use step"` function.
20
+ * If you do I/O here, implement it as a `"use-step"` function.
21
21
  */
22
22
  expandEvents?: (events: ContextEvent[], context: StoredContext<Context>, env: Env) => Promise<ContextEvent[]> | ContextEvent[];
23
23
  /**
@@ -36,7 +36,7 @@ export interface StoryConfig<Context, Env extends StoryEnvironment = StoryEnviro
36
36
  * Model configuration (DurableAgent-style).
37
37
  *
38
38
  * - `string`: AI Gateway model id, resolved in the LLM step (e.g. `"openai/gpt-5.1-thinking"`).
39
- * - `() => Promise<model>`: model factory. For Workflow compatibility, provide a `"use step"` function
39
+ * - `() => Promise<model>`: model factory. For Workflow compatibility, provide a `"use-step"` function
40
40
  * so it can be serialized by reference.
41
41
  * - `(context, env) => ...`: dynamic selection based on current context/env (runs in workflow context).
42
42
  */
@@ -53,7 +53,7 @@ export interface StoryStreamOptions {
53
53
  *
54
54
  * - `string`: Vercel AI Gateway model id (e.g. `"openai/gpt-5"`), resolved inside the LLM step.
55
55
  * - `function`: a function that returns a model instance. For Workflow compatibility, this should
56
- * be a `"use step"` function (so it can be serialized by reference).
56
+ * be a `"use-step"` function (so it can be serialized by reference).
57
57
  */
58
58
  export type StoryModelInit = string | (() => Promise<any>) | any;
59
59
  export type StoryReactParams<Env extends StoryEnvironment = StoryEnvironment> = {
@@ -117,7 +117,7 @@ export declare abstract class Story<Context, Env extends StoryEnvironment = Stor
117
117
  * - This stage is ALWAYS executed by the engine.
118
118
  * - If you don't provide an implementation, the default behavior is an identity transform
119
119
  * (events pass through unchanged).
120
- * - If your implementation performs I/O, implement it as a `"use step"` function (provided via
120
+ * - If your implementation performs I/O, implement it as a `"use-step"` function (provided via
121
121
  * the builder) so results are durable and replay-safe.
122
122
  * - If it’s pure/deterministic, it can run in workflow context.
123
123
  */
@@ -1,15 +1,49 @@
1
1
  import { applyToolExecutionResultToParts } from "./story.toolcalls.js";
2
2
  import { executeReaction } from "./steps/reaction.steps.js";
3
3
  import { toolsToModelTools } from "./tools-to-model-tools.js";
4
- import { closeStoryStream, writeContextSubstate, writeStoryPing, writeToolOutputs } from "./steps/stream.steps.js";
4
+ import { closeStoryStream, writeContextSubstate, writeStoryLog, writeStoryPing, writeToolOutputs, } from "./steps/stream.steps.js";
5
5
  import { completeExecution, createReactionEvent, initializeContext, saveReactionEvent, saveTriggerEvent, updateContextContent, updateContextStatus, updateEvent, } from "./steps/store.steps.js";
6
- function storyEngineInfo(message, ...args) {
6
+ function safeLogArg(value) {
7
+ if (value === null)
8
+ return null;
9
+ if (value === undefined)
10
+ return null;
11
+ const t = typeof value;
12
+ if (t === "string" || t === "number" || t === "boolean")
13
+ return value;
14
+ if (t === "bigint")
15
+ return String(value);
16
+ try {
17
+ const seen = new WeakSet();
18
+ return JSON.stringify(value, (_k, v) => {
19
+ if (typeof v === "bigint")
20
+ return String(v);
21
+ if (typeof v === "string" && v.length > 5000)
22
+ return "[truncated-string]";
23
+ if (typeof v === "object" && v !== null) {
24
+ if (seen.has(v))
25
+ return "[circular]";
26
+ seen.add(v);
27
+ }
28
+ return v;
29
+ }, 2);
30
+ }
31
+ catch {
32
+ try {
33
+ return String(value);
34
+ }
35
+ catch {
36
+ return "[unserializable]";
37
+ }
38
+ }
39
+ }
40
+ async function storyEngineInfo(message, ...args) {
7
41
  // CRITICAL: static string log messages only. Dynamic values go in args.
8
- console.log(message, ...args);
42
+ await writeStoryLog({ level: "info", message, args: args.map(safeLogArg) });
9
43
  }
10
- function storyEngineDebug(message, ...args) {
44
+ async function storyEngineDebug(message, ...args) {
11
45
  // CRITICAL: static string log messages only. Dynamic values go in args.
12
- console.debug(message, ...args);
46
+ await writeStoryLog({ level: "debug", message, args: args.map(safeLogArg) });
13
47
  }
14
48
  export class Story {
15
49
  constructor(opts = {}) {
@@ -28,7 +62,7 @@ export class Story {
28
62
  * - This stage is ALWAYS executed by the engine.
29
63
  * - If you don't provide an implementation, the default behavior is an identity transform
30
64
  * (events pass through unchanged).
31
- * - If your implementation performs I/O, implement it as a `"use step"` function (provided via
65
+ * - If your implementation performs I/O, implement it as a `"use-step"` function (provided via
32
66
  * the builder) so results are durable and replay-safe.
33
67
  * - If it’s pure/deterministic, it can run in workflow context.
34
68
  */
@@ -67,17 +101,17 @@ export class Story {
67
101
  const preventClose = params.options?.preventClose ?? false;
68
102
  const sendFinish = params.options?.sendFinish ?? true;
69
103
  const silent = params.options?.silent ?? false;
70
- storyEngineInfo("[ekairos/story] story.engine react begin");
71
- storyEngineInfo("[ekairos/story] story.engine react contextIdentifier", params.contextIdentifier);
72
- storyEngineInfo("[ekairos/story] story.engine react maxIterations", maxIterations);
73
- storyEngineInfo("[ekairos/story] story.engine react maxModelSteps", maxModelSteps);
74
- storyEngineInfo("[ekairos/story] story.engine react silent", silent);
104
+ await storyEngineInfo("[ekairos/story] story.engine react begin");
105
+ await storyEngineInfo("[ekairos/story] story.engine react contextIdentifier", params.contextIdentifier);
106
+ await storyEngineInfo("[ekairos/story] story.engine react maxIterations", maxIterations);
107
+ await storyEngineInfo("[ekairos/story] story.engine react maxModelSteps", maxModelSteps);
108
+ await storyEngineInfo("[ekairos/story] story.engine react silent", silent);
75
109
  // 1) Ensure context exists (step)
76
110
  const ctxResult = await initializeContext(params.env, params.contextIdentifier, { silent });
77
111
  const currentContext = ctxResult.context;
78
- storyEngineInfo("[ekairos/story] story.engine initializeContext ok");
79
- storyEngineInfo("[ekairos/story] story.engine initializeContext contextId", currentContext.id);
80
- storyEngineInfo("[ekairos/story] story.engine initializeContext isNew", ctxResult.isNew);
112
+ await storyEngineInfo("[ekairos/story] story.engine initializeContext ok");
113
+ await storyEngineInfo("[ekairos/story] story.engine initializeContext contextId", currentContext.id);
114
+ await storyEngineInfo("[ekairos/story] story.engine initializeContext isNew", ctxResult.isNew);
81
115
  const contextSelector = params.contextIdentifier?.id
82
116
  ? { id: String(params.contextIdentifier.id) }
83
117
  : params.contextIdentifier?.key
@@ -89,16 +123,16 @@ export class Story {
89
123
  // 2) Persist trigger event + create execution shell (steps)
90
124
  const persistedTriggerEvent = await saveTriggerEvent(params.env, contextSelector, triggerEvent);
91
125
  const triggerEventId = persistedTriggerEvent.id;
92
- storyEngineInfo("[ekairos/story] story.engine saveTriggerEvent ok");
93
- storyEngineInfo("[ekairos/story] story.engine saveTriggerEvent triggerEventId", triggerEventId);
126
+ await storyEngineInfo("[ekairos/story] story.engine saveTriggerEvent ok");
127
+ await storyEngineInfo("[ekairos/story] story.engine saveTriggerEvent triggerEventId", triggerEventId);
94
128
  const { reactionEventId, executionId } = await createReactionEvent({
95
129
  env: params.env,
96
130
  contextIdentifier: contextSelector,
97
131
  triggerEventId,
98
132
  });
99
- storyEngineInfo("[ekairos/story] story.engine createReactionEvent ok");
100
- storyEngineInfo("[ekairos/story] story.engine createReactionEvent reactionEventId", reactionEventId);
101
- storyEngineInfo("[ekairos/story] story.engine createReactionEvent executionId", executionId);
133
+ await storyEngineInfo("[ekairos/story] story.engine createReactionEvent ok");
134
+ await storyEngineInfo("[ekairos/story] story.engine createReactionEvent reactionEventId", reactionEventId);
135
+ await storyEngineInfo("[ekairos/story] story.engine createReactionEvent executionId", executionId);
102
136
  // Emit a simple ping chunk early so clients can validate that streaming works end-to-end.
103
137
  // This should be ignored safely by clients that don't care about it.
104
138
  if (!silent) {
@@ -125,22 +159,22 @@ export class Story {
125
159
  };
126
160
  try {
127
161
  for (let iter = 0; iter < maxIterations; iter++) {
128
- storyEngineInfo("[ekairos/story] story.engine === LOOP ITERATION ===", iter);
162
+ await storyEngineInfo("[ekairos/story] story.engine === LOOP ITERATION ===", iter);
129
163
  // Hook: Story DSL `context()` (implemented by subclasses via `initialize()`)
130
- storyEngineInfo("[ekairos/story] >>> HOOK context() BEGIN", iter);
164
+ await storyEngineInfo("[ekairos/story] >>> HOOK context() BEGIN", iter);
131
165
  const nextContent = await this.initialize(updatedContext, params.env);
132
- storyEngineInfo("[ekairos/story] <<< HOOK context() END", iter);
166
+ await storyEngineInfo("[ekairos/story] <<< HOOK context() END", iter);
133
167
  updatedContext = await updateContextContent(params.env, contextSelector, nextContent);
134
- storyEngineInfo("[ekairos/story] story.engine updateContextContent ok");
168
+ await storyEngineInfo("[ekairos/story] story.engine updateContextContent ok");
135
169
  await this.opts.onContextUpdated?.({ env: params.env, context: updatedContext });
136
170
  // Hook: Story DSL `narrative()` (implemented by subclasses via `buildSystemPrompt()`)
137
- storyEngineInfo("[ekairos/story] >>> HOOK narrative() BEGIN", iter);
171
+ await storyEngineInfo("[ekairos/story] >>> HOOK narrative() BEGIN", iter);
138
172
  const systemPrompt = await this.buildSystemPrompt(updatedContext, params.env);
139
- storyEngineInfo("[ekairos/story] <<< HOOK narrative() END", iter);
173
+ await storyEngineInfo("[ekairos/story] <<< HOOK narrative() END", iter);
140
174
  // Hook: Story DSL `actions()` (implemented by subclasses via `buildTools()`)
141
- storyEngineInfo("[ekairos/story] >>> HOOK actions() BEGIN", iter);
175
+ await storyEngineInfo("[ekairos/story] >>> HOOK actions() BEGIN", iter);
142
176
  const toolsAll = await this.buildTools(updatedContext, params.env);
143
- storyEngineInfo("[ekairos/story] <<< HOOK actions() END", iter);
177
+ await storyEngineInfo("[ekairos/story] <<< HOOK actions() END", iter);
144
178
  // IMPORTANT: step args must be serializable.
145
179
  // Match DurableAgent behavior: convert tool input schemas to plain JSON Schema in workflow context.
146
180
  const toolsForModel = toolsToModelTools(toolsAll);
@@ -156,11 +190,11 @@ export class Story {
156
190
  sendStart: !silent && iter === 0 && reactionEvent === null,
157
191
  silent,
158
192
  });
159
- storyEngineInfo("[ekairos/story] story.engine executeReaction ok");
160
- storyEngineInfo("[ekairos/story] story.engine executeReaction toolCallsCount", toolCalls.length);
193
+ await storyEngineInfo("[ekairos/story] story.engine executeReaction ok");
194
+ await storyEngineInfo("[ekairos/story] story.engine executeReaction toolCallsCount", toolCalls.length);
161
195
  if (toolCalls.length) {
162
- storyEngineInfo("[ekairos/story] >>> TOOL_CALLS requested", toolCalls.map((tc) => tc?.toolName).filter(Boolean));
163
- storyEngineDebug("[ekairos/story] >>> TOOL_CALLS payload", toolCalls);
196
+ await storyEngineInfo("[ekairos/story] >>> TOOL_CALLS requested", toolCalls.map((tc) => tc?.toolName).filter(Boolean));
197
+ await storyEngineDebug("[ekairos/story] >>> TOOL_CALLS payload", toolCalls);
164
198
  }
165
199
  // Persist/append the assistant event for this iteration
166
200
  if (!reactionEvent) {
@@ -168,7 +202,7 @@ export class Story {
168
202
  ...assistantEvent,
169
203
  status: "pending",
170
204
  });
171
- storyEngineInfo("[ekairos/story] story.engine saveReactionEvent ok");
205
+ await storyEngineInfo("[ekairos/story] story.engine saveReactionEvent ok");
172
206
  }
173
207
  else {
174
208
  reactionEvent = await updateEvent(params.env, reactionEvent.id, {
@@ -181,14 +215,14 @@ export class Story {
181
215
  },
182
216
  status: "pending",
183
217
  });
184
- storyEngineInfo("[ekairos/story] story.engine updateEvent appendAssistantParts ok");
218
+ await storyEngineInfo("[ekairos/story] story.engine updateEvent appendAssistantParts ok");
185
219
  }
186
220
  this.opts.onEventCreated?.(assistantEvent);
187
221
  // Done: no tool calls requested by the model
188
222
  if (!toolCalls.length) {
189
- storyEngineInfo("[ekairos/story] >>> HOOK onEnd() BEGIN", iter);
223
+ await storyEngineInfo("[ekairos/story] >>> HOOK onEnd() BEGIN", iter);
190
224
  const endResult = await this.callOnEnd(assistantEvent);
191
- storyEngineInfo("[ekairos/story] <<< HOOK onEnd() END", iter, endResult);
225
+ await storyEngineInfo("[ekairos/story] <<< HOOK onEnd() END", iter, endResult);
192
226
  if (endResult) {
193
227
  // Mark reaction event completed
194
228
  await updateEvent(params.env, reactionEventId, {
@@ -216,7 +250,7 @@ export class Story {
216
250
  const executionResults = await Promise.all(toolCalls.map(async (tc) => {
217
251
  const toolDef = toolsAll[tc.toolName];
218
252
  if (!toolDef || typeof toolDef.execute !== "function") {
219
- storyEngineInfo("[ekairos/story] story.engine toolExecution missingTool", tc?.toolName);
253
+ await storyEngineInfo("[ekairos/story] story.engine toolExecution missingTool", tc?.toolName);
220
254
  return {
221
255
  tc,
222
256
  success: false,
@@ -224,8 +258,8 @@ export class Story {
224
258
  errorText: `Tool "${tc.toolName}" not found or has no execute().`,
225
259
  };
226
260
  }
227
- storyEngineInfo("[ekairos/story] >>> TOOL_EXEC BEGIN", tc?.toolName, tc?.toolCallId);
228
- storyEngineDebug("[ekairos/story] >>> TOOL_EXEC input", tc?.toolName, tc?.toolCallId, tc?.args);
261
+ await storyEngineInfo("[ekairos/story] >>> TOOL_EXEC BEGIN", tc?.toolName, tc?.toolCallId);
262
+ await storyEngineDebug("[ekairos/story] >>> TOOL_EXEC input", tc?.toolName, tc?.toolCallId, tc?.args);
229
263
  try {
230
264
  const output = await toolDef.execute(tc.args, {
231
265
  toolCallId: tc.toolCallId,
@@ -235,13 +269,13 @@ export class Story {
235
269
  triggerEventId,
236
270
  contextId: currentContext.id,
237
271
  });
238
- storyEngineInfo("[ekairos/story] <<< TOOL_EXEC OK", tc?.toolName, tc?.toolCallId);
239
- storyEngineDebug("[ekairos/story] <<< TOOL_EXEC output", tc?.toolName, tc?.toolCallId, output);
272
+ await storyEngineInfo("[ekairos/story] <<< TOOL_EXEC OK", tc?.toolName, tc?.toolCallId);
273
+ await storyEngineDebug("[ekairos/story] <<< TOOL_EXEC output", tc?.toolName, tc?.toolCallId, output);
240
274
  return { tc, success: true, output };
241
275
  }
242
276
  catch (e) {
243
- storyEngineInfo("[ekairos/story] <<< TOOL_EXEC FAILED", tc?.toolName, tc?.toolCallId);
244
- storyEngineDebug("[ekairos/story] <<< TOOL_EXEC error", tc?.toolName, tc?.toolCallId, e instanceof Error ? e.message : String(e));
277
+ await storyEngineInfo("[ekairos/story] <<< TOOL_EXEC FAILED", tc?.toolName, tc?.toolCallId);
278
+ await storyEngineDebug("[ekairos/story] <<< TOOL_EXEC error", tc?.toolName, tc?.toolCallId, e instanceof Error ? e.message : String(e));
245
279
  return {
246
280
  tc,
247
281
  success: false,
@@ -250,7 +284,7 @@ export class Story {
250
284
  };
251
285
  }
252
286
  }));
253
- storyEngineInfo("[ekairos/story] story.engine toolExecution resultsCount", executionResults.length);
287
+ await storyEngineInfo("[ekairos/story] story.engine toolExecution resultsCount", executionResults.length);
254
288
  // Emit tool outputs to the workflow stream (step)
255
289
  if (!silent) {
256
290
  await writeToolOutputs({
@@ -282,7 +316,7 @@ export class Story {
282
316
  content: { parts },
283
317
  status: "pending",
284
318
  });
285
- storyEngineInfo("[ekairos/story] story.engine updateEvent mergeToolResults ok");
319
+ await storyEngineInfo("[ekairos/story] story.engine updateEvent mergeToolResults ok");
286
320
  }
287
321
  // Callback for observability/integration
288
322
  for (const r of executionResults) {
@@ -298,7 +332,7 @@ export class Story {
298
332
  // Stop/continue boundary: allow the Story to decide if the loop should continue.
299
333
  // IMPORTANT: we call this after tool results have been merged into the persisted `reactionEvent`,
300
334
  // so stories can inspect `reactionEvent.content.parts` deterministically.
301
- storyEngineInfo("[ekairos/story] >>> HOOK shouldContinue() BEGIN", iter);
335
+ await storyEngineInfo("[ekairos/story] >>> HOOK shouldContinue() BEGIN", iter);
302
336
  const continueLoop = await this.shouldContinue({
303
337
  env: params.env,
304
338
  context: updatedContext,
@@ -307,7 +341,7 @@ export class Story {
307
341
  toolCalls,
308
342
  toolExecutionResults: executionResults,
309
343
  });
310
- storyEngineInfo("[ekairos/story] <<< HOOK shouldContinue() END", iter, continueLoop);
344
+ await storyEngineInfo("[ekairos/story] <<< HOOK shouldContinue() END", iter, continueLoop);
311
345
  if (continueLoop === false) {
312
346
  await updateEvent(params.env, reactionEventId, {
313
347
  ...reactionEvent,
@@ -11,7 +11,7 @@ export type SerializableToolForModel = {
11
11
  inputSchema: unknown;
12
12
  };
13
13
  /**
14
- * Convert AI SDK tools to a serializable representation that can be passed to `"use step"` functions.
14
+ * Convert AI SDK tools to a serializable representation that can be passed to `"use-step"` functions.
15
15
  *
16
16
  * This matches DurableAgent's internal `toolsToModelTools` behavior:
17
17
  * `inputSchema: asSchema(tool.inputSchema).jsonSchema`
@@ -1,6 +1,6 @@
1
1
  import { asSchema } from "ai";
2
2
  /**
3
- * Convert AI SDK tools to a serializable representation that can be passed to `"use step"` functions.
3
+ * Convert AI SDK tools to a serializable representation that can be passed to `"use-step"` functions.
4
4
  *
5
5
  * This matches DurableAgent's internal `toolsToModelTools` behavior:
6
6
  * `inputSchema: asSchema(tool.inputSchema).jsonSchema`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekairos/story",
3
- "version": "1.21.54-beta.0",
3
+ "version": "1.21.57-beta.0",
4
4
  "description": "Pulzar Story - Workflow-based AI Stories",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -24,6 +24,11 @@
24
24
  "require": "./dist/index.js",
25
25
  "default": "./dist/index.js"
26
26
  },
27
+ "./schema": {
28
+ "types": "./dist/schema.d.ts",
29
+ "import": "./dist/schema.js",
30
+ "default": "./dist/schema.js"
31
+ },
27
32
  "./runtime": {
28
33
  "types": "./dist/runtime.d.ts",
29
34
  "import": "./dist/runtime.js",
@@ -42,6 +47,9 @@
42
47
  },
43
48
  "typesVersions": {
44
49
  "*": {
50
+ "schema": [
51
+ "dist/schema.d.ts"
52
+ ],
45
53
  "runtime": [
46
54
  "dist/runtime.d.ts"
47
55
  ],
@@ -62,14 +70,14 @@
62
70
  },
63
71
  "dependencies": {
64
72
  "@ai-sdk/openai": "^2.0.52",
65
- "@ekairos/domain": "^1.21.54-beta.0",
73
+ "@ekairos/domain": "^1.21.57-beta.0",
66
74
  "@instantdb/admin": "^0.22.13",
67
75
  "@instantdb/core": "^0.22.13",
68
76
  "@vercel/sandbox": "^0.0.23",
69
77
  "ai": "^5.0.95",
70
78
  "ajv": "^8.17.1",
71
79
  "llamaindex": "^0.12.0",
72
- "workflow": "4.0.1-beta.13",
80
+ "workflow": "4.0.1-beta.41",
73
81
  "xmlbuilder2": "^3.1.1",
74
82
  "zod": "^4.1.8"
75
83
  },