@ekairos/story 1.21.56-beta.0 → 1.21.58-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.
|
@@ -7,6 +7,8 @@ export async function initializeContext(env, contextIdentifier, opts) {
|
|
|
7
7
|
"use step";
|
|
8
8
|
const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
|
|
9
9
|
const { store } = await resolveStoryRuntime(env);
|
|
10
|
+
console.log("[ekairos/story] story.engine react begin");
|
|
11
|
+
console.log("[ekairos/story] story.engine react contextIdentifier", contextIdentifier);
|
|
10
12
|
// Detect creation explicitly so the engine can run onContextCreated hooks.
|
|
11
13
|
let result;
|
|
12
14
|
if (!contextIdentifier) {
|
|
@@ -23,6 +25,9 @@ export async function initializeContext(env, contextIdentifier, opts) {
|
|
|
23
25
|
result = { context: created, isNew: true };
|
|
24
26
|
}
|
|
25
27
|
}
|
|
28
|
+
console.log("[ekairos/story] story.engine initializeContext ok");
|
|
29
|
+
console.log("[ekairos/story] story.engine initializeContext contextId", result.context.id);
|
|
30
|
+
console.log("[ekairos/story] story.engine initializeContext isNew", result.isNew);
|
|
26
31
|
// If we're running in a non-streaming context (e.g. tests or headless usage),
|
|
27
32
|
// we skip writing stream chunks entirely.
|
|
28
33
|
if (!opts?.silent) {
|
|
@@ -58,7 +63,10 @@ export async function saveTriggerEvent(env, contextIdentifier, event) {
|
|
|
58
63
|
"use step";
|
|
59
64
|
const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
|
|
60
65
|
const { store } = await resolveStoryRuntime(env);
|
|
61
|
-
|
|
66
|
+
const saved = await store.saveEvent(contextIdentifier, event);
|
|
67
|
+
console.log("[ekairos/story] story.engine saveTriggerEvent ok");
|
|
68
|
+
console.log("[ekairos/story] story.engine saveTriggerEvent triggerEventId", saved.id);
|
|
69
|
+
return saved;
|
|
62
70
|
}
|
|
63
71
|
export async function saveReactionEvent(env, contextIdentifier, event) {
|
|
64
72
|
"use step";
|
|
@@ -89,6 +97,9 @@ export async function createReactionEvent(params) {
|
|
|
89
97
|
: `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
90
98
|
await store.updateContextStatus(params.contextIdentifier, "streaming");
|
|
91
99
|
const execution = await store.createExecution(params.contextIdentifier, params.triggerEventId, reactionEventId);
|
|
100
|
+
console.log("[ekairos/story] story.engine createReactionEvent ok");
|
|
101
|
+
console.log("[ekairos/story] story.engine createReactionEvent reactionEventId", reactionEventId);
|
|
102
|
+
console.log("[ekairos/story] story.engine createReactionEvent executionId", execution.id);
|
|
92
103
|
return { reactionEventId, executionId: execution.id };
|
|
93
104
|
}
|
|
94
105
|
export async function completeExecution(env, contextIdentifier, executionId, status) {
|
|
@@ -1,8 +1,3 @@
|
|
|
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>;
|
|
6
1
|
export declare function writeContextSubstate(params: {
|
|
7
2
|
/**
|
|
8
3
|
* Ephemeral substate key for the UI (story engine internal state).
|
|
@@ -1,15 +1,3 @@
|
|
|
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
|
-
}
|
|
13
1
|
export async function writeContextSubstate(params) {
|
|
14
2
|
"use step";
|
|
15
3
|
const { getWritable } = await import("workflow");
|
package/dist/story.engine.js
CHANGED
|
@@ -1,50 +1,8 @@
|
|
|
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,
|
|
4
|
+
import { closeStoryStream, writeContextSubstate, 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 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) {
|
|
41
|
-
// CRITICAL: static string log messages only. Dynamic values go in args.
|
|
42
|
-
await writeStoryLog({ level: "info", message, args: args.map(safeLogArg) });
|
|
43
|
-
}
|
|
44
|
-
async function storyEngineDebug(message, ...args) {
|
|
45
|
-
// CRITICAL: static string log messages only. Dynamic values go in args.
|
|
46
|
-
await writeStoryLog({ level: "debug", message, args: args.map(safeLogArg) });
|
|
47
|
-
}
|
|
48
6
|
export class Story {
|
|
49
7
|
constructor(opts = {}) {
|
|
50
8
|
this.opts = opts;
|
|
@@ -101,17 +59,9 @@ export class Story {
|
|
|
101
59
|
const preventClose = params.options?.preventClose ?? false;
|
|
102
60
|
const sendFinish = params.options?.sendFinish ?? true;
|
|
103
61
|
const silent = params.options?.silent ?? false;
|
|
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);
|
|
109
62
|
// 1) Ensure context exists (step)
|
|
110
63
|
const ctxResult = await initializeContext(params.env, params.contextIdentifier, { silent });
|
|
111
64
|
const currentContext = ctxResult.context;
|
|
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);
|
|
115
65
|
const contextSelector = params.contextIdentifier?.id
|
|
116
66
|
? { id: String(params.contextIdentifier.id) }
|
|
117
67
|
: params.contextIdentifier?.key
|
|
@@ -123,16 +73,11 @@ export class Story {
|
|
|
123
73
|
// 2) Persist trigger event + create execution shell (steps)
|
|
124
74
|
const persistedTriggerEvent = await saveTriggerEvent(params.env, contextSelector, triggerEvent);
|
|
125
75
|
const triggerEventId = persistedTriggerEvent.id;
|
|
126
|
-
await storyEngineInfo("[ekairos/story] story.engine saveTriggerEvent ok");
|
|
127
|
-
await storyEngineInfo("[ekairos/story] story.engine saveTriggerEvent triggerEventId", triggerEventId);
|
|
128
76
|
const { reactionEventId, executionId } = await createReactionEvent({
|
|
129
77
|
env: params.env,
|
|
130
78
|
contextIdentifier: contextSelector,
|
|
131
79
|
triggerEventId,
|
|
132
80
|
});
|
|
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);
|
|
136
81
|
// Emit a simple ping chunk early so clients can validate that streaming works end-to-end.
|
|
137
82
|
// This should be ignored safely by clients that don't care about it.
|
|
138
83
|
if (!silent) {
|
|
@@ -159,22 +104,14 @@ export class Story {
|
|
|
159
104
|
};
|
|
160
105
|
try {
|
|
161
106
|
for (let iter = 0; iter < maxIterations; iter++) {
|
|
162
|
-
await storyEngineInfo("[ekairos/story] story.engine === LOOP ITERATION ===", iter);
|
|
163
107
|
// Hook: Story DSL `context()` (implemented by subclasses via `initialize()`)
|
|
164
|
-
await storyEngineInfo("[ekairos/story] >>> HOOK context() BEGIN", iter);
|
|
165
108
|
const nextContent = await this.initialize(updatedContext, params.env);
|
|
166
|
-
await storyEngineInfo("[ekairos/story] <<< HOOK context() END", iter);
|
|
167
109
|
updatedContext = await updateContextContent(params.env, contextSelector, nextContent);
|
|
168
|
-
await storyEngineInfo("[ekairos/story] story.engine updateContextContent ok");
|
|
169
110
|
await this.opts.onContextUpdated?.({ env: params.env, context: updatedContext });
|
|
170
111
|
// Hook: Story DSL `narrative()` (implemented by subclasses via `buildSystemPrompt()`)
|
|
171
|
-
await storyEngineInfo("[ekairos/story] >>> HOOK narrative() BEGIN", iter);
|
|
172
112
|
const systemPrompt = await this.buildSystemPrompt(updatedContext, params.env);
|
|
173
|
-
await storyEngineInfo("[ekairos/story] <<< HOOK narrative() END", iter);
|
|
174
113
|
// Hook: Story DSL `actions()` (implemented by subclasses via `buildTools()`)
|
|
175
|
-
await storyEngineInfo("[ekairos/story] >>> HOOK actions() BEGIN", iter);
|
|
176
114
|
const toolsAll = await this.buildTools(updatedContext, params.env);
|
|
177
|
-
await storyEngineInfo("[ekairos/story] <<< HOOK actions() END", iter);
|
|
178
115
|
// IMPORTANT: step args must be serializable.
|
|
179
116
|
// Match DurableAgent behavior: convert tool input schemas to plain JSON Schema in workflow context.
|
|
180
117
|
const toolsForModel = toolsToModelTools(toolsAll);
|
|
@@ -190,19 +127,12 @@ export class Story {
|
|
|
190
127
|
sendStart: !silent && iter === 0 && reactionEvent === null,
|
|
191
128
|
silent,
|
|
192
129
|
});
|
|
193
|
-
await storyEngineInfo("[ekairos/story] story.engine executeReaction ok");
|
|
194
|
-
await storyEngineInfo("[ekairos/story] story.engine executeReaction toolCallsCount", toolCalls.length);
|
|
195
|
-
if (toolCalls.length) {
|
|
196
|
-
await storyEngineInfo("[ekairos/story] >>> TOOL_CALLS requested", toolCalls.map((tc) => tc?.toolName).filter(Boolean));
|
|
197
|
-
await storyEngineDebug("[ekairos/story] >>> TOOL_CALLS payload", toolCalls);
|
|
198
|
-
}
|
|
199
130
|
// Persist/append the assistant event for this iteration
|
|
200
131
|
if (!reactionEvent) {
|
|
201
132
|
reactionEvent = await saveReactionEvent(params.env, contextSelector, {
|
|
202
133
|
...assistantEvent,
|
|
203
134
|
status: "pending",
|
|
204
135
|
});
|
|
205
|
-
await storyEngineInfo("[ekairos/story] story.engine saveReactionEvent ok");
|
|
206
136
|
}
|
|
207
137
|
else {
|
|
208
138
|
reactionEvent = await updateEvent(params.env, reactionEvent.id, {
|
|
@@ -215,14 +145,11 @@ export class Story {
|
|
|
215
145
|
},
|
|
216
146
|
status: "pending",
|
|
217
147
|
});
|
|
218
|
-
await storyEngineInfo("[ekairos/story] story.engine updateEvent appendAssistantParts ok");
|
|
219
148
|
}
|
|
220
149
|
this.opts.onEventCreated?.(assistantEvent);
|
|
221
150
|
// Done: no tool calls requested by the model
|
|
222
151
|
if (!toolCalls.length) {
|
|
223
|
-
await storyEngineInfo("[ekairos/story] >>> HOOK onEnd() BEGIN", iter);
|
|
224
152
|
const endResult = await this.callOnEnd(assistantEvent);
|
|
225
|
-
await storyEngineInfo("[ekairos/story] <<< HOOK onEnd() END", iter, endResult);
|
|
226
153
|
if (endResult) {
|
|
227
154
|
// Mark reaction event completed
|
|
228
155
|
await updateEvent(params.env, reactionEventId, {
|
|
@@ -250,7 +177,6 @@ export class Story {
|
|
|
250
177
|
const executionResults = await Promise.all(toolCalls.map(async (tc) => {
|
|
251
178
|
const toolDef = toolsAll[tc.toolName];
|
|
252
179
|
if (!toolDef || typeof toolDef.execute !== "function") {
|
|
253
|
-
await storyEngineInfo("[ekairos/story] story.engine toolExecution missingTool", tc?.toolName);
|
|
254
180
|
return {
|
|
255
181
|
tc,
|
|
256
182
|
success: false,
|
|
@@ -258,8 +184,6 @@ export class Story {
|
|
|
258
184
|
errorText: `Tool "${tc.toolName}" not found or has no execute().`,
|
|
259
185
|
};
|
|
260
186
|
}
|
|
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);
|
|
263
187
|
try {
|
|
264
188
|
const output = await toolDef.execute(tc.args, {
|
|
265
189
|
toolCallId: tc.toolCallId,
|
|
@@ -269,13 +193,9 @@ export class Story {
|
|
|
269
193
|
triggerEventId,
|
|
270
194
|
contextId: currentContext.id,
|
|
271
195
|
});
|
|
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);
|
|
274
196
|
return { tc, success: true, output };
|
|
275
197
|
}
|
|
276
198
|
catch (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));
|
|
279
199
|
return {
|
|
280
200
|
tc,
|
|
281
201
|
success: false,
|
|
@@ -284,7 +204,6 @@ export class Story {
|
|
|
284
204
|
};
|
|
285
205
|
}
|
|
286
206
|
}));
|
|
287
|
-
await storyEngineInfo("[ekairos/story] story.engine toolExecution resultsCount", executionResults.length);
|
|
288
207
|
// Emit tool outputs to the workflow stream (step)
|
|
289
208
|
if (!silent) {
|
|
290
209
|
await writeToolOutputs({
|
|
@@ -316,7 +235,6 @@ export class Story {
|
|
|
316
235
|
content: { parts },
|
|
317
236
|
status: "pending",
|
|
318
237
|
});
|
|
319
|
-
await storyEngineInfo("[ekairos/story] story.engine updateEvent mergeToolResults ok");
|
|
320
238
|
}
|
|
321
239
|
// Callback for observability/integration
|
|
322
240
|
for (const r of executionResults) {
|
|
@@ -332,7 +250,6 @@ export class Story {
|
|
|
332
250
|
// Stop/continue boundary: allow the Story to decide if the loop should continue.
|
|
333
251
|
// IMPORTANT: we call this after tool results have been merged into the persisted `reactionEvent`,
|
|
334
252
|
// so stories can inspect `reactionEvent.content.parts` deterministically.
|
|
335
|
-
await storyEngineInfo("[ekairos/story] >>> HOOK shouldContinue() BEGIN", iter);
|
|
336
253
|
const continueLoop = await this.shouldContinue({
|
|
337
254
|
env: params.env,
|
|
338
255
|
context: updatedContext,
|
|
@@ -341,7 +258,6 @@ export class Story {
|
|
|
341
258
|
toolCalls,
|
|
342
259
|
toolExecutionResults: executionResults,
|
|
343
260
|
});
|
|
344
|
-
await storyEngineInfo("[ekairos/story] <<< HOOK shouldContinue() END", iter, continueLoop);
|
|
345
261
|
if (continueLoop === false) {
|
|
346
262
|
await updateEvent(params.env, reactionEventId, {
|
|
347
263
|
...reactionEvent,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekairos/story",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.58-beta.0",
|
|
4
4
|
"description": "Pulzar Story - Workflow-based AI Stories",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"@ai-sdk/openai": "^2.0.52",
|
|
73
|
-
"@ekairos/domain": "^1.21.
|
|
73
|
+
"@ekairos/domain": "^1.21.58-beta.0",
|
|
74
74
|
"@instantdb/admin": "^0.22.13",
|
|
75
75
|
"@instantdb/core": "^0.22.13",
|
|
76
76
|
"@vercel/sandbox": "^0.0.23",
|