@ekairos/story 1.21.53-beta.0 → 1.21.56-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 +11 -5
- package/dist/runtime.step.d.ts +10 -0
- package/dist/runtime.step.js +4 -0
- package/dist/steps/do-story-stream-step.d.ts +1 -1
- package/dist/steps/do-story-stream-step.js +3 -3
- package/dist/steps/reaction.steps.d.ts +1 -1
- package/dist/steps/reaction.steps.js +4 -5
- package/dist/steps/store.steps.d.ts +1 -1
- package/dist/steps/store.steps.js +10 -2
- package/dist/steps/stream.steps.d.ts +5 -0
- package/dist/steps/stream.steps.js +17 -1
- package/dist/story.builder.d.ts +2 -2
- package/dist/story.engine.d.ts +2 -2
- package/dist/story.engine.js +80 -46
- package/dist/tools-to-model-tools.d.ts +1 -1
- package/dist/tools-to-model-tools.js +1 -1
- package/package.json +11 -3
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("//") ||
|
|
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
|
|
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>;
|
|
@@ -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 {
|
|
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 {
|
|
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
|
-
|
|
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();
|
package/dist/story.builder.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
*/
|
package/dist/story.engine.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
*/
|
package/dist/story.engine.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
3
|
+
"version": "1.21.56-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.
|
|
73
|
+
"@ekairos/domain": "^1.21.56-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.
|
|
80
|
+
"workflow": "4.0.1-beta.41",
|
|
73
81
|
"xmlbuilder2": "^3.1.1",
|
|
74
82
|
"zod": "^4.1.8"
|
|
75
83
|
},
|