@ekairos/thread 1.21.88-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.
Files changed (86) hide show
  1. package/README.md +363 -0
  2. package/dist/codex.d.ts +95 -0
  3. package/dist/codex.js +91 -0
  4. package/dist/env.d.ts +12 -0
  5. package/dist/env.js +62 -0
  6. package/dist/events.d.ts +35 -0
  7. package/dist/events.js +102 -0
  8. package/dist/index.d.ts +9 -0
  9. package/dist/index.js +9 -0
  10. package/dist/mcp.d.ts +1 -0
  11. package/dist/mcp.js +1 -0
  12. package/dist/mirror.d.ts +41 -0
  13. package/dist/mirror.js +1 -0
  14. package/dist/oidc.d.ts +7 -0
  15. package/dist/oidc.js +25 -0
  16. package/dist/polyfills/dom-events.d.ts +1 -0
  17. package/dist/polyfills/dom-events.js +89 -0
  18. package/dist/react.d.ts +62 -0
  19. package/dist/react.js +101 -0
  20. package/dist/runtime.d.ts +17 -0
  21. package/dist/runtime.js +23 -0
  22. package/dist/runtime.step.d.ts +9 -0
  23. package/dist/runtime.step.js +7 -0
  24. package/dist/schema.d.ts +2 -0
  25. package/dist/schema.js +200 -0
  26. package/dist/steps/do-story-stream-step.d.ts +29 -0
  27. package/dist/steps/do-story-stream-step.js +89 -0
  28. package/dist/steps/do-thread-stream-step.d.ts +29 -0
  29. package/dist/steps/do-thread-stream-step.js +90 -0
  30. package/dist/steps/mirror.steps.d.ts +6 -0
  31. package/dist/steps/mirror.steps.js +48 -0
  32. package/dist/steps/reaction.steps.d.ts +43 -0
  33. package/dist/steps/reaction.steps.js +354 -0
  34. package/dist/steps/store.steps.d.ts +98 -0
  35. package/dist/steps/store.steps.js +512 -0
  36. package/dist/steps/stream.steps.d.ts +41 -0
  37. package/dist/steps/stream.steps.js +99 -0
  38. package/dist/steps/trace.steps.d.ts +37 -0
  39. package/dist/steps/trace.steps.js +265 -0
  40. package/dist/stores/instant.document-parser.d.ts +6 -0
  41. package/dist/stores/instant.document-parser.js +210 -0
  42. package/dist/stores/instant.documents.d.ts +16 -0
  43. package/dist/stores/instant.documents.js +152 -0
  44. package/dist/stores/instant.store.d.ts +78 -0
  45. package/dist/stores/instant.store.js +530 -0
  46. package/dist/story.actions.d.ts +60 -0
  47. package/dist/story.actions.js +120 -0
  48. package/dist/story.builder.d.ts +115 -0
  49. package/dist/story.builder.js +130 -0
  50. package/dist/story.config.d.ts +54 -0
  51. package/dist/story.config.js +125 -0
  52. package/dist/story.d.ts +2 -0
  53. package/dist/story.engine.d.ts +224 -0
  54. package/dist/story.engine.js +464 -0
  55. package/dist/story.hooks.d.ts +21 -0
  56. package/dist/story.hooks.js +31 -0
  57. package/dist/story.js +6 -0
  58. package/dist/story.registry.d.ts +21 -0
  59. package/dist/story.registry.js +30 -0
  60. package/dist/story.store.d.ts +107 -0
  61. package/dist/story.store.js +1 -0
  62. package/dist/story.toolcalls.d.ts +60 -0
  63. package/dist/story.toolcalls.js +73 -0
  64. package/dist/thread.builder.d.ts +118 -0
  65. package/dist/thread.builder.js +134 -0
  66. package/dist/thread.config.d.ts +15 -0
  67. package/dist/thread.config.js +30 -0
  68. package/dist/thread.d.ts +3 -0
  69. package/dist/thread.engine.d.ts +229 -0
  70. package/dist/thread.engine.js +471 -0
  71. package/dist/thread.events.d.ts +35 -0
  72. package/dist/thread.events.js +105 -0
  73. package/dist/thread.hooks.d.ts +21 -0
  74. package/dist/thread.hooks.js +31 -0
  75. package/dist/thread.js +7 -0
  76. package/dist/thread.reactor.d.ts +82 -0
  77. package/dist/thread.reactor.js +65 -0
  78. package/dist/thread.registry.d.ts +21 -0
  79. package/dist/thread.registry.js +30 -0
  80. package/dist/thread.store.d.ts +121 -0
  81. package/dist/thread.store.js +1 -0
  82. package/dist/thread.toolcalls.d.ts +60 -0
  83. package/dist/thread.toolcalls.js +73 -0
  84. package/dist/tools-to-model-tools.d.ts +19 -0
  85. package/dist/tools-to-model-tools.js +21 -0
  86. package/package.json +133 -0
