@ekairos/story 1.21.29-beta.0 → 1.21.32-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/README.md +174 -0
- package/dist/agent.builder.d.ts +52 -0
- package/dist/agent.builder.d.ts.map +1 -0
- package/dist/agent.builder.js +110 -0
- package/dist/agent.builder.js.map +1 -0
- package/dist/agent.d.ts +2 -119
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.engine.d.ts +75 -0
- package/dist/agent.engine.d.ts.map +1 -0
- package/dist/agent.engine.js +455 -0
- package/dist/agent.engine.js.map +1 -0
- package/dist/agent.js +8 -607
- package/dist/agent.js.map +1 -1
- package/dist/ekairos.config.d.ts +21 -0
- package/dist/ekairos.config.d.ts.map +1 -0
- package/dist/ekairos.config.js +26 -0
- package/dist/ekairos.config.js.map +1 -0
- package/dist/events.d.ts +11 -7
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +37 -210
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -25
- package/dist/index.js.map +1 -1
- package/dist/legacy.story.d.ts +5 -0
- package/dist/legacy.story.d.ts.map +1 -0
- package/dist/legacy.story.js +15 -0
- package/dist/legacy.story.js.map +1 -0
- package/dist/runtime.d.ts +12 -0
- package/dist/runtime.js +12 -0
- package/dist/schema-document.d.ts +0 -1
- package/dist/schema-document.js +14 -18
- package/dist/schema-document.js.map +1 -1
- package/dist/schema.d.ts +0 -1
- package/dist/schema.js +22 -26
- package/dist/schema.js.map +1 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +2 -1
- package/dist/service.js.map +1 -1
- package/dist/steps/do-story-stream-step.d.ts +29 -0
- package/dist/steps/do-story-stream-step.d.ts.map +1 -0
- package/dist/steps/do-story-stream-step.js +89 -0
- package/dist/steps/do-story-stream-step.js.map +1 -0
- package/dist/steps/index.d.ts +1 -3
- package/dist/steps/index.d.ts.map +1 -1
- package/dist/steps/index.js +3 -17
- package/dist/steps/index.js.map +1 -1
- package/dist/steps/store.steps.d.ts +43 -0
- package/dist/steps/store.steps.d.ts.map +1 -0
- package/dist/steps/store.steps.js +123 -0
- package/dist/steps/store.steps.js.map +1 -0
- package/dist/steps/story.steps.d.ts +35 -0
- package/dist/steps/story.steps.d.ts.map +1 -0
- package/dist/steps/story.steps.js +59 -0
- package/dist/steps/story.steps.js.map +1 -0
- package/dist/steps/stream.steps.d.ts +28 -0
- package/dist/steps/stream.steps.d.ts.map +1 -0
- package/dist/steps/stream.steps.js +75 -0
- package/dist/steps/stream.steps.js.map +1 -0
- package/dist/stores/instant.document-parser.d.ts +5 -0
- package/dist/stores/instant.document-parser.d.ts.map +1 -0
- package/dist/stores/instant.document-parser.js +116 -0
- package/dist/stores/instant.document-parser.js.map +1 -0
- package/dist/stores/instant.documents.d.ts +16 -0
- package/dist/stores/instant.documents.js +108 -0
- package/dist/stores/instant.store.d.ts +40 -0
- package/dist/stores/instant.store.d.ts.map +1 -0
- package/dist/stores/instant.store.js +207 -0
- package/dist/stores/instant.store.js.map +1 -0
- package/dist/story.builder.d.ts +116 -0
- package/dist/story.builder.d.ts.map +1 -0
- package/dist/story.builder.js +130 -0
- package/dist/story.builder.js.map +1 -0
- package/dist/story.config.d.ts +43 -0
- package/dist/story.config.d.ts.map +1 -0
- package/dist/story.config.js +57 -0
- package/dist/story.config.js.map +1 -0
- package/dist/story.d.ts +2 -50
- package/dist/story.d.ts.map +1 -1
- package/dist/story.engine.d.ts +174 -0
- package/dist/story.engine.d.ts.map +1 -0
- package/dist/story.engine.js +283 -0
- package/dist/story.engine.js.map +1 -0
- package/dist/story.js +6 -55
- package/dist/story.js.map +1 -1
- package/dist/story.legacy.d.ts +12 -0
- package/dist/story.legacy.d.ts.map +1 -0
- package/dist/story.legacy.js +15 -0
- package/dist/story.legacy.js.map +1 -0
- package/dist/story.registry.d.ts +21 -0
- package/dist/story.registry.d.ts.map +1 -0
- package/dist/story.registry.js +30 -0
- package/dist/story.registry.js.map +1 -0
- package/dist/story.store.d.ts +59 -0
- package/dist/story.store.d.ts.map +1 -0
- package/dist/story.store.js +1 -0
- package/dist/story.store.js.map +1 -0
- package/dist/story.streams.d.ts +55 -0
- package/dist/story.streams.d.ts.map +1 -0
- package/dist/story.streams.js +99 -0
- package/dist/story.streams.js.map +1 -0
- package/dist/story.toolcalls.d.ts +60 -0
- package/dist/story.toolcalls.d.ts.map +1 -0
- package/dist/story.toolcalls.js +73 -0
- package/dist/story.toolcalls.js.map +1 -0
- package/dist/tools-to-model-tools.d.ts +19 -0
- package/dist/tools-to-model-tools.js +21 -0
- package/dist/workflow.d.ts +20 -0
- package/dist/workflow.js +27 -0
- package/package.json +15 -4
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
let runtimeResolver = null;
|
|
2
|
+
function getRuntimeResolver() {
|
|
3
|
+
return runtimeResolver;
|
|
4
|
+
}
|
|
5
|
+
let runtimeBootstrap = null;
|
|
6
|
+
export function configureStoryRuntimeBootstrap(bootstrap) {
|
|
7
|
+
runtimeBootstrap = bootstrap;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Configure the story runtime resolver (global).
|
|
11
|
+
*
|
|
12
|
+
* This should be called once during initialization (module load) in your app.
|
|
13
|
+
* The resolver itself will be invoked inside steps, where it can safely create DB clients.
|
|
14
|
+
*/
|
|
15
|
+
export function configureStoryRuntime(resolver) {
|
|
16
|
+
runtimeResolver = resolver;
|
|
17
|
+
}
|
|
18
|
+
export function isStoryRuntimeConfigured() {
|
|
19
|
+
return Boolean(runtimeResolver);
|
|
20
|
+
}
|
|
21
|
+
export async function resolveStoryRuntime(env) {
|
|
22
|
+
if (!getRuntimeResolver()) {
|
|
23
|
+
// Best-effort: allow the step runtime to self-bootstrap once.
|
|
24
|
+
if (runtimeBootstrap) {
|
|
25
|
+
await runtimeBootstrap();
|
|
26
|
+
}
|
|
27
|
+
// Convention bootstrap (Next.js / monorepo apps):
|
|
28
|
+
// If the app exposes `src/ekairos.ts` and uses the `@/` alias, loading that module will
|
|
29
|
+
// run `ekairosConfig.setup()` which configures the resolver + bootstrap hook.
|
|
30
|
+
//
|
|
31
|
+
// This is intentionally ONLY attempted when runtime is missing, and is safe as long as
|
|
32
|
+
// `story.config` is not part of client bundles (see `@ekairos/story/runtime`).
|
|
33
|
+
if (!getRuntimeResolver()) {
|
|
34
|
+
try {
|
|
35
|
+
// @ts-expect-error - optional, app-provided convention module
|
|
36
|
+
await import("@/ekairos");
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// ignore: module missing / alias not configured
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// If bootstrap succeeded, proceed.
|
|
43
|
+
const resolver = getRuntimeResolver();
|
|
44
|
+
if (resolver)
|
|
45
|
+
return await resolver(env);
|
|
46
|
+
throw new Error([
|
|
47
|
+
"Story runtime is not configured.",
|
|
48
|
+
"",
|
|
49
|
+
"Convention:",
|
|
50
|
+
"- Create an app-level `ekairos.ts` that exports `ekairosConfig = createEkairosConfig({ runtime })`",
|
|
51
|
+
"- Ensure `ekairosConfig.setup()` runs in the step runtime (module load / worker boot).",
|
|
52
|
+
"",
|
|
53
|
+
"If you already have that file, ensure it is evaluated in the step runtime before calling story store steps.",
|
|
54
|
+
].join("\n"));
|
|
55
|
+
}
|
|
56
|
+
return await getRuntimeResolver()(env);
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"story.config.js","sourceRoot":"","sources":["../src/story.config.ts"],"names":[],"mappings":"AAyBA,IAAI,eAAe,GAAqC,IAAI,CAAA;AAiB5D,IAAI,gBAAgB,GAA4B,IAAI,CAAA;AAEpD,MAAM,UAAU,8BAA8B,CAAC,SAA2B;IACxE,gBAAgB,GAAG,SAAS,CAAA;AAC9B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAAmC;IAEnC,eAAe,GAAG,QAAe,CAAA;AACnC,CAAC;AAED,MAAM,UAAU,wBAAwB;IACtC,OAAO,OAAO,CAAC,eAAe,CAAC,CAAA;AACjC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,GAAqB;IAC7D,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,8DAA8D;QAC9D,IAAI,gBAAgB,EAAE,CAAC;YACrB,MAAM,gBAAgB,EAAE,CAAA;QAC1B,CAAC;QACD,MAAM,IAAI,KAAK,CACb;YACE,kCAAkC;YAClC,EAAE;YACF,aAAa;YACb,oGAAoG;YACpG,wFAAwF;YACxF,EAAE;YACF,6GAA6G;SAC9G,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAA;IACH,CAAC;IACD,OAAO,MAAM,eAAe,CAAC,GAAG,CAAC,CAAA;AACnC,CAAC","sourcesContent":["import type { StoryStore } from \"./story.store\"\r\n\r\n/**\r\n * ## story.config.ts\r\n *\r\n * Workflow-first story execution means we **cannot** keep non-serializable runtime objects\r\n * (database clients, network handles, SDK instances) inside workflow state.\r\n *\r\n * Instead, we centralize runtime construction behind a global resolver:\r\n * - The workflow passes a **serializable** `StoryEnvironment` into steps.\r\n * - Steps call `resolveStoryRuntime(env)` to instantiate the right store (Instant/Postgres/etc).\r\n *\r\n * This keeps `createStory(...)` purely declarative while still allowing per-tenant/per-org routing.\r\n */\r\n\r\nexport type StoryEnvironment = Record<string, unknown>\r\n\r\nexport type StoryRuntime = {\r\n store: StoryStore\r\n}\r\n\r\nexport type StoryRuntimeResolver<Env extends StoryEnvironment = StoryEnvironment> = (\r\n env: Env\r\n) => Promise<StoryRuntime> | StoryRuntime\r\n\r\nlet runtimeResolver: StoryRuntimeResolver<any> | null = null\r\n\r\n/**\r\n * Optional global bootstrap hook for step runtimes.\r\n *\r\n * Motivation:\r\n * - Workflow step runtimes may execute in a separate process/bundle.\r\n * - If the app's `ekairos.ts` was not evaluated in that runtime yet, the story runtime\r\n * resolver won't be configured and store steps will fail.\r\n *\r\n * Convention:\r\n * - Apps should have a single `ekairos.ts` that creates `ekairosConfig` and calls `ekairosConfig.setup()`.\r\n * - That module (or `createEkairosConfig().setup()`) can register this bootstrap function,\r\n * so library steps can \"pull\" initialization on-demand before first store access.\r\n */\r\ntype RuntimeBootstrap = () => void | Promise<void>\r\n\r\nlet runtimeBootstrap: RuntimeBootstrap | null = null\r\n\r\nexport function configureStoryRuntimeBootstrap(bootstrap: RuntimeBootstrap) {\r\n runtimeBootstrap = bootstrap\r\n}\r\n\r\n/**\r\n * Configure the story runtime resolver (global).\r\n *\r\n * This should be called once during initialization (module load) in your app.\r\n * The resolver itself will be invoked inside steps, where it can safely create DB clients.\r\n */\r\nexport function configureStoryRuntime<Env extends StoryEnvironment>(\r\n resolver: StoryRuntimeResolver<Env>,\r\n) {\r\n runtimeResolver = resolver as any\r\n}\r\n\r\nexport function isStoryRuntimeConfigured() {\r\n return Boolean(runtimeResolver)\r\n}\r\n\r\nexport async function resolveStoryRuntime(env: StoryEnvironment): Promise<StoryRuntime> {\r\n if (!runtimeResolver) {\r\n // Best-effort: allow the step runtime to self-bootstrap once.\r\n if (runtimeBootstrap) {\r\n await runtimeBootstrap()\r\n }\r\n throw new Error(\r\n [\r\n \"Story runtime is not configured.\",\r\n \"\",\r\n \"Convention:\",\r\n \"- Create an app-level `ekairos.ts` that exports `ekairosConfig = createEkairosConfig({ runtime })`\",\r\n \"- Ensure `ekairosConfig.setup()` runs in the step runtime (module load / worker boot).\",\r\n \"\",\r\n \"If you already have that file, ensure it is evaluated in the step runtime before calling story store steps.\",\r\n ].join(\"\\n\"),\r\n )\r\n }\r\n return await runtimeResolver(env)\r\n}\r\n\r\n\r\n\r\n"]}
|
package/dist/story.d.ts
CHANGED
|
@@ -1,50 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
export type FieldSchema = {
|
|
4
|
-
type: PrimitiveType;
|
|
5
|
-
description?: string;
|
|
6
|
-
required?: boolean;
|
|
7
|
-
properties?: Record<string, FieldSchema>;
|
|
8
|
-
items?: FieldSchema;
|
|
9
|
-
};
|
|
10
|
-
export type StepInputSchema = {
|
|
11
|
-
type: "object";
|
|
12
|
-
description?: string;
|
|
13
|
-
properties?: Record<string, FieldSchema>;
|
|
14
|
-
};
|
|
15
|
-
export type StoryActionSpec = {
|
|
16
|
-
name: string;
|
|
17
|
-
description: string;
|
|
18
|
-
implementationKey: string;
|
|
19
|
-
inputSchema?: StepInputSchema;
|
|
20
|
-
finalize?: boolean;
|
|
21
|
-
execute?: (args: any & {
|
|
22
|
-
contextId?: string;
|
|
23
|
-
}) => Promise<any>;
|
|
24
|
-
};
|
|
25
|
-
export type StoryOptions = {
|
|
26
|
-
reasoningEffort?: "low" | "medium" | "high";
|
|
27
|
-
webSearch?: boolean;
|
|
28
|
-
maxLoops?: number;
|
|
29
|
-
finalActions?: string[];
|
|
30
|
-
includeBaseTools?: {
|
|
31
|
-
createMessage?: boolean;
|
|
32
|
-
requestDirection?: boolean;
|
|
33
|
-
end?: boolean;
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
export type StoryConfig = {
|
|
37
|
-
narrative: string;
|
|
38
|
-
actions: StoryActionSpec[];
|
|
39
|
-
options?: StoryOptions;
|
|
40
|
-
};
|
|
41
|
-
export type StoryStartArgs = {
|
|
42
|
-
context?: ContextIdentifier | null;
|
|
43
|
-
trigger?: any | null;
|
|
44
|
-
};
|
|
45
|
-
export declare function story(key: string, config: StoryConfig): (args?: StoryStartArgs) => Promise<{
|
|
46
|
-
contextId: unknown;
|
|
47
|
-
status: "completed";
|
|
48
|
-
}>;
|
|
49
|
-
export type { StoryActionSpec as StoryAction, StoryOptions as StoryConfigOptions };
|
|
50
|
-
//# sourceMappingURL=story.d.ts.map
|
|
1
|
+
export { Story, type StoryOptions, type StoryStreamOptions, type ShouldContinue, type StoryShouldContinueArgs, } from "./story.engine";
|
|
2
|
+
export { story, createStory, type StoryConfig, type StoryInstance, type RegistrableStoryBuilder, } from "./story.builder";
|
package/dist/story.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"story.d.ts","sourceRoot":"","sources":["../src/story.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"story.d.ts","sourceRoot":"","sources":["../src/story.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,EACL,KAAK,YAAY,EACjB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EAEL,KAAK,EACL,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,uBAAuB,GAC7B,MAAM,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { Tool } from "ai";
|
|
2
|
+
import type { StoryEnvironment } from "./story.config";
|
|
3
|
+
import type { ContextEvent, ContextIdentifier, StoredContext } from "./story.store";
|
|
4
|
+
export interface StoryOptions<Context = any, Env extends StoryEnvironment = StoryEnvironment> {
|
|
5
|
+
onContextCreated?: (args: {
|
|
6
|
+
env: Env;
|
|
7
|
+
context: StoredContext<Context>;
|
|
8
|
+
}) => void | Promise<void>;
|
|
9
|
+
onContextUpdated?: (args: {
|
|
10
|
+
env: Env;
|
|
11
|
+
context: StoredContext<Context>;
|
|
12
|
+
}) => void | Promise<void>;
|
|
13
|
+
onEventCreated?: (event: ContextEvent) => void | Promise<void>;
|
|
14
|
+
onToolCallExecuted?: (executionEvent: any) => void | Promise<void>;
|
|
15
|
+
onEnd?: (lastEvent: ContextEvent) => void | {
|
|
16
|
+
end?: boolean;
|
|
17
|
+
} | Promise<void | {
|
|
18
|
+
end?: boolean;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
export interface StoryStreamOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Maximum loop iterations (LLM call → tool execution → repeat).
|
|
24
|
+
* Default: 20
|
|
25
|
+
*/
|
|
26
|
+
maxIterations?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Maximum model steps per LLM call.
|
|
29
|
+
* Default: 1 (or 5 if you override it in your implementation).
|
|
30
|
+
*/
|
|
31
|
+
maxModelSteps?: number;
|
|
32
|
+
/**
|
|
33
|
+
* If true, we do not close the workflow writable stream.
|
|
34
|
+
* Default: false.
|
|
35
|
+
*/
|
|
36
|
+
preventClose?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* If true, we write a `finish` chunk to the workflow stream.
|
|
39
|
+
* Default: true.
|
|
40
|
+
*/
|
|
41
|
+
sendFinish?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Model initializer (DurableAgent-style).
|
|
45
|
+
*
|
|
46
|
+
* - `string`: Vercel AI Gateway model id (e.g. `"openai/gpt-5"`), resolved inside the LLM step.
|
|
47
|
+
* - `function`: a function that returns a model instance. For Workflow compatibility, this should
|
|
48
|
+
* be a `"use step"` function (so it can be serialized by reference).
|
|
49
|
+
*/
|
|
50
|
+
export type StoryModelInit = string | (() => Promise<any>) | any;
|
|
51
|
+
export type StoryReactParams<Env extends StoryEnvironment = StoryEnvironment> = {
|
|
52
|
+
env: Env;
|
|
53
|
+
/**
|
|
54
|
+
* Context selector (exclusive: `{ id }` OR `{ key }`).
|
|
55
|
+
* If omitted/null, the story will create a new context.
|
|
56
|
+
*/
|
|
57
|
+
context?: ContextIdentifier | null;
|
|
58
|
+
options?: StoryStreamOptions;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* ## Story loop continuation signal
|
|
62
|
+
*
|
|
63
|
+
* This hook result is intentionally a **boolean** so stories can be extremely declarative:
|
|
64
|
+
*
|
|
65
|
+
* - `return true` => **continue** the durable loop
|
|
66
|
+
* - `return false` => **finalize** the durable loop
|
|
67
|
+
*
|
|
68
|
+
* (No imports required in callers.)
|
|
69
|
+
*/
|
|
70
|
+
export type ShouldContinue = boolean;
|
|
71
|
+
export type StoryShouldContinueArgs<Context = any, Env extends StoryEnvironment = StoryEnvironment> = {
|
|
72
|
+
env: Env;
|
|
73
|
+
context: StoredContext<Context>;
|
|
74
|
+
/**
|
|
75
|
+
* The persisted reaction event **so far** for the current streaming run.
|
|
76
|
+
*
|
|
77
|
+
* This contains the assistant's streamed parts as well as merged tool execution
|
|
78
|
+
* outcomes (e.g. `state: "output-available"` / `"output-error"`).
|
|
79
|
+
*
|
|
80
|
+
* Stories can inspect `reactionEvent.content.parts` to determine stop conditions
|
|
81
|
+
* (for example: when `tool-end` has an `output-available` state).
|
|
82
|
+
*/
|
|
83
|
+
reactionEvent: ContextEvent;
|
|
84
|
+
assistantEvent: ContextEvent;
|
|
85
|
+
toolCalls: any[];
|
|
86
|
+
toolExecutionResults: Array<{
|
|
87
|
+
tc: any;
|
|
88
|
+
success: boolean;
|
|
89
|
+
output: any;
|
|
90
|
+
errorText?: string;
|
|
91
|
+
}>;
|
|
92
|
+
};
|
|
93
|
+
export declare abstract class Story<Context, Env extends StoryEnvironment = StoryEnvironment> {
|
|
94
|
+
private opts;
|
|
95
|
+
constructor(opts?: StoryOptions<Context, Env>);
|
|
96
|
+
protected abstract initialize(context: StoredContext<Context>, env: Env): Promise<Context> | Context;
|
|
97
|
+
protected abstract buildSystemPrompt(context: StoredContext<Context>, env: Env): Promise<string> | string;
|
|
98
|
+
protected abstract buildTools(context: StoredContext<Context>, env: Env): Promise<Record<string, Tool>> | Record<string, Tool>;
|
|
99
|
+
/**
|
|
100
|
+
* First-class event expansion stage (runs on every iteration of the durable loop).
|
|
101
|
+
*
|
|
102
|
+
* Use this to expand/normalize events before they are converted into model messages.
|
|
103
|
+
* Typical use-cases:
|
|
104
|
+
* - Expand file/document references into text (LlamaCloud/Reducto/…)
|
|
105
|
+
* - Token compaction / summarization of older parts
|
|
106
|
+
* - Attaching derived context snippets to the next model call
|
|
107
|
+
*
|
|
108
|
+
* IMPORTANT:
|
|
109
|
+
* - This stage is ALWAYS executed by the engine.
|
|
110
|
+
* - If you don't provide an implementation, the default behavior is an identity transform
|
|
111
|
+
* (events pass through unchanged).
|
|
112
|
+
* - If your implementation performs I/O, implement it as a `"use step"` function (provided via
|
|
113
|
+
* the builder) so results are durable and replay-safe.
|
|
114
|
+
* - If it’s pure/deterministic, it can run in workflow context.
|
|
115
|
+
*/
|
|
116
|
+
protected expandEvents(events: ContextEvent[], _context: StoredContext<Context>, _env: Env): Promise<ContextEvent[]>;
|
|
117
|
+
protected getModel(_context: StoredContext<Context>, _env: Env): StoryModelInit;
|
|
118
|
+
/**
|
|
119
|
+
* Story stop/continue hook.
|
|
120
|
+
*
|
|
121
|
+
* After the model streamed and tools executed, the story can decide whether the loop should
|
|
122
|
+
* continue.
|
|
123
|
+
*
|
|
124
|
+
* Default: `true` (continue).
|
|
125
|
+
*/
|
|
126
|
+
protected shouldContinue(_args: StoryShouldContinueArgs<Context, Env>): Promise<ShouldContinue>;
|
|
127
|
+
/**
|
|
128
|
+
* Workflow-first execution entrypoint.
|
|
129
|
+
*
|
|
130
|
+
* - Streaming is written to the workflow run's output stream.
|
|
131
|
+
* - All I/O is delegated to steps (store access, LLM streaming, stream writes).
|
|
132
|
+
* - This method returns metadata only.
|
|
133
|
+
*/
|
|
134
|
+
/**
|
|
135
|
+
* React to an incoming event and advance the story.
|
|
136
|
+
*
|
|
137
|
+
* This is the primary workflow entrypoint.
|
|
138
|
+
*/
|
|
139
|
+
react(triggerEvent: ContextEvent, params: StoryReactParams<Env>): Promise<{
|
|
140
|
+
contextId: string;
|
|
141
|
+
context: StoredContext<Context>;
|
|
142
|
+
triggerEventId: string;
|
|
143
|
+
reactionEventId: string;
|
|
144
|
+
executionId: string;
|
|
145
|
+
}>;
|
|
146
|
+
/**
|
|
147
|
+
* @deprecated Back-compat: old object-style call signature.
|
|
148
|
+
*/
|
|
149
|
+
react(params: {
|
|
150
|
+
env: Env;
|
|
151
|
+
/** @deprecated Use `triggerEvent` */
|
|
152
|
+
incomingEvent?: ContextEvent;
|
|
153
|
+
triggerEvent?: ContextEvent;
|
|
154
|
+
contextIdentifier: ContextIdentifier | null;
|
|
155
|
+
options?: StoryStreamOptions;
|
|
156
|
+
}): Promise<{
|
|
157
|
+
contextId: string;
|
|
158
|
+
context: StoredContext<Context>;
|
|
159
|
+
triggerEventId: string;
|
|
160
|
+
reactionEventId: string;
|
|
161
|
+
executionId: string;
|
|
162
|
+
}>;
|
|
163
|
+
/**
|
|
164
|
+
* @deprecated Use `react()` instead. Kept for backwards compatibility.
|
|
165
|
+
*/
|
|
166
|
+
stream(triggerEvent: ContextEvent, params: StoryReactParams<Env>): Promise<{
|
|
167
|
+
contextId: string;
|
|
168
|
+
context: StoredContext<Context>;
|
|
169
|
+
triggerEventId: string;
|
|
170
|
+
reactionEventId: string;
|
|
171
|
+
executionId: string;
|
|
172
|
+
}>;
|
|
173
|
+
private callOnEnd;
|
|
174
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"story.engine.d.ts","sourceRoot":"","sources":["../src/story.engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,283 @@
|
|
|
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";
|
|
6
|
+
export class Story {
|
|
7
|
+
constructor(opts = {}) {
|
|
8
|
+
this.opts = opts;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* First-class event expansion stage (runs on every iteration of the durable loop).
|
|
12
|
+
*
|
|
13
|
+
* Use this to expand/normalize events before they are converted into model messages.
|
|
14
|
+
* Typical use-cases:
|
|
15
|
+
* - Expand file/document references into text (LlamaCloud/Reducto/…)
|
|
16
|
+
* - Token compaction / summarization of older parts
|
|
17
|
+
* - Attaching derived context snippets to the next model call
|
|
18
|
+
*
|
|
19
|
+
* IMPORTANT:
|
|
20
|
+
* - This stage is ALWAYS executed by the engine.
|
|
21
|
+
* - If you don't provide an implementation, the default behavior is an identity transform
|
|
22
|
+
* (events pass through unchanged).
|
|
23
|
+
* - If your implementation performs I/O, implement it as a `"use step"` function (provided via
|
|
24
|
+
* the builder) so results are durable and replay-safe.
|
|
25
|
+
* - If it’s pure/deterministic, it can run in workflow context.
|
|
26
|
+
*/
|
|
27
|
+
async expandEvents(events, _context, _env) {
|
|
28
|
+
return events;
|
|
29
|
+
}
|
|
30
|
+
getModel(_context, _env) {
|
|
31
|
+
return "openai/gpt-5";
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Story stop/continue hook.
|
|
35
|
+
*
|
|
36
|
+
* After the model streamed and tools executed, the story can decide whether the loop should
|
|
37
|
+
* continue.
|
|
38
|
+
*
|
|
39
|
+
* Default: `true` (continue).
|
|
40
|
+
*/
|
|
41
|
+
async shouldContinue(_args) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
async react(incomingEventOrParams, paramsMaybe) {
|
|
45
|
+
const params = typeof incomingEventOrParams?.type === "string" && paramsMaybe
|
|
46
|
+
? {
|
|
47
|
+
env: paramsMaybe.env,
|
|
48
|
+
triggerEvent: incomingEventOrParams,
|
|
49
|
+
contextIdentifier: paramsMaybe.context ?? null,
|
|
50
|
+
options: paramsMaybe.options,
|
|
51
|
+
}
|
|
52
|
+
: incomingEventOrParams;
|
|
53
|
+
const triggerEvent = params.triggerEvent ?? params.incomingEvent;
|
|
54
|
+
if (!triggerEvent) {
|
|
55
|
+
throw new Error("Story.react: triggerEvent is required");
|
|
56
|
+
}
|
|
57
|
+
const maxIterations = params.options?.maxIterations ?? 20;
|
|
58
|
+
const maxModelSteps = params.options?.maxModelSteps ?? 1;
|
|
59
|
+
const preventClose = params.options?.preventClose ?? false;
|
|
60
|
+
const sendFinish = params.options?.sendFinish ?? true;
|
|
61
|
+
// 1) Ensure context exists (step)
|
|
62
|
+
const ctxResult = await ensureContextAndEmitContextId(params.env, params.contextIdentifier);
|
|
63
|
+
const currentContext = ctxResult.context;
|
|
64
|
+
const contextSelector = params.contextIdentifier?.id
|
|
65
|
+
? { id: String(params.contextIdentifier.id) }
|
|
66
|
+
: params.contextIdentifier?.key
|
|
67
|
+
? { key: params.contextIdentifier.key }
|
|
68
|
+
: { id: String(currentContext.id) };
|
|
69
|
+
if (ctxResult.isNew) {
|
|
70
|
+
await this.opts.onContextCreated?.({ env: params.env, context: currentContext });
|
|
71
|
+
}
|
|
72
|
+
// 2) Persist trigger event + create execution shell (steps)
|
|
73
|
+
const persistedTriggerEvent = await saveEvent(params.env, contextSelector, triggerEvent);
|
|
74
|
+
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;
|
|
79
|
+
let reactionEvent = null;
|
|
80
|
+
// Latest persisted context state for this run (we keep it in memory; store is updated via steps).
|
|
81
|
+
let updatedContext = currentContext;
|
|
82
|
+
const failExecution = async () => {
|
|
83
|
+
try {
|
|
84
|
+
await completeExecution(params.env, contextSelector, executionId, "failed");
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// noop
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
await closeStoryStream({ preventClose, sendFinish });
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// noop
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
try {
|
|
97
|
+
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)
|
|
100
|
+
const nextContent = await this.initialize(updatedContext, params.env);
|
|
101
|
+
updatedContext = await updateContextContent(params.env, contextSelector, nextContent);
|
|
102
|
+
await this.opts.onContextUpdated?.({ env: params.env, context: updatedContext });
|
|
103
|
+
const systemPrompt = await this.buildSystemPrompt(updatedContext, params.env);
|
|
104
|
+
const toolsAll = await this.buildTools(updatedContext, params.env);
|
|
105
|
+
// IMPORTANT: step args must be serializable.
|
|
106
|
+
// Match DurableAgent behavior: convert tool input schemas to plain JSON Schema in workflow context.
|
|
107
|
+
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({
|
|
111
|
+
model: this.getModel(updatedContext, params.env),
|
|
112
|
+
system: systemPrompt,
|
|
113
|
+
messages: messagesForModel,
|
|
114
|
+
tools: toolsForModel,
|
|
115
|
+
eventId: reactionEventId,
|
|
116
|
+
maxSteps: maxModelSteps,
|
|
117
|
+
// Only emit a `start` chunk once per story turn.
|
|
118
|
+
sendStart: iter === 0 && reactionEvent === null,
|
|
119
|
+
});
|
|
120
|
+
// Persist/append the assistant event for this iteration
|
|
121
|
+
if (!reactionEvent) {
|
|
122
|
+
reactionEvent = await saveEvent(params.env, contextSelector, {
|
|
123
|
+
...assistantEvent,
|
|
124
|
+
status: "pending",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
reactionEvent = await updateEvent(params.env, reactionEvent.id, {
|
|
129
|
+
...reactionEvent,
|
|
130
|
+
content: {
|
|
131
|
+
parts: [
|
|
132
|
+
...(reactionEvent?.content?.parts ?? []),
|
|
133
|
+
...(assistantEvent?.content?.parts ?? []),
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
status: "pending",
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
this.opts.onEventCreated?.(assistantEvent);
|
|
140
|
+
// Done: no tool calls requested by the model
|
|
141
|
+
if (!toolCalls.length) {
|
|
142
|
+
const endResult = await this.callOnEnd(assistantEvent);
|
|
143
|
+
if (endResult) {
|
|
144
|
+
// Mark reaction event completed
|
|
145
|
+
await updateEvent(params.env, reactionEventId, {
|
|
146
|
+
...reactionEvent,
|
|
147
|
+
status: "completed",
|
|
148
|
+
});
|
|
149
|
+
await updateContextStatus(params.env, contextSelector, "open");
|
|
150
|
+
await completeExecution(params.env, contextSelector, executionId, "completed");
|
|
151
|
+
await closeStoryStream({ preventClose, sendFinish });
|
|
152
|
+
return {
|
|
153
|
+
contextId: currentContext.id,
|
|
154
|
+
context: updatedContext,
|
|
155
|
+
triggerEventId,
|
|
156
|
+
reactionEventId,
|
|
157
|
+
executionId,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Execute tool calls (workflow context; tool implementations decide step vs workflow)
|
|
162
|
+
if (toolCalls.length) {
|
|
163
|
+
await writeContextSubstate({ key: "actions", transient: true });
|
|
164
|
+
}
|
|
165
|
+
const executionResults = await Promise.all(toolCalls.map(async (tc) => {
|
|
166
|
+
const toolDef = toolsAll[tc.toolName];
|
|
167
|
+
if (!toolDef || typeof toolDef.execute !== "function") {
|
|
168
|
+
return {
|
|
169
|
+
tc,
|
|
170
|
+
success: false,
|
|
171
|
+
output: null,
|
|
172
|
+
errorText: `Tool "${tc.toolName}" not found or has no execute().`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const output = await toolDef.execute(tc.args, {
|
|
177
|
+
toolCallId: tc.toolCallId,
|
|
178
|
+
messages: messagesForModel,
|
|
179
|
+
eventId: reactionEventId,
|
|
180
|
+
executionId,
|
|
181
|
+
triggerEventId,
|
|
182
|
+
contextId: currentContext.id,
|
|
183
|
+
});
|
|
184
|
+
return { tc, success: true, output };
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
return {
|
|
188
|
+
tc,
|
|
189
|
+
success: false,
|
|
190
|
+
output: null,
|
|
191
|
+
errorText: e instanceof Error ? e.message : String(e),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}));
|
|
195
|
+
// 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
|
+
});
|
|
201
|
+
// Clear action status once tool execution results have been emitted.
|
|
202
|
+
if (toolCalls.length) {
|
|
203
|
+
await writeContextSubstate({ key: null, transient: true });
|
|
204
|
+
}
|
|
205
|
+
// Merge tool results into persisted parts (so next LLM call can see them)
|
|
206
|
+
if (reactionEvent) {
|
|
207
|
+
let parts = reactionEvent?.content?.parts ?? [];
|
|
208
|
+
for (const r of executionResults) {
|
|
209
|
+
parts = applyToolExecutionResultToParts(parts, r.tc, {
|
|
210
|
+
success: Boolean(r.success),
|
|
211
|
+
result: r.output,
|
|
212
|
+
message: r.errorText,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
reactionEvent = await updateEvent(params.env, reactionEventId, {
|
|
216
|
+
...reactionEvent,
|
|
217
|
+
content: { parts },
|
|
218
|
+
status: "pending",
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
// Callback for observability/integration
|
|
222
|
+
for (const r of executionResults) {
|
|
223
|
+
await this.opts.onToolCallExecuted?.({
|
|
224
|
+
toolCall: r.tc,
|
|
225
|
+
success: r.success,
|
|
226
|
+
output: r.output,
|
|
227
|
+
errorText: r.errorText,
|
|
228
|
+
eventId: reactionEventId,
|
|
229
|
+
executionId,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
// Stop/continue boundary: allow the Story to decide if the loop should continue.
|
|
233
|
+
// IMPORTANT: we call this after tool results have been merged into the persisted `reactionEvent`,
|
|
234
|
+
// so stories can inspect `reactionEvent.content.parts` deterministically.
|
|
235
|
+
const continueLoop = await this.shouldContinue({
|
|
236
|
+
env: params.env,
|
|
237
|
+
context: updatedContext,
|
|
238
|
+
reactionEvent: reactionEvent ?? assistantEvent,
|
|
239
|
+
assistantEvent,
|
|
240
|
+
toolCalls,
|
|
241
|
+
toolExecutionResults: executionResults,
|
|
242
|
+
});
|
|
243
|
+
if (continueLoop === false) {
|
|
244
|
+
await updateEvent(params.env, reactionEventId, {
|
|
245
|
+
...reactionEvent,
|
|
246
|
+
status: "completed",
|
|
247
|
+
});
|
|
248
|
+
await updateContextStatus(params.env, contextSelector, "open");
|
|
249
|
+
await completeExecution(params.env, contextSelector, executionId, "completed");
|
|
250
|
+
await closeStoryStream({ preventClose, sendFinish });
|
|
251
|
+
return {
|
|
252
|
+
contextId: currentContext.id,
|
|
253
|
+
context: updatedContext,
|
|
254
|
+
triggerEventId,
|
|
255
|
+
reactionEventId,
|
|
256
|
+
executionId,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
throw new Error(`Story: maxIterations reached (${maxIterations}) without completion`);
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
await failExecution();
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* @deprecated Use `react()` instead. Kept for backwards compatibility.
|
|
269
|
+
*/
|
|
270
|
+
async stream(triggerEvent, params) {
|
|
271
|
+
return await this.react(triggerEvent, params);
|
|
272
|
+
}
|
|
273
|
+
async callOnEnd(lastEvent) {
|
|
274
|
+
if (!this.opts.onEnd)
|
|
275
|
+
return true;
|
|
276
|
+
const result = await this.opts.onEnd(lastEvent);
|
|
277
|
+
if (typeof result === "boolean")
|
|
278
|
+
return result;
|
|
279
|
+
if (result && typeof result === "object" && "end" in result)
|
|
280
|
+
return Boolean(result.end);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"story.engine.js","sourceRoot":"","sources":["../src/story.engine.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ModelMessage, Tool, UIMessageChunk } from \"ai\"\r\n\r\nimport type { StoryEnvironment } from \"./story.config\"\r\nimport type { ContextEvent, ContextIdentifier, StoredContext } from \"./story.store\"\r\nimport { applyToolExecutionResultToParts } from \"./story.toolcalls\"\r\n\r\nimport { doStoryStreamStep } from \"./steps/do-story-stream-step\"\r\nimport { closeStoryStream, writeContextIdChunk, writeToolOutputs } from \"./steps/stream.steps\"\r\nimport {\r\n completeExecution,\r\n createExecution,\r\n eventsToModelMessages,\r\n generateId,\r\n getContext,\r\n getEvents,\r\n getOrCreateContext,\r\n saveEvent,\r\n updateContextContent,\r\n updateContextStatus,\r\n updateEvent,\r\n} from \"./steps/store.steps\"\r\n\r\nexport interface StoryOptions<Context = any, Env extends StoryEnvironment = StoryEnvironment> {\r\n onContextCreated?: (args: { env: Env; context: StoredContext<Context> }) => void | Promise<void>\r\n onContextUpdated?: (args: { env: Env; context: StoredContext<Context> }) => void | Promise<void>\r\n onEventCreated?: (event: ContextEvent) => void | Promise<void>\r\n onToolCallExecuted?: (executionEvent: any) => void | Promise<void>\r\n onEnd?: (\r\n lastEvent: ContextEvent,\r\n ) => void | { end?: boolean } | Promise<void | { end?: boolean }>\r\n}\r\n\r\nexport interface StoryStreamOptions {\r\n /**\r\n * Maximum loop iterations (LLM call → tool execution → repeat).\r\n * Default: 20\r\n */\r\n maxIterations?: number\r\n\r\n /**\r\n * Maximum model steps per LLM call.\r\n * Default: 1 (or 5 if you override it in your implementation).\r\n */\r\n maxModelSteps?: number\r\n\r\n /**\r\n * If true, we do not close the workflow writable stream.\r\n * Default: false.\r\n */\r\n preventClose?: boolean\r\n\r\n /**\r\n * If true, we write a `finish` chunk to the workflow stream.\r\n * Default: true.\r\n */\r\n sendFinish?: boolean\r\n}\r\n\r\nexport abstract class Story<Context, Env extends StoryEnvironment = StoryEnvironment> {\r\n constructor(private opts: StoryOptions<Context, Env> = {}) {}\r\n\r\n protected abstract initialize(\r\n context: StoredContext<Context>,\r\n env: Env,\r\n ): Promise<Context> | Context\r\n\r\n protected abstract buildSystemPrompt(\r\n context: StoredContext<Context>,\r\n env: Env,\r\n ): Promise<string> | string\r\n\r\n protected abstract buildTools(\r\n context: StoredContext<Context>,\r\n env: Env,\r\n ): Promise<Record<string, Tool>> | Record<string, Tool>\r\n\r\n /**\r\n * First-class event expansion stage (runs on every iteration of the durable loop).\r\n *\r\n * Use this to expand/normalize events before they are converted into model messages.\r\n * Typical use-cases:\r\n * - Expand file/document references into text (LlamaCloud/Reducto/…)\r\n * - Token compaction / summarization of older parts\r\n * - Attaching derived context snippets to the next model call\r\n *\r\n * IMPORTANT:\r\n * - This stage is ALWAYS executed by the engine.\r\n * - If you don't provide an implementation, the default behavior is an identity transform\r\n * (events pass through unchanged).\r\n * - If your implementation performs I/O, implement it as a `\"use step\"` function (provided via\r\n * the builder) so results are durable and replay-safe.\r\n * - If it’s pure/deterministic, it can run in workflow context.\r\n */\r\n protected async expandEvents(\r\n events: ContextEvent[],\r\n _context: StoredContext<Context>,\r\n _env: Env,\r\n ): Promise<ContextEvent[]> {\r\n return events\r\n }\r\n\r\n protected getModel(_context: StoredContext<Context>, _env: Env): any {\r\n return \"openai/gpt-5\"\r\n }\r\n\r\n /**\r\n * Return tool names that should terminate the loop after they execute.\r\n * This mirrors the durable-agent style where “finalization” is a tool-driven boundary.\r\n */\r\n protected async getFinalizationToolNames(): Promise<string[]> {\r\n return []\r\n }\r\n\r\n /**\r\n * Workflow-first execution entrypoint.\r\n *\r\n * - The workflow owns the stream (`writable`).\r\n * - All I/O is delegated to steps (store access, LLM streaming, stream writes).\r\n * - This method returns metadata only.\r\n */\r\n public async stream(params: {\r\n env: Env\r\n incomingEvent: ContextEvent\r\n contextIdentifier: ContextIdentifier | null\r\n writable: WritableStream<UIMessageChunk>\r\n options?: StoryStreamOptions\r\n }) {\r\n const maxIterations = params.options?.maxIterations ?? 20\r\n const maxModelSteps = params.options?.maxModelSteps ?? 1\r\n const preventClose = params.options?.preventClose ?? false\r\n const sendFinish = params.options?.sendFinish ?? true\r\n\r\n // 1) Ensure context exists (step)\r\n const ctxResult = await getOrCreateContext<Context>(\r\n params.env,\r\n params.contextIdentifier,\r\n )\r\n const currentContext = ctxResult.context\r\n\r\n const contextSelector: ContextIdentifier =\r\n params.contextIdentifier?.id\r\n ? { id: String(params.contextIdentifier.id) }\r\n : params.contextIdentifier?.key\r\n ? { key: params.contextIdentifier.key }\r\n : { id: String(currentContext.id) }\r\n\r\n // 1.5) Tell the client which context/thread to use (workflow-owned writable, written from a step).\r\n await writeContextIdChunk({ writable: params.writable, contextId: String(currentContext.id) })\r\n\r\n if (ctxResult.isNew) {\r\n await this.opts.onContextCreated?.({ env: params.env, context: currentContext })\r\n }\r\n\r\n // 2) Persist trigger event + create execution shell (steps)\r\n const triggerEvent = await saveEvent(params.env, contextSelector, params.incomingEvent)\r\n const triggerEventId = triggerEvent.id\r\n\r\n const reactionEventId = await generateId()\r\n\r\n await updateContextStatus(params.env, contextSelector, \"streaming\")\r\n const execution = await createExecution(params.env, contextSelector, triggerEventId, reactionEventId)\r\n const executionId = execution.id\r\n\r\n let reactionEvent: ContextEvent | null = null\r\n\r\n const failExecution = async () => {\r\n try {\r\n await completeExecution(params.env, contextSelector, executionId, \"failed\")\r\n } catch {\r\n // noop\r\n }\r\n try {\r\n await closeStoryStream({ writable: params.writable, preventClose, sendFinish })\r\n } catch {\r\n // noop\r\n }\r\n }\r\n\r\n try {\r\n for (let iter = 0; iter < maxIterations; iter++) {\r\n const ctxRow = await getContext<Context>(params.env, contextSelector)\r\n if (!ctxRow) throw new Error(\"Story: context not found\")\r\n\r\n // Normalize/initialize context (workflow-level; may call steps if needed)\r\n const nextContent = await this.initialize(ctxRow, params.env)\r\n const updatedContext = await updateContextContent<Context>(\r\n params.env,\r\n contextSelector,\r\n nextContent,\r\n )\r\n\r\n await this.opts.onContextUpdated?.({ env: params.env, context: updatedContext })\r\n\r\n const systemPrompt = await this.buildSystemPrompt(updatedContext, params.env)\r\n const toolsAll = await this.buildTools(updatedContext, params.env)\r\n\r\n // Strip execute before sending to the model (manual tool loop)\r\n const toolsForModel: Record<string, Tool> = {}\r\n for (const [name, t] of Object.entries(toolsAll)) {\r\n toolsForModel[name] = { ...(t as any) }\r\n delete (toolsForModel[name] as any).execute\r\n }\r\n\r\n const events = await getEvents(params.env, contextSelector)\r\n const expandedEvents = await this.expandEvents(events, updatedContext, params.env)\r\n const messagesForModel: ModelMessage[] = await eventsToModelMessages(\r\n params.env,\r\n expandedEvents,\r\n )\r\n\r\n const { assistantEvent, toolCalls } = await doStoryStreamStep({\r\n model: this.getModel(updatedContext, params.env),\r\n system: systemPrompt,\r\n messages: messagesForModel,\r\n tools: toolsForModel,\r\n writable: params.writable,\r\n eventId: reactionEventId,\r\n maxSteps: maxModelSteps,\r\n })\r\n\r\n // Persist/append the assistant event for this iteration\r\n if (!reactionEvent) {\r\n reactionEvent = await saveEvent(params.env, contextSelector, {\r\n ...(assistantEvent as any),\r\n status: \"pending\",\r\n })\r\n } else {\r\n reactionEvent = await updateEvent(params.env, reactionEvent.id, {\r\n ...(reactionEvent as any),\r\n content: {\r\n parts: [\r\n ...((reactionEvent as any)?.content?.parts ?? []),\r\n ...((assistantEvent as any)?.content?.parts ?? []),\r\n ],\r\n },\r\n status: \"pending\",\r\n } as any)\r\n }\r\n\r\n this.opts.onEventCreated?.(assistantEvent)\r\n\r\n // Done: no tool calls requested by the model\r\n if (!toolCalls.length) {\r\n const endResult = await this.callOnEnd(assistantEvent)\r\n if (endResult) {\r\n // Mark reaction event completed\r\n await updateEvent(params.env, reactionEventId, {\r\n ...(reactionEvent as any),\r\n status: \"completed\",\r\n } as any)\r\n await updateContextStatus(params.env, contextSelector, \"open\")\r\n await completeExecution(params.env, contextSelector, executionId, \"completed\")\r\n await closeStoryStream({ writable: params.writable, preventClose, sendFinish })\r\n return {\r\n contextId: currentContext.id,\r\n triggerEventId,\r\n reactionEventId,\r\n executionId,\r\n }\r\n }\r\n }\r\n\r\n // Execute tool calls (workflow context; tool implementations decide step vs workflow)\r\n const executionResults = await Promise.all(\r\n toolCalls.map(async (tc: any) => {\r\n const toolDef = (toolsAll as any)[tc.toolName] as any\r\n if (!toolDef || typeof toolDef.execute !== \"function\") {\r\n return {\r\n tc,\r\n success: false,\r\n output: null,\r\n errorText: `Tool \"${tc.toolName}\" not found or has no execute().`,\r\n }\r\n }\r\n try {\r\n const output = await toolDef.execute(tc.args, {\r\n toolCallId: tc.toolCallId,\r\n messages: messagesForModel,\r\n })\r\n return { tc, success: true, output }\r\n } catch (e: any) {\r\n return {\r\n tc,\r\n success: false,\r\n output: null,\r\n errorText: e instanceof Error ? e.message : String(e),\r\n }\r\n }\r\n }),\r\n )\r\n\r\n // Emit tool outputs to the workflow stream (step)\r\n await writeToolOutputs({\r\n writable: params.writable,\r\n results: executionResults.map((r: any) =>\r\n r.success\r\n ? ({ toolCallId: r.tc.toolCallId, success: true, output: r.output } as const)\r\n : ({ toolCallId: r.tc.toolCallId, success: false, errorText: r.errorText } as const),\r\n ),\r\n })\r\n\r\n // Merge tool results into persisted parts (so next LLM call can see them)\r\n if (reactionEvent) {\r\n let parts = (reactionEvent as any)?.content?.parts ?? []\r\n for (const r of executionResults as any[]) {\r\n parts = applyToolExecutionResultToParts(parts, r.tc, {\r\n success: Boolean(r.success),\r\n result: r.output,\r\n message: r.errorText,\r\n })\r\n }\r\n\r\n reactionEvent = await updateEvent(params.env, reactionEventId, {\r\n ...(reactionEvent as any),\r\n content: { parts },\r\n status: \"pending\",\r\n } as any)\r\n }\r\n\r\n // Callback for observability/integration\r\n for (const r of executionResults as any[]) {\r\n await this.opts.onToolCallExecuted?.({\r\n toolCall: r.tc,\r\n success: r.success,\r\n output: r.output,\r\n errorText: r.errorText,\r\n eventId: reactionEventId,\r\n executionId,\r\n })\r\n }\r\n\r\n // Finalization boundary\r\n const finalToolNames = await this.getFinalizationToolNames()\r\n const shouldFinalize = (executionResults as any[]).some((r) =>\r\n finalToolNames.includes(r.tc.toolName),\r\n )\r\n\r\n if (shouldFinalize) {\r\n await updateEvent(params.env, reactionEventId, {\r\n ...(reactionEvent as any),\r\n status: \"completed\",\r\n } as any)\r\n await updateContextStatus(params.env, contextSelector, \"open\")\r\n await completeExecution(params.env, contextSelector, executionId, \"completed\")\r\n await closeStoryStream({ writable: params.writable, preventClose, sendFinish })\r\n return {\r\n contextId: currentContext.id,\r\n triggerEventId,\r\n reactionEventId,\r\n executionId,\r\n }\r\n }\r\n }\r\n\r\n throw new Error(`Story: maxIterations reached (${maxIterations}) without completion`)\r\n } catch (error) {\r\n await failExecution()\r\n throw error\r\n }\r\n }\r\n\r\n private async callOnEnd(lastEvent: ContextEvent): Promise<boolean> {\r\n if (!this.opts.onEnd) return true\r\n const result = await this.opts.onEnd(lastEvent)\r\n if (typeof result === \"boolean\") return result\r\n if (result && typeof result === \"object\" && \"end\" in result) return Boolean((result as any).end)\r\n return true\r\n }\r\n}\r\n\r\n\r\n\r\n"]}
|