@ekairos/story 1.21.41-beta.0 → 1.21.52-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.
@@ -1,6 +1,6 @@
1
1
  import type { Tool } from "ai";
2
- import type { StoryEnvironment } from "./story.config";
3
- import type { ContextEvent, ContextIdentifier, StoredContext } from "./story.store";
2
+ import type { StoryEnvironment } from "./story.config.js";
3
+ import type { ContextEvent, ContextIdentifier, StoredContext } from "./story.store.js";
4
4
  export interface StoryOptions<Context = any, Env extends StoryEnvironment = StoryEnvironment> {
5
5
  onContextCreated?: (args: {
6
6
  env: Env;
@@ -39,6 +39,14 @@ export interface StoryStreamOptions {
39
39
  * Default: true.
40
40
  */
41
41
  sendFinish?: boolean;
42
+ /**
43
+ * If true, the story loop runs silently (no UI streaming output).
44
+ *
45
+ * Persistence (contexts/events/executions) still happens normally.
46
+ *
47
+ * Default: false.
48
+ */
49
+ silent?: boolean;
42
50
  }
43
51
  /**
44
52
  * Model initializer (DurableAgent-style).
@@ -1,8 +1,16 @@
1
- import { applyToolExecutionResultToParts } from "./story.toolcalls";
2
- import { doStoryStreamStep } from "./steps/do-story-stream-step";
3
- import { toolsToModelTools } from "./tools-to-model-tools";
4
- import { closeStoryStream, writeContextSubstate, writeToolOutputs } from "./steps/stream.steps";
5
- import { completeExecution, createExecution, eventsToModelMessages, generateId, ensureContextAndEmitContextId, getEvents, saveEvent, updateContextContent, updateContextStatus, updateEvent, } from "./steps/store.steps";
1
+ import { applyToolExecutionResultToParts } from "./story.toolcalls.js";
2
+ import { executeReaction } from "./steps/reaction.steps.js";
3
+ import { toolsToModelTools } from "./tools-to-model-tools.js";
4
+ import { closeStoryStream, writeContextSubstate, writeStoryPing, writeToolOutputs } from "./steps/stream.steps.js";
5
+ import { completeExecution, createReactionEvent, initializeContext, saveReactionEvent, saveTriggerEvent, updateContextContent, updateContextStatus, updateEvent, } from "./steps/store.steps.js";
6
+ function storyEngineInfo(message, ...args) {
7
+ // CRITICAL: static string log messages only. Dynamic values go in args.
8
+ console.log(message, ...args);
9
+ }
10
+ function storyEngineDebug(message, ...args) {
11
+ // CRITICAL: static string log messages only. Dynamic values go in args.
12
+ console.debug(message, ...args);
13
+ }
6
14
  export class Story {
7
15
  constructor(opts = {}) {
8
16
  this.opts = opts;
@@ -58,9 +66,18 @@ export class Story {
58
66
  const maxModelSteps = params.options?.maxModelSteps ?? 1;
59
67
  const preventClose = params.options?.preventClose ?? false;
60
68
  const sendFinish = params.options?.sendFinish ?? true;
69
+ 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);
61
75
  // 1) Ensure context exists (step)
62
- const ctxResult = await ensureContextAndEmitContextId(params.env, params.contextIdentifier);
76
+ const ctxResult = await initializeContext(params.env, params.contextIdentifier, { silent });
63
77
  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);
64
81
  const contextSelector = params.contextIdentifier?.id
65
82
  ? { id: String(params.contextIdentifier.id) }
66
83
  : params.contextIdentifier?.key
@@ -70,12 +87,23 @@ export class Story {
70
87
  await this.opts.onContextCreated?.({ env: params.env, context: currentContext });
71
88
  }
72
89
  // 2) Persist trigger event + create execution shell (steps)
73
- const persistedTriggerEvent = await saveEvent(params.env, contextSelector, triggerEvent);
90
+ const persistedTriggerEvent = await saveTriggerEvent(params.env, contextSelector, triggerEvent);
74
91
  const triggerEventId = persistedTriggerEvent.id;
75
- const reactionEventId = await generateId();
76
- await updateContextStatus(params.env, contextSelector, "streaming");
77
- const execution = await createExecution(params.env, contextSelector, triggerEventId, reactionEventId);
78
- const executionId = execution.id;
92
+ storyEngineInfo("[ekairos/story] story.engine saveTriggerEvent ok");
93
+ storyEngineInfo("[ekairos/story] story.engine saveTriggerEvent triggerEventId", triggerEventId);
94
+ const { reactionEventId, executionId } = await createReactionEvent({
95
+ env: params.env,
96
+ contextIdentifier: contextSelector,
97
+ triggerEventId,
98
+ });
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);
102
+ // Emit a simple ping chunk early so clients can validate that streaming works end-to-end.
103
+ // This should be ignored safely by clients that don't care about it.
104
+ if (!silent) {
105
+ await writeStoryPing({ label: "story-start" });
106
+ }
79
107
  let reactionEvent = null;
80
108
  // Latest persisted context state for this run (we keep it in memory; store is updated via steps).
81
109
  let updatedContext = currentContext;
@@ -87,7 +115,9 @@ export class Story {
87
115
  // noop
88
116
  }
89
117
  try {
90
- await closeStoryStream({ preventClose, sendFinish });
118
+ if (!silent) {
119
+ await closeStoryStream({ preventClose, sendFinish });
120
+ }
91
121
  }
92
122
  catch {
93
123
  // noop
@@ -95,34 +125,50 @@ export class Story {
95
125
  };
96
126
  try {
97
127
  for (let iter = 0; iter < maxIterations; iter++) {
98
- const events = await getEvents(params.env, contextSelector);
99
- // Normalize/initialize context (workflow-level; may call steps if needed)
128
+ storyEngineInfo("[ekairos/story] story.engine === LOOP ITERATION ===", iter);
129
+ // Hook: Story DSL `context()` (implemented by subclasses via `initialize()`)
130
+ storyEngineInfo("[ekairos/story] >>> HOOK context() BEGIN", iter);
100
131
  const nextContent = await this.initialize(updatedContext, params.env);
132
+ storyEngineInfo("[ekairos/story] <<< HOOK context() END", iter);
101
133
  updatedContext = await updateContextContent(params.env, contextSelector, nextContent);
134
+ storyEngineInfo("[ekairos/story] story.engine updateContextContent ok");
102
135
  await this.opts.onContextUpdated?.({ env: params.env, context: updatedContext });
136
+ // Hook: Story DSL `narrative()` (implemented by subclasses via `buildSystemPrompt()`)
137
+ storyEngineInfo("[ekairos/story] >>> HOOK narrative() BEGIN", iter);
103
138
  const systemPrompt = await this.buildSystemPrompt(updatedContext, params.env);
139
+ storyEngineInfo("[ekairos/story] <<< HOOK narrative() END", iter);
140
+ // Hook: Story DSL `actions()` (implemented by subclasses via `buildTools()`)
141
+ storyEngineInfo("[ekairos/story] >>> HOOK actions() BEGIN", iter);
104
142
  const toolsAll = await this.buildTools(updatedContext, params.env);
143
+ storyEngineInfo("[ekairos/story] <<< HOOK actions() END", iter);
105
144
  // IMPORTANT: step args must be serializable.
106
145
  // Match DurableAgent behavior: convert tool input schemas to plain JSON Schema in workflow context.
107
146
  const toolsForModel = toolsToModelTools(toolsAll);
108
- const expandedEvents = await this.expandEvents(events, updatedContext, params.env);
109
- const messagesForModel = await eventsToModelMessages(params.env, expandedEvents);
110
- const { assistantEvent, toolCalls } = await doStoryStreamStep({
147
+ const { assistantEvent, toolCalls, messagesForModel } = await executeReaction({
148
+ env: params.env,
149
+ contextIdentifier: contextSelector,
111
150
  model: this.getModel(updatedContext, params.env),
112
151
  system: systemPrompt,
113
- messages: messagesForModel,
114
152
  tools: toolsForModel,
115
153
  eventId: reactionEventId,
116
154
  maxSteps: maxModelSteps,
117
155
  // Only emit a `start` chunk once per story turn.
118
- sendStart: iter === 0 && reactionEvent === null,
156
+ sendStart: !silent && iter === 0 && reactionEvent === null,
157
+ silent,
119
158
  });
159
+ storyEngineInfo("[ekairos/story] story.engine executeReaction ok");
160
+ storyEngineInfo("[ekairos/story] story.engine executeReaction toolCallsCount", toolCalls.length);
161
+ 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);
164
+ }
120
165
  // Persist/append the assistant event for this iteration
121
166
  if (!reactionEvent) {
122
- reactionEvent = await saveEvent(params.env, contextSelector, {
167
+ reactionEvent = await saveReactionEvent(params.env, contextSelector, {
123
168
  ...assistantEvent,
124
169
  status: "pending",
125
170
  });
171
+ storyEngineInfo("[ekairos/story] story.engine saveReactionEvent ok");
126
172
  }
127
173
  else {
128
174
  reactionEvent = await updateEvent(params.env, reactionEvent.id, {
@@ -135,11 +181,14 @@ export class Story {
135
181
  },
136
182
  status: "pending",
137
183
  });
184
+ storyEngineInfo("[ekairos/story] story.engine updateEvent appendAssistantParts ok");
138
185
  }
139
186
  this.opts.onEventCreated?.(assistantEvent);
140
187
  // Done: no tool calls requested by the model
141
188
  if (!toolCalls.length) {
189
+ storyEngineInfo("[ekairos/story] >>> HOOK onEnd() BEGIN", iter);
142
190
  const endResult = await this.callOnEnd(assistantEvent);
191
+ storyEngineInfo("[ekairos/story] <<< HOOK onEnd() END", iter, endResult);
143
192
  if (endResult) {
144
193
  // Mark reaction event completed
145
194
  await updateEvent(params.env, reactionEventId, {
@@ -148,7 +197,9 @@ export class Story {
148
197
  });
149
198
  await updateContextStatus(params.env, contextSelector, "open");
150
199
  await completeExecution(params.env, contextSelector, executionId, "completed");
151
- await closeStoryStream({ preventClose, sendFinish });
200
+ if (!silent) {
201
+ await closeStoryStream({ preventClose, sendFinish });
202
+ }
152
203
  return {
153
204
  contextId: currentContext.id,
154
205
  context: updatedContext,
@@ -159,12 +210,13 @@ export class Story {
159
210
  }
160
211
  }
161
212
  // Execute tool calls (workflow context; tool implementations decide step vs workflow)
162
- if (toolCalls.length) {
213
+ if (!silent && toolCalls.length) {
163
214
  await writeContextSubstate({ key: "actions", transient: true });
164
215
  }
165
216
  const executionResults = await Promise.all(toolCalls.map(async (tc) => {
166
217
  const toolDef = toolsAll[tc.toolName];
167
218
  if (!toolDef || typeof toolDef.execute !== "function") {
219
+ storyEngineInfo("[ekairos/story] story.engine toolExecution missingTool", tc?.toolName);
168
220
  return {
169
221
  tc,
170
222
  success: false,
@@ -172,6 +224,8 @@ export class Story {
172
224
  errorText: `Tool "${tc.toolName}" not found or has no execute().`,
173
225
  };
174
226
  }
227
+ storyEngineInfo("[ekairos/story] >>> TOOL_EXEC BEGIN", tc?.toolName, tc?.toolCallId);
228
+ storyEngineDebug("[ekairos/story] >>> TOOL_EXEC input", tc?.toolName, tc?.toolCallId, tc?.args);
175
229
  try {
176
230
  const output = await toolDef.execute(tc.args, {
177
231
  toolCallId: tc.toolCallId,
@@ -181,9 +235,13 @@ export class Story {
181
235
  triggerEventId,
182
236
  contextId: currentContext.id,
183
237
  });
238
+ storyEngineInfo("[ekairos/story] <<< TOOL_EXEC OK", tc?.toolName, tc?.toolCallId);
239
+ storyEngineDebug("[ekairos/story] <<< TOOL_EXEC output", tc?.toolName, tc?.toolCallId, output);
184
240
  return { tc, success: true, output };
185
241
  }
186
242
  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));
187
245
  return {
188
246
  tc,
189
247
  success: false,
@@ -192,14 +250,21 @@ export class Story {
192
250
  };
193
251
  }
194
252
  }));
253
+ storyEngineInfo("[ekairos/story] story.engine toolExecution resultsCount", executionResults.length);
195
254
  // Emit tool outputs to the workflow stream (step)
196
- await writeToolOutputs({
197
- results: executionResults.map((r) => r.success
198
- ? { toolCallId: r.tc.toolCallId, success: true, output: r.output }
199
- : { toolCallId: r.tc.toolCallId, success: false, errorText: r.errorText }),
200
- });
255
+ if (!silent) {
256
+ await writeToolOutputs({
257
+ results: executionResults.map((r) => r.success
258
+ ? { toolCallId: r.tc.toolCallId, success: true, output: r.output }
259
+ : {
260
+ toolCallId: r.tc.toolCallId,
261
+ success: false,
262
+ errorText: r.errorText,
263
+ }),
264
+ });
265
+ }
201
266
  // Clear action status once tool execution results have been emitted.
202
- if (toolCalls.length) {
267
+ if (!silent && toolCalls.length) {
203
268
  await writeContextSubstate({ key: null, transient: true });
204
269
  }
205
270
  // Merge tool results into persisted parts (so next LLM call can see them)
@@ -217,6 +282,7 @@ export class Story {
217
282
  content: { parts },
218
283
  status: "pending",
219
284
  });
285
+ storyEngineInfo("[ekairos/story] story.engine updateEvent mergeToolResults ok");
220
286
  }
221
287
  // Callback for observability/integration
222
288
  for (const r of executionResults) {
@@ -232,6 +298,7 @@ export class Story {
232
298
  // Stop/continue boundary: allow the Story to decide if the loop should continue.
233
299
  // IMPORTANT: we call this after tool results have been merged into the persisted `reactionEvent`,
234
300
  // so stories can inspect `reactionEvent.content.parts` deterministically.
301
+ storyEngineInfo("[ekairos/story] >>> HOOK shouldContinue() BEGIN", iter);
235
302
  const continueLoop = await this.shouldContinue({
236
303
  env: params.env,
237
304
  context: updatedContext,
@@ -240,6 +307,7 @@ export class Story {
240
307
  toolCalls,
241
308
  toolExecutionResults: executionResults,
242
309
  });
310
+ storyEngineInfo("[ekairos/story] <<< HOOK shouldContinue() END", iter, continueLoop);
243
311
  if (continueLoop === false) {
244
312
  await updateEvent(params.env, reactionEventId, {
245
313
  ...reactionEvent,
@@ -247,7 +315,9 @@ export class Story {
247
315
  });
248
316
  await updateContextStatus(params.env, contextSelector, "open");
249
317
  await completeExecution(params.env, contextSelector, executionId, "completed");
250
- await closeStoryStream({ preventClose, sendFinish });
318
+ if (!silent) {
319
+ await closeStoryStream({ preventClose, sendFinish });
320
+ }
251
321
  return {
252
322
  contextId: currentContext.id,
253
323
  context: updatedContext,
package/dist/story.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export {
2
2
  // engine
3
- Story, } from "./story.engine";
3
+ Story, } from "./story.engine.js";
4
4
  export {
5
5
  // builder
6
- story, createStory, } from "./story.builder";
6
+ story, createStory, } from "./story.builder.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekairos/story",
3
- "version": "1.21.41-beta.0",
3
+ "version": "1.21.52-beta.0",
4
4
  "description": "Pulzar Story - Workflow-based AI Stories",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -21,6 +21,7 @@
21
21
  ".": {
22
22
  "types": "./dist/index.d.ts",
23
23
  "import": "./dist/index.js",
24
+ "require": "./dist/index.js",
24
25
  "default": "./dist/index.js"
25
26
  },
26
27
  "./runtime": {
@@ -39,6 +40,19 @@
39
40
  "default": "./dist/stores/instant.store.js"
40
41
  }
41
42
  },
43
+ "typesVersions": {
44
+ "*": {
45
+ "runtime": [
46
+ "dist/runtime.d.ts"
47
+ ],
48
+ "next": [
49
+ "dist/next.d.ts"
50
+ ],
51
+ "instant": [
52
+ "dist/stores/instant.store.d.ts"
53
+ ]
54
+ }
55
+ },
42
56
  "scripts": {
43
57
  "build": "tsc -p tsconfig.json",
44
58
  "dev": "tsc -p tsconfig.json --watch",
@@ -48,7 +62,7 @@
48
62
  },
49
63
  "dependencies": {
50
64
  "@ai-sdk/openai": "^2.0.52",
51
- "@ekairos/domain": "^1.21.41-beta.0",
65
+ "@ekairos/domain": "^1.21.52-beta.0",
52
66
  "@instantdb/admin": "^0.22.13",
53
67
  "@instantdb/core": "^0.22.13",
54
68
  "@vercel/sandbox": "^0.0.23",