@@ -0,0 +1,21 @@
1
+ export declare function getClientResumeHookUrl(): string | undefined;
2
+ /**
3
+ * Deterministic hook token for approving an `auto: false` tool call.
4
+ *
5
+ * External systems can resume the hook with:
6
+ * `resumeHook(toolApprovalHookToken({ executionId, toolCallId }), { approved: true })`
7
+ */
8
+ export declare function toolApprovalHookToken(params: {
9
+ executionId: string;
10
+ toolCallId: string;
11
+ }): string;
12
+ /**
13
+ * Deterministic webhook token for approving an `auto: false` tool call.
14
+ *
15
+ * When using Workflow DevKit, the webhook is available at:
16
+ * `/.well-known/workflow/v1/webhook/:token`
17
+ */
18
+ export declare function toolApprovalWebhookToken(params: {
19
+ executionId: string;
20
+ toolCallId: string;
21
+ }): string;
@@ -0,0 +1,31 @@
1
+ function stripTrailingSlash(value) {
2
+ return value.replace(/\/$/, "");
3
+ }
4
+ export function getClientResumeHookUrl() {
5
+ const direct = process.env.EKAIROS_CLIENT_RESUME_HOOK_URL;
6
+ if (typeof direct === "string" && direct.trim())
7
+ return direct.trim();
8
+ const base = process.env.EKAIROS_CLIENT_BASE_URL;
9
+ if (typeof base === "string" && base.trim()) {
10
+ return `${stripTrailingSlash(base.trim())}/api/ekairos/resume-hook`;
11
+ }
12
+ return undefined;
13
+ }
14
+ /**
15
+ * Deterministic hook token for approving an `auto: false` tool call.
16
+ *
17
+ * External systems can resume the hook with:
18
+ * `resumeHook(toolApprovalHookToken({ executionId, toolCallId }), { approved: true })`
19
+ */
20
+ export function toolApprovalHookToken(params) {
21
+ return `ekairos_story:tool-approval:${params.executionId}:${params.toolCallId}`;
22
+ }
23
+ /**
24
+ * Deterministic webhook token for approving an `auto: false` tool call.
25
+ *
26
+ * When using Workflow DevKit, the webhook is available at:
27
+ * `/.well-known/workflow/v1/webhook/:token`
28
+ */
29
+ export function toolApprovalWebhookToken(params) {
30
+ return `ekairos_story:tool-approval-webhook:${params.executionId}:${params.toolCallId}`;
31
+ }
package/dist/story.js ADDED
@@ -0,0 +1,6 @@
1
+ export {
2
+ // engine
3
+ Story, } from "./story.engine.js";
4
+ export {
5
+ // builder
6
+ story, createStory, } from "./story.builder.js";
@@ -0,0 +1,21 @@
1
+ import type { StoryEnvironment } from "./story.config.js";
2
+ import type { StoryInstance } from "./story.builder.js";
3
+ /**
4
+ * Global registry for stories, similar to Inngest's function registry.
5
+ *
6
+ * Goals:
7
+ * - `createStory("key")` can be called many times (module reloads, tests, etc.)
8
+ * - Execution resolves a story by key (`getStory("key")`) instead of importing the module directly.
9
+ * - Registration is intentionally **runtime-local** (per process / per serverless instance).
10
+ */
11
+ export type StoryKey = string;
12
+ type AnyStory = StoryInstance<any, any>;
13
+ export type StoryFactory = () => AnyStory;
14
+ export declare function registerStory(key: StoryKey, factory: StoryFactory): void;
15
+ export declare function hasStory(key: StoryKey): boolean;
16
+ export declare function getStory<Env extends StoryEnvironment = StoryEnvironment>(key: StoryKey): AnyStory & {
17
+ __config: {};
18
+ };
19
+ export declare function getStoryFactory(key: StoryKey): StoryFactory;
20
+ export declare function listStories(): string[];
21
+ export {};
@@ -0,0 +1,30 @@
1
+ const registry = new Map();
2
+ export function registerStory(key, factory) {
3
+ if (!key || typeof key !== "string") {
4
+ throw new Error("registerStory: key must be a non-empty string");
5
+ }
6
+ if (typeof factory !== "function") {
7
+ throw new Error("registerStory: factory must be a function");
8
+ }
9
+ registry.set(key, factory);
10
+ }
11
+ export function hasStory(key) {
12
+ return registry.has(key);
13
+ }
14
+ export function getStory(key) {
15
+ const factory = registry.get(key);
16
+ if (!factory) {
17
+ throw new Error(`Story "${key}" is not registered. Ensure the module that calls createStory("${key}") is imported during boot.`);
18
+ }
19
+ return factory();
20
+ }
21
+ export function getStoryFactory(key) {
22
+ const factory = registry.get(key);
23
+ if (!factory) {
24
+ throw new Error(`Story "${key}" is not registered. Ensure the module that calls createStory("${key}") is imported during boot.`);
25
+ }
26
+ return factory;
27
+ }
28
+ export function listStories() {
29
+ return Array.from(registry.keys());
30
+ }
@@ -0,0 +1,107 @@
1
+ import type { ModelMessage } from "ai";
2
+ /**
3
+ * ## story.store.ts
4
+ *
5
+ * A `StoryStore` is the persistence boundary for stories.
6
+ *
7
+ * Workflow DevKit constraints mean **workflows must be deterministic** and all I/O must happen
8
+ * in steps. This library therefore models persistence as a pluggable store that can be
9
+ * instantiated *inside steps* (InstantDB, Postgres, etc.).
10
+ *
11
+ * The core engine should depend only on this interface (not on a specific database).
12
+ */
13
+ export type ContextIdentifier = {
14
+ id: string;
15
+ key?: never;
16
+ } | {
17
+ key: string;
18
+ id?: never;
19
+ };
20
+ export type ContextStatus = "open" | "streaming" | "closed";
21
+ export type StoredContext<Context> = {
22
+ id: string;
23
+ key: string | null;
24
+ status: ContextStatus;
25
+ createdAt: Date;
26
+ updatedAt?: Date;
27
+ content: Context | null;
28
+ };
29
+ export type ContextEvent = {
30
+ id: string;
31
+ type: string;
32
+ channel: string;
33
+ createdAt: string;
34
+ status?: string;
35
+ content: any;
36
+ };
37
+ export type ExecutionStatus = "executing" | "completed" | "failed";
38
+ export type StoryStepStatus = "running" | "completed" | "failed";
39
+ export type StoryStep = {
40
+ id: string;
41
+ createdAt: Date;
42
+ updatedAt?: Date;
43
+ status: StoryStepStatus;
44
+ iteration: number;
45
+ executionId: string;
46
+ triggerEventId?: string;
47
+ reactionEventId?: string;
48
+ eventId: string;
49
+ toolCalls?: any;
50
+ toolExecutionResults?: any;
51
+ continueLoop?: boolean;
52
+ errorText?: string;
53
+ };
54
+ export interface StoryStore {
55
+ getOrCreateContext<C>(contextIdentifier: ContextIdentifier | null): Promise<StoredContext<C>>;
56
+ getContext<C>(contextIdentifier: ContextIdentifier): Promise<StoredContext<C> | null>;
57
+ updateContextContent<C>(contextIdentifier: ContextIdentifier, content: C): Promise<StoredContext<C>>;
58
+ updateContextStatus(contextIdentifier: ContextIdentifier, status: ContextStatus): Promise<void>;
59
+ saveEvent(contextIdentifier: ContextIdentifier, event: ContextEvent): Promise<ContextEvent>;
60
+ updateEvent(eventId: string, event: ContextEvent): Promise<ContextEvent>;
61
+ getEvent(eventId: string): Promise<ContextEvent | null>;
62
+ getEvents(contextIdentifier: ContextIdentifier): Promise<ContextEvent[]>;
63
+ createExecution(contextIdentifier: ContextIdentifier, triggerEventId: string, reactionEventId: string): Promise<{
64
+ id: string;
65
+ }>;
66
+ completeExecution(contextIdentifier: ContextIdentifier, executionId: string, status: Exclude<ExecutionStatus, "executing">): Promise<void>;
67
+ /**
68
+ * Creates a persisted story step (intended: one per story loop iteration).
69
+ *
70
+ * IMPORTANT: IDs should be generated inside the store boundary (step runtime) to remain replay-safe.
71
+ */
72
+ createStep(params: {
73
+ executionId: string;
74
+ iteration: number;
75
+ }): Promise<{
76
+ id: string;
77
+ eventId: string;
78
+ }>;
79
+ /**
80
+ * Updates a persisted story step with completion metadata (tools, errors, continue signal, etc).
81
+ */
82
+ updateStep(stepId: string, patch: Partial<Pick<StoryStep, "status" | "toolCalls" | "toolExecutionResults" | "continueLoop" | "errorText" | "updatedAt">>): Promise<void>;
83
+ /**
84
+ * Persists normalized parts for a given step (parts-first).
85
+ * The event keeps `content.parts` for back-compat, but the source-of-truth can be `story_parts`.
86
+ */
87
+ saveStepParts(params: {
88
+ stepId: string;
89
+ parts: any[];
90
+ }): Promise<void>;
91
+ /**
92
+ * Links an event to an execution so consumers can traverse:
93
+ * `event -> execution -> steps`
94
+ */
95
+ linkEventToExecution(params: {
96
+ eventId: string;
97
+ executionId: string;
98
+ }): Promise<void>;
99
+ /**
100
+ * Converts events to model messages for the next LLM call.
101
+ *
102
+ * NOTE:
103
+ * - Attachment/document expansion should happen earlier in the Story loop via `expandEvents(...)`.
104
+ * - This method should stay focused on converting already-normalized event parts into `ModelMessage[]`.
105
+ */
106
+ eventsToModelMessages(events: ContextEvent[]): Promise<ModelMessage[]>;
107
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,60 @@
1
+ /**
2
+ * ## story.toolcalls.ts
3
+ *
4
+ * This module isolates the **tool-call plumbing** used by `story.engine.ts`.
5
+ *
6
+ * In our runtime, tool calls are represented as **event parts** produced by the AI SDK.
7
+ * The engine needs to:
8
+ * - extract a normalized list of tool calls from `event.content.parts`, and
9
+ * - merge tool execution outcomes back into those parts (so the persisted event reflects
10
+ * `output-available` / `output-error`, etc.).
11
+ *
12
+ * Keeping this logic here helps `story.engine.ts` read like orchestration, and keeps
13
+ * these transformations testable and reusable.
14
+ */
15
+ import type { ContextEvent } from "./story.store.js";
16
+ export type ToolCall = {
17
+ toolCallId: string;
18
+ toolName: string;
19
+ args: any;
20
+ };
21
+ /**
22
+ * Extracts tool calls from an event's `parts` array.
23
+ *
24
+ * Expected part shape (loosely):
25
+ * - `type`: string like `"tool-<toolName>"`
26
+ * - `toolCallId`: string
27
+ * - `input`: any (tool args)
28
+ *
29
+ * We intentionally treat the input as `any` because the part schema is produced by the AI SDK.
30
+ */
31
+ export declare function extractToolCallsFromParts(parts: any[] | undefined | null): ToolCall[];
32
+ /**
33
+ * Applies a tool execution outcome to the matching tool part.
34
+ *
35
+ * This does not mutate `parts` — it returns a new array.
36
+ *
37
+ * We match the tool part by:
38
+ * - `type === "tool-<toolName>"` and
39
+ * - `toolCallId` equality
40
+ *
41
+ * Then we set:
42
+ * - on success: `{ state: "output-available", output: <result> }`
43
+ * - on failure: `{ state: "output-error", errorText: <message> }`
44
+ */
45
+ export declare function applyToolExecutionResultToParts(parts: any[], toolCall: Pick<ToolCall, "toolCallId" | "toolName">, execution: {
46
+ success: boolean;
47
+ result: any;
48
+ message?: string;
49
+ }): any[];
50
+ /**
51
+ * Returns `true` when a given tool has a **settled** execution result in an event's parts.
52
+ *
53
+ * We treat a tool part as "executed" once it has either:
54
+ * - `state: "output-available"` (success), or
55
+ * - `state: "output-error"` (failure).
56
+ *
57
+ * This is useful for stop/continue logic in `story.shouldContinue(...)` where you want to
58
+ * decide based on the persisted `reactionEvent` (not ephemeral in-memory arrays).
59
+ */
60
+ export declare function didToolExecute(event: Pick<ContextEvent, "content">, toolName: string): boolean;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * ## story.toolcalls.ts
3
+ *
4
+ * This module isolates the **tool-call plumbing** used by `story.engine.ts`.
5
+ *
6
+ * In our runtime, tool calls are represented as **event parts** produced by the AI SDK.
7
+ * The engine needs to:
8
+ * - extract a normalized list of tool calls from `event.content.parts`, and
9
+ * - merge tool execution outcomes back into those parts (so the persisted event reflects
10
+ * `output-available` / `output-error`, etc.).
11
+ *
12
+ * Keeping this logic here helps `story.engine.ts` read like orchestration, and keeps
13
+ * these transformations testable and reusable.
14
+ */
15
+ /**
16
+ * Extracts tool calls from an event's `parts` array.
17
+ *
18
+ * Expected part shape (loosely):
19
+ * - `type`: string like `"tool-<toolName>"`
20
+ * - `toolCallId`: string
21
+ * - `input`: any (tool args)
22
+ *
23
+ * We intentionally treat the input as `any` because the part schema is produced by the AI SDK.
24
+ */
25
+ export function extractToolCallsFromParts(parts) {
26
+ const safeParts = parts ?? [];
27
+ return safeParts.reduce((acc, p) => {
28
+ if (typeof p?.type === "string" && p.type.startsWith("tool-")) {
29
+ const toolName = p.type.split("-").slice(1).join("-");
30
+ acc.push({ toolCallId: p.toolCallId, toolName, args: p.input });
31
+ }
32
+ return acc;
33
+ }, []);
34
+ }
35
+ /**
36
+ * Applies a tool execution outcome to the matching tool part.
37
+ *
38
+ * This does not mutate `parts` — it returns a new array.
39
+ *
40
+ * We match the tool part by:
41
+ * - `type === "tool-<toolName>"` and
42
+ * - `toolCallId` equality
43
+ *
44
+ * Then we set:
45
+ * - on success: `{ state: "output-available", output: <result> }`
46
+ * - on failure: `{ state: "output-error", errorText: <message> }`
47
+ */
48
+ export function applyToolExecutionResultToParts(parts, toolCall, execution) {
49
+ return parts.map((p) => {
50
+ if (p?.type === `tool-${toolCall.toolName}` && p.toolCallId === toolCall.toolCallId) {
51
+ if (execution.success) {
52
+ return { ...p, state: "output-available", output: execution.result };
53
+ }
54
+ return { ...p, state: "output-error", errorText: String(execution.message || "Error") };
55
+ }
56
+ return p;
57
+ });
58
+ }
59
+ /**
60
+ * Returns `true` when a given tool has a **settled** execution result in an event's parts.
61
+ *
62
+ * We treat a tool part as "executed" once it has either:
63
+ * - `state: "output-available"` (success), or
64
+ * - `state: "output-error"` (failure).
65
+ *
66
+ * This is useful for stop/continue logic in `story.shouldContinue(...)` where you want to
67
+ * decide based on the persisted `reactionEvent` (not ephemeral in-memory arrays).
68
+ */
69
+ export function didToolExecute(event, toolName) {
70
+ const parts = event.content.parts;
71
+ return parts.some((p) => p.type === `tool-${toolName}` &&
72
+ (p.state === "output-available" || p.state === "output-error"));
73
+ }
@@ -0,0 +1,118 @@
1
+ import type { ThreadEnvironment } from "./thread.config.js";
2
+ import { Thread, type ThreadModelInit, type ThreadOptions, type ThreadTool, type ShouldContinue, type ThreadShouldContinueArgs, type ThreadReactParams } from "./thread.engine.js";
3
+ import type { ThreadReactor } from "./thread.reactor.js";
4
+ import type { ThreadItem, StoredContext } from "./thread.store.js";
5
+ import { type ThreadKey } from "./thread.registry.js";
6
+ export interface ThreadConfig<Context, Env extends ThreadEnvironment = ThreadEnvironment> {
7
+ context: (context: StoredContext<Context>, env: Env) => Promise<Context> | Context;
8
+ /**
9
+ * Event expansion stage (first-class; executed by the engine on every loop iteration).
10
+ *
11
+ * Runs inside the Thread loop after events are loaded from the store and before
12
+ * they are converted into model messages.
13
+ *
14
+ * This is the intended place to do "document expansion" (turn file references
15
+ * into text parts) using LlamaCloud/Reducto/etc.
16
+ *
17
+ * If you do not provide an implementation, the default is an identity transform
18
+ * (events pass through unchanged).
19
+ *
20
+ * If you do I/O here, implement it as a `"use-step"` function.
21
+ */
22
+ expandEvents?: (events: ThreadItem[], context: StoredContext<Context>, env: Env) => Promise<ThreadItem[]> | ThreadItem[];
23
+ /**
24
+ * Thread-first "system" message for the model for this Thread run.
25
+ */
26
+ narrative: (context: StoredContext<Context>, env: Env) => Promise<string> | string;
27
+ /**
28
+ * Actions available to the model (aka "tools" in AI SDK terminology).
29
+ */
30
+ actions: (context: StoredContext<Context>, env: Env) => Promise<Record<string, ThreadTool>> | Record<string, ThreadTool>;
31
+ /**
32
+ * @deprecated Use `actions()` instead.
33
+ */
34
+ tools?: (context: StoredContext<Context>, env: Env) => Promise<Record<string, ThreadTool>> | Record<string, ThreadTool>;
35
+ /**
36
+ * Model configuration (DurableAgent-style).
37
+ *
38
+ * - `string`: AI Gateway model id, resolved in the LLM step (e.g. `"openai/gpt-5.1-thinking"`).
39
+ * - `() => Promise<model>`: model factory. For Workflow compatibility, provide a `"use-step"` function
40
+ * so it can be serialized by reference.
41
+ * - `(context, env) => ...`: dynamic selection based on current context/env (runs in workflow context).
42
+ */
43
+ model?: ThreadModelInit | ((context: StoredContext<Context>, env: Env) => ThreadModelInit);
44
+ reactor?: ThreadReactor<Context, Env>;
45
+ /**
46
+ * Called after each streamed model "reaction" and subsequent tool executions.
47
+ *
48
+ * Use this to decide whether the Thread loop should continue.
49
+ *
50
+ * - `true` => continue looping
51
+ * - `false` => finalize the Thread run
52
+ */
53
+ shouldContinue?: (args: ThreadShouldContinueArgs<Context, Env>) => Promise<ShouldContinue> | ShouldContinue;
54
+ opts?: ThreadOptions<Context, Env>;
55
+ }
56
+ export type ThreadInstance<Context, Env extends ThreadEnvironment = ThreadEnvironment> = Thread<Context, Env> & {
57
+ readonly __config: ThreadConfig<Context, Env>;
58
+ };
59
+ export declare function thread<Context, Env extends ThreadEnvironment = ThreadEnvironment>(config: ThreadConfig<Context, Env>): ThreadInstance<Context, Env>;
60
+ type AnyContextInitializer<Env extends ThreadEnvironment> = (context: StoredContext<any>, env: Env) => Promise<any> | any;
61
+ type InferContextFromInitializer<I extends AnyContextInitializer<any>> = Awaited<ReturnType<I>>;
62
+ type BuilderSystemPrompt<Context, Env extends ThreadEnvironment> = (context: StoredContext<Context>, env: Env) => Promise<string> | string;
63
+ type BuilderTools<Context, Env extends ThreadEnvironment> = (context: StoredContext<Context>, env: Env) => Promise<Record<string, ThreadTool>> | Record<string, ThreadTool>;
64
+ type BuilderExpandEvents<Context, Env extends ThreadEnvironment> = (events: ThreadItem[], context: StoredContext<Context>, env: Env) => Promise<ThreadItem[]> | ThreadItem[];
65
+ type BuilderShouldContinue<Context, Env extends ThreadEnvironment> = (args: ThreadShouldContinueArgs<Context, Env>) => Promise<ShouldContinue> | ShouldContinue;
66
+ type BuilderModel<Context, Env extends ThreadEnvironment> = ThreadModelInit | ((context: StoredContext<Context>, env: Env) => ThreadModelInit);
67
+ export type RegistrableThreadBuilder = {
68
+ key: ThreadKey;
69
+ register: () => void;
70
+ };
71
+ type FluentThreadBuilder<Context, Env extends ThreadEnvironment> = {
72
+ key: ThreadKey;
73
+ expandEvents(fn: BuilderExpandEvents<Context, Env>): FluentThreadBuilder<Context, Env>;
74
+ narrative(fn: BuilderSystemPrompt<Context, Env>): FluentThreadBuilder<Context, Env>;
75
+ /**
76
+ * "System" facade (AI SDK terminology).
77
+ */
78
+ system(fn: BuilderSystemPrompt<Context, Env>): FluentThreadBuilder<Context, Env>;
79
+ actions(fn: BuilderTools<Context, Env>): FluentThreadBuilder<Context, Env>;
80
+ /**
81
+ * @deprecated Use `actions()` instead.
82
+ */
83
+ tools(fn: BuilderTools<Context, Env>): FluentThreadBuilder<Context, Env>;
84
+ model(model: BuilderModel<Context, Env>): FluentThreadBuilder<Context, Env>;
85
+ reactor(reactor: ThreadReactor<Context, Env>): FluentThreadBuilder<Context, Env>;
86
+ /**
87
+ * Stop/continue hook (DurableAgent-like stop condition).
88
+ */
89
+ shouldContinue(fn: BuilderShouldContinue<Context, Env>): FluentThreadBuilder<Context, Env>;
90
+ opts(opts: ThreadOptions<Context, Env>): FluentThreadBuilder<Context, Env>;
91
+ /**
92
+ * Convenience: react to an incoming event without requiring an explicit `.build()`.
93
+ *
94
+ * This still validates the config is complete (same as `.build()`), then delegates to the
95
+ * underlying `Thread.react(...)`.
96
+ */
97
+ react(triggerEvent: ThreadItem, params: ThreadReactParams<Env>): ReturnType<Thread<Context, Env>["react"]>;
98
+ /**
99
+ * @deprecated Use `react()` instead. Kept for backwards compatibility.
100
+ */
101
+ stream(triggerEvent: ThreadItem, params: ThreadReactParams<Env>): ReturnType<Thread<Context, Env>["react"]>;
102
+ /**
103
+ * Registers this Thread definition in the global registry under `key`.
104
+ *
105
+ * This is intended to be called at module load / boot time so durable workflows
106
+ * can resume and still resolve the Thread by key without requiring an endpoint
107
+ * to call `.build()` again.
108
+ */
109
+ register(): void;
110
+ config(): ThreadConfig<Context, Env>;
111
+ build(): ThreadInstance<Context, Env>;
112
+ };
113
+ type CreateThreadEntry<Env extends ThreadEnvironment> = {
114
+ context<Initializer extends AnyContextInitializer<Env>>(initializer: Initializer): FluentThreadBuilder<InferContextFromInitializer<Initializer>, Env>;
115
+ initialize<Initializer extends AnyContextInitializer<Env>>(initializer: Initializer): FluentThreadBuilder<InferContextFromInitializer<Initializer>, Env>;
116
+ };
117
+ export declare function createThread<Env extends ThreadEnvironment = ThreadEnvironment>(key: ThreadKey): CreateThreadEntry<Env>;
118
+ export {};
@@ -0,0 +1,134 @@
1
+ import { Thread, } from "./thread.engine.js";
2
+ import { registerThread } from "./thread.registry.js";
3
+ export function thread(config) {
4
+ class FunctionalThread extends Thread {
5
+ constructor() {
6
+ super(config.opts, config.reactor);
7
+ this.__config = config;
8
+ }
9
+ async initialize(context, env) {
10
+ return config.context(context, env);
11
+ }
12
+ async expandEvents(events, context, env) {
13
+ if (config.expandEvents)
14
+ return config.expandEvents(events, context, env);
15
+ return super.expandEvents(events, context, env);
16
+ }
17
+ async buildSystemPrompt(context, env) {
18
+ if (config.narrative)
19
+ return config.narrative(context, env);
20
+ throw new Error("Thread config is missing narrative()");
21
+ }
22
+ async buildTools(context, env) {
23
+ // Back-compat: accept `tools` in old configs.
24
+ if (config.actions)
25
+ return config.actions(context, env);
26
+ if (config.tools)
27
+ return config.tools(context, env);
28
+ throw new Error("Thread config is missing actions()");
29
+ }
30
+ getModel(context, env) {
31
+ if (typeof config.model === "function")
32
+ return config.model(context, env);
33
+ return config.model ?? super.getModel(context, env);
34
+ }
35
+ async shouldContinue(args) {
36
+ if (config.shouldContinue)
37
+ return config.shouldContinue(args);
38
+ return true;
39
+ }
40
+ }
41
+ const instance = new FunctionalThread();
42
+ return Object.assign(instance, { __config: config });
43
+ }
44
+ function assertConfigComplete(config) {
45
+ if (!config.context) {
46
+ throw new Error("createThread: you must define context() before building the Thread.");
47
+ }
48
+ if (!config.narrative) {
49
+ throw new Error("createThread: you must define narrative() before building the Thread.");
50
+ }
51
+ if (!config.actions && !config.tools) {
52
+ throw new Error("createThread: you must define actions() before building the Thread.");
53
+ }
54
+ }
55
+ export function createThread(key) {
56
+ const initializeBuilder = (initializer) => {
57
+ const typedInitializer = (ctx, env) => initializer(ctx, env);
58
+ const fluentState = {
59
+ context: typedInitializer,
60
+ };
61
+ let cached = null;
62
+ const builder = {
63
+ key,
64
+ expandEvents(fn) {
65
+ fluentState.expandEvents = fn;
66
+ return builder;
67
+ },
68
+ narrative(narrative) {
69
+ fluentState.narrative = narrative;
70
+ return builder;
71
+ },
72
+ system(system) {
73
+ // Facade alias.
74
+ fluentState.narrative = system;
75
+ return builder;
76
+ },
77
+ actions(actionsFactory) {
78
+ fluentState.actions = actionsFactory;
79
+ return builder;
80
+ },
81
+ tools(toolsFactory) {
82
+ // Back-compat alias.
83
+ fluentState.actions = toolsFactory;
84
+ return builder;
85
+ },
86
+ model(model) {
87
+ fluentState.model = model;
88
+ return builder;
89
+ },
90
+ reactor(reactor) {
91
+ fluentState.reactor = reactor;
92
+ return builder;
93
+ },
94
+ shouldContinue(fn) {
95
+ fluentState.shouldContinue = fn;
96
+ return builder;
97
+ },
98
+ opts(options) {
99
+ fluentState.opts = options;
100
+ return builder;
101
+ },
102
+ react(triggerEvent, params) {
103
+ assertConfigComplete(fluentState);
104
+ if (!cached) {
105
+ const config = fluentState;
106
+ cached = thread(config);
107
+ }
108
+ return cached.react(triggerEvent, params);
109
+ },
110
+ stream(triggerEvent, params) {
111
+ return builder.react(triggerEvent, params);
112
+ },
113
+ register() {
114
+ assertConfigComplete(fluentState);
115
+ const config = fluentState;
116
+ registerThread(key, () => thread(config));
117
+ },
118
+ config() {
119
+ assertConfigComplete(fluentState);
120
+ return fluentState;
121
+ },
122
+ build() {
123
+ assertConfigComplete(fluentState);
124
+ const config = fluentState;
125
+ return thread(config);
126
+ },
127
+ };
128
+ return builder;
129
+ };
130
+ return {
131
+ context: initializeBuilder,
132
+ initialize: initializeBuilder,
133
+ };
134
+ }
@@ -0,0 +1,15 @@
1
+ import type { ThreadStore } from "./thread.store.js";
2
+ import type { ConcreteDomain } from "@ekairos/domain";
3
+ /**
4
+ * ## thread.config.ts
5
+ *
6
+ * Thread runtime is resolved from the app runtime configured in `@ekairos/domain/runtime`.
7
+ * There is no thread-specific runtime configuration surface.
8
+ */
9
+ export type ThreadEnvironment = Record<string, unknown>;
10
+ export type ThreadRuntime = {
11
+ store: ThreadStore;
12
+ db: any;
13
+ domain?: ConcreteDomain<any, any>;
14
+ };
15
+ export declare function coerceThreadRuntime(value: any): Promise<ThreadRuntime>;
@@ -0,0 +1,30 @@
1
+ const runtimeByDb = new WeakMap();
2
+ export async function coerceThreadRuntime(value) {
3
+ if (!value) {
4
+ throw new Error("Thread runtime resolver returned no value.");
5
+ }
6
+ if (typeof value === "object" && value.store) {
7
+ return value;
8
+ }
9
+ const dbCandidate = typeof value === "object" && value !== null && "db" in value
10
+ ? value.db
11
+ : value;
12
+ if (!dbCandidate) {
13
+ throw new Error("Thread runtime resolver did not provide a database or store.");
14
+ }
15
+ if (typeof dbCandidate === "object" && dbCandidate !== null) {
16
+ const cached = runtimeByDb.get(dbCandidate);
17
+ if (cached)
18
+ return cached;
19
+ }
20
+ const { InstantStore } = await import("./stores/instant.store.js");
21
+ const runtime = {
22
+ store: new InstantStore(dbCandidate),
23
+ db: dbCandidate,
24
+ domain: typeof value === "object" ? value.domain : undefined,
25
+ };
26
+ if (typeof dbCandidate === "object" && dbCandidate !== null) {
27
+ runtimeByDb.set(dbCandidate, runtime);
28
+ }
29
+ return runtime;
30
+ }
@@ -0,0 +1,3 @@
1
+ export { Thread, type ThreadOptions, type ThreadStreamOptions, type ShouldContinue, type ThreadShouldContinueArgs, } from "./thread.engine.js";
2
+ export { thread, createThread, type ThreadConfig, type ThreadInstance, type RegistrableThreadBuilder, } from "./thread.builder.js";
3
+ export { createAiSdkReactor, type CreateAiSdkReactorOptions, type ThreadReactor, type ThreadReactorParams, type ThreadReactionResult, type ThreadReactionToolCall, type ThreadReactionLLM, } from "./thread.reactor.js";