@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.
- package/README.md +363 -0
- package/dist/codex.d.ts +95 -0
- package/dist/codex.js +91 -0
- package/dist/env.d.ts +12 -0
- package/dist/env.js +62 -0
- package/dist/events.d.ts +35 -0
- package/dist/events.js +102 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +1 -0
- package/dist/mirror.d.ts +41 -0
- package/dist/mirror.js +1 -0
- package/dist/oidc.d.ts +7 -0
- package/dist/oidc.js +25 -0
- package/dist/polyfills/dom-events.d.ts +1 -0
- package/dist/polyfills/dom-events.js +89 -0
- package/dist/react.d.ts +62 -0
- package/dist/react.js +101 -0
- package/dist/runtime.d.ts +17 -0
- package/dist/runtime.js +23 -0
- package/dist/runtime.step.d.ts +9 -0
- package/dist/runtime.step.js +7 -0
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +200 -0
- package/dist/steps/do-story-stream-step.d.ts +29 -0
- package/dist/steps/do-story-stream-step.js +89 -0
- package/dist/steps/do-thread-stream-step.d.ts +29 -0
- package/dist/steps/do-thread-stream-step.js +90 -0
- package/dist/steps/mirror.steps.d.ts +6 -0
- package/dist/steps/mirror.steps.js +48 -0
- package/dist/steps/reaction.steps.d.ts +43 -0
- package/dist/steps/reaction.steps.js +354 -0
- package/dist/steps/store.steps.d.ts +98 -0
- package/dist/steps/store.steps.js +512 -0
- package/dist/steps/stream.steps.d.ts +41 -0
- package/dist/steps/stream.steps.js +99 -0
- package/dist/steps/trace.steps.d.ts +37 -0
- package/dist/steps/trace.steps.js +265 -0
- package/dist/stores/instant.document-parser.d.ts +6 -0
- package/dist/stores/instant.document-parser.js +210 -0
- package/dist/stores/instant.documents.d.ts +16 -0
- package/dist/stores/instant.documents.js +152 -0
- package/dist/stores/instant.store.d.ts +78 -0
- package/dist/stores/instant.store.js +530 -0
- package/dist/story.actions.d.ts +60 -0
- package/dist/story.actions.js +120 -0
- package/dist/story.builder.d.ts +115 -0
- package/dist/story.builder.js +130 -0
- package/dist/story.config.d.ts +54 -0
- package/dist/story.config.js +125 -0
- package/dist/story.d.ts +2 -0
- package/dist/story.engine.d.ts +224 -0
- package/dist/story.engine.js +464 -0
- package/dist/story.hooks.d.ts +21 -0
- package/dist/story.hooks.js +31 -0
- package/dist/story.js +6 -0
- package/dist/story.registry.d.ts +21 -0
- package/dist/story.registry.js +30 -0
- package/dist/story.store.d.ts +107 -0
- package/dist/story.store.js +1 -0
- package/dist/story.toolcalls.d.ts +60 -0
- package/dist/story.toolcalls.js +73 -0
- package/dist/thread.builder.d.ts +118 -0
- package/dist/thread.builder.js +134 -0
- package/dist/thread.config.d.ts +15 -0
- package/dist/thread.config.js +30 -0
- package/dist/thread.d.ts +3 -0
- package/dist/thread.engine.d.ts +229 -0
- package/dist/thread.engine.js +471 -0
- package/dist/thread.events.d.ts +35 -0
- package/dist/thread.events.js +105 -0
- package/dist/thread.hooks.d.ts +21 -0
- package/dist/thread.hooks.js +31 -0
- package/dist/thread.js +7 -0
- package/dist/thread.reactor.d.ts +82 -0
- package/dist/thread.reactor.js +65 -0
- package/dist/thread.registry.d.ts +21 -0
- package/dist/thread.registry.js +30 -0
- package/dist/thread.store.d.ts +121 -0
- package/dist/thread.store.js +1 -0
- package/dist/thread.toolcalls.d.ts +60 -0
- package/dist/thread.toolcalls.js +73 -0
- package/dist/tools-to-model-tools.d.ts +19 -0
- package/dist/tools-to-model-tools.js +21 -0
- package/package.json +133 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import "../polyfills/dom-events.js";
|
|
2
|
+
import type { DomainSchemaResult } from "@ekairos/domain";
|
|
3
|
+
import type { ModelMessage } from "ai";
|
|
4
|
+
import type { ThreadItem, ContextIdentifier, ThreadIdentifier, ThreadStatus, ContextStatus, StoredThread, StoredContext, ThreadStore } from "../thread.store.js";
|
|
5
|
+
export { parseAndStoreDocument } from "./instant.document-parser.js";
|
|
6
|
+
export { coerceDocumentTextPages, expandEventsWithInstantDocuments, } from "./instant.documents.js";
|
|
7
|
+
/**
|
|
8
|
+
* InstantDB-backed ThreadStore.
|
|
9
|
+
*
|
|
10
|
+
* This is intentionally kept behind the store boundary so the core story engine
|
|
11
|
+
* can remain database-agnostic.
|
|
12
|
+
*/
|
|
13
|
+
export type InstantStoreDb = any;
|
|
14
|
+
export declare class InstantStore implements ThreadStore {
|
|
15
|
+
private db;
|
|
16
|
+
constructor(db: InstantStoreDb);
|
|
17
|
+
private ensureContextThreadLink;
|
|
18
|
+
private normalizeThread;
|
|
19
|
+
private normalizeContext;
|
|
20
|
+
private getThreadByContextId;
|
|
21
|
+
private getContextByThreadId;
|
|
22
|
+
private createThread;
|
|
23
|
+
getOrCreateThread(threadIdentifier: ThreadIdentifier | null): Promise<StoredThread>;
|
|
24
|
+
getThread(threadIdentifier: ThreadIdentifier): Promise<StoredThread | null>;
|
|
25
|
+
updateThreadStatus(threadIdentifier: ThreadIdentifier, status: ThreadStatus): Promise<void>;
|
|
26
|
+
getOrCreateContext<C>(contextIdentifier: ContextIdentifier | null): Promise<StoredContext<C>>;
|
|
27
|
+
private createContextForThread;
|
|
28
|
+
getContext<C>(contextIdentifier: ContextIdentifier): Promise<StoredContext<C> | null>;
|
|
29
|
+
updateContextContent<C>(contextIdentifier: ContextIdentifier, content: C): Promise<StoredContext<C>>;
|
|
30
|
+
updateContextStatus(contextIdentifier: ContextIdentifier, status: ContextStatus): Promise<void>;
|
|
31
|
+
private resolveThreadContext;
|
|
32
|
+
saveItem(contextIdentifier: ContextIdentifier, event: ThreadItem): Promise<ThreadItem>;
|
|
33
|
+
updateItem(eventId: string, event: ThreadItem): Promise<ThreadItem>;
|
|
34
|
+
getItem(eventId: string): Promise<ThreadItem | null>;
|
|
35
|
+
getItems(contextIdentifier: ContextIdentifier): Promise<ThreadItem[]>;
|
|
36
|
+
createExecution(contextIdentifier: ContextIdentifier, triggerEventId: string, reactionEventId: string): Promise<{
|
|
37
|
+
id: string;
|
|
38
|
+
}>;
|
|
39
|
+
completeExecution(contextIdentifier: ContextIdentifier, executionId: string, status: "completed" | "failed"): Promise<void>;
|
|
40
|
+
createStep(params: {
|
|
41
|
+
executionId: string;
|
|
42
|
+
iteration: number;
|
|
43
|
+
}): Promise<{
|
|
44
|
+
id: string;
|
|
45
|
+
eventId: string;
|
|
46
|
+
}>;
|
|
47
|
+
updateStep(stepId: string, patch: Partial<{
|
|
48
|
+
status: "running" | "completed" | "failed";
|
|
49
|
+
toolCalls: any;
|
|
50
|
+
toolExecutionResults: any;
|
|
51
|
+
continueLoop: boolean;
|
|
52
|
+
errorText: string;
|
|
53
|
+
updatedAt: Date;
|
|
54
|
+
}>): Promise<void>;
|
|
55
|
+
linkItemToExecution(params: {
|
|
56
|
+
itemId: string;
|
|
57
|
+
executionId: string;
|
|
58
|
+
}): Promise<void>;
|
|
59
|
+
saveStepParts(params: {
|
|
60
|
+
stepId: string;
|
|
61
|
+
parts: any[];
|
|
62
|
+
}): Promise<void>;
|
|
63
|
+
itemsToModelMessages(events: ThreadItem[]): Promise<ModelMessage[]>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Helper to create a ThreadRuntimeResolver that returns an InstantStore.
|
|
67
|
+
*
|
|
68
|
+
* This keeps the app-level `ekairos.ts` extremely small.
|
|
69
|
+
*/
|
|
70
|
+
export declare function createInstantStoreRuntime(params: {
|
|
71
|
+
getDb: (orgId: string) => Promise<InstantStoreDb> | InstantStoreDb;
|
|
72
|
+
getOrgId?: (env: Record<string, unknown>) => string;
|
|
73
|
+
domain?: DomainSchemaResult;
|
|
74
|
+
}): (env: Record<string, unknown>) => Promise<{
|
|
75
|
+
store: InstantStore;
|
|
76
|
+
db: InstantStoreDb;
|
|
77
|
+
domain?: any;
|
|
78
|
+
}>;
|
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
import "../polyfills/dom-events.js";
|
|
2
|
+
import { id, lookup } from "@instantdb/admin";
|
|
3
|
+
import { convertItemsToModelMessages } from "../thread.events.js";
|
|
4
|
+
export { parseAndStoreDocument } from "./instant.document-parser.js";
|
|
5
|
+
import { expandEventsWithInstantDocuments } from "./instant.documents.js";
|
|
6
|
+
export { coerceDocumentTextPages, expandEventsWithInstantDocuments, } from "./instant.documents.js";
|
|
7
|
+
export class InstantStore {
|
|
8
|
+
constructor(db) {
|
|
9
|
+
this.db = db;
|
|
10
|
+
}
|
|
11
|
+
async ensureContextThreadLink(context, contextIdentifier) {
|
|
12
|
+
if (context.threadId)
|
|
13
|
+
return context;
|
|
14
|
+
const fallbackKey = contextIdentifier?.key ??
|
|
15
|
+
context.key ??
|
|
16
|
+
(context.id ? String(context.id) : null);
|
|
17
|
+
const thread = await this.createThread(fallbackKey ? { key: String(fallbackKey) } : null, context.id ? String(context.id) : undefined);
|
|
18
|
+
await this.db.transact([
|
|
19
|
+
this.db.tx.thread_contexts[context.id].link({ thread: thread.id }),
|
|
20
|
+
]);
|
|
21
|
+
const refreshed = await this.getContext({ id: context.id });
|
|
22
|
+
if (refreshed)
|
|
23
|
+
return refreshed;
|
|
24
|
+
return {
|
|
25
|
+
...context,
|
|
26
|
+
threadId: thread.id,
|
|
27
|
+
key: thread.key ?? context.key,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
normalizeThread(row) {
|
|
31
|
+
return {
|
|
32
|
+
id: String(row?.id ?? ""),
|
|
33
|
+
key: typeof row?.key === "string" ? row.key : null,
|
|
34
|
+
name: typeof row?.name === "string" ? row.name : null,
|
|
35
|
+
status: (typeof row?.status === "string" ? row.status : "open"),
|
|
36
|
+
createdAt: row?.createdAt instanceof Date ? row.createdAt : new Date(row?.createdAt ?? Date.now()),
|
|
37
|
+
updatedAt: row?.updatedAt instanceof Date ? row.updatedAt : row?.updatedAt ? new Date(row.updatedAt) : undefined,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
normalizeContext(row, thread) {
|
|
41
|
+
return {
|
|
42
|
+
id: String(row?.id ?? ""),
|
|
43
|
+
threadId: thread?.id,
|
|
44
|
+
key: thread?.key ?? null,
|
|
45
|
+
status: (typeof row?.status === "string" ? row.status : "open"),
|
|
46
|
+
createdAt: row?.createdAt instanceof Date ? row.createdAt : new Date(row?.createdAt ?? Date.now()),
|
|
47
|
+
updatedAt: row?.updatedAt instanceof Date ? row.updatedAt : row?.updatedAt ? new Date(row.updatedAt) : undefined,
|
|
48
|
+
content: row?.content ?? null,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async getThreadByContextId(contextId) {
|
|
52
|
+
try {
|
|
53
|
+
const res = await this.db.query({
|
|
54
|
+
thread_contexts: {
|
|
55
|
+
$: { where: { id: contextId }, limit: 1 },
|
|
56
|
+
thread: {},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
const ctx = res?.thread_contexts?.[0];
|
|
60
|
+
const threadNode = Array.isArray(ctx?.thread) ? ctx.thread[0] : ctx?.thread;
|
|
61
|
+
if (threadNode)
|
|
62
|
+
return this.normalizeThread(threadNode);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// fallback below
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const res = await this.db.query({
|
|
69
|
+
thread_threads: {
|
|
70
|
+
$: { where: { "contexts.id": contextId }, limit: 1 },
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
const row = res?.thread_threads?.[0];
|
|
74
|
+
return row ? this.normalizeThread(row) : null;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async getContextByThreadId(threadId) {
|
|
81
|
+
try {
|
|
82
|
+
const res = await this.db.query({
|
|
83
|
+
thread_contexts: {
|
|
84
|
+
$: { where: { thread: threadId }, limit: 1 },
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
const row = res?.thread_contexts?.[0];
|
|
88
|
+
if (!row)
|
|
89
|
+
return null;
|
|
90
|
+
const thread = await this.getThread({ id: threadId });
|
|
91
|
+
return this.normalizeContext(row, thread);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
try {
|
|
95
|
+
const res = await this.db.query({
|
|
96
|
+
thread_contexts: {
|
|
97
|
+
$: { where: { "thread.id": threadId }, limit: 1 },
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
const row = res?.thread_contexts?.[0];
|
|
101
|
+
if (!row)
|
|
102
|
+
return null;
|
|
103
|
+
const thread = await this.getThread({ id: threadId });
|
|
104
|
+
return this.normalizeContext(row, thread);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async createThread(threadIdentifier, fallbackKey) {
|
|
112
|
+
const threadId = threadIdentifier?.id ?? id();
|
|
113
|
+
const key = threadIdentifier?.key ??
|
|
114
|
+
fallbackKey ??
|
|
115
|
+
(threadIdentifier?.id ? String(threadIdentifier.id) : null);
|
|
116
|
+
await this.db.transact([
|
|
117
|
+
this.db.tx.thread_threads[threadId].create({
|
|
118
|
+
createdAt: new Date(),
|
|
119
|
+
updatedAt: new Date(),
|
|
120
|
+
status: "open",
|
|
121
|
+
key,
|
|
122
|
+
}),
|
|
123
|
+
]);
|
|
124
|
+
const thread = await this.getThread({ id: threadId });
|
|
125
|
+
if (!thread) {
|
|
126
|
+
throw new Error("InstantStore: failed to create thread");
|
|
127
|
+
}
|
|
128
|
+
return thread;
|
|
129
|
+
}
|
|
130
|
+
async getOrCreateThread(threadIdentifier) {
|
|
131
|
+
if (!threadIdentifier) {
|
|
132
|
+
return this.createThread(null);
|
|
133
|
+
}
|
|
134
|
+
const existing = await this.getThread(threadIdentifier);
|
|
135
|
+
if (existing)
|
|
136
|
+
return existing;
|
|
137
|
+
return this.createThread(threadIdentifier);
|
|
138
|
+
}
|
|
139
|
+
async getThread(threadIdentifier) {
|
|
140
|
+
try {
|
|
141
|
+
if (threadIdentifier.id) {
|
|
142
|
+
const res = await this.db.query({
|
|
143
|
+
thread_threads: {
|
|
144
|
+
$: { where: { id: threadIdentifier.id }, limit: 1 },
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
const row = res?.thread_threads?.[0];
|
|
148
|
+
return row ? this.normalizeThread(row) : null;
|
|
149
|
+
}
|
|
150
|
+
const res = await this.db.query({
|
|
151
|
+
thread_threads: {
|
|
152
|
+
$: { where: { key: threadIdentifier.key }, limit: 1 },
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
const row = res?.thread_threads?.[0];
|
|
156
|
+
return row ? this.normalizeThread(row) : null;
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
throw new Error("InstantStore: Error getting thread: " + error.message);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async updateThreadStatus(threadIdentifier, status) {
|
|
163
|
+
const thread = await this.getThread(threadIdentifier);
|
|
164
|
+
if (!thread)
|
|
165
|
+
throw new Error("InstantStore: thread not found");
|
|
166
|
+
await this.db.transact([
|
|
167
|
+
this.db.tx.thread_threads[thread.id].update({
|
|
168
|
+
status,
|
|
169
|
+
updatedAt: new Date(),
|
|
170
|
+
}),
|
|
171
|
+
]);
|
|
172
|
+
}
|
|
173
|
+
async getOrCreateContext(contextIdentifier) {
|
|
174
|
+
if (!contextIdentifier) {
|
|
175
|
+
const contextId = id();
|
|
176
|
+
const thread = await this.createThread(null, contextId);
|
|
177
|
+
return await this.createContextForThread(thread, contextId);
|
|
178
|
+
}
|
|
179
|
+
const context = await this.getContext(contextIdentifier);
|
|
180
|
+
if (context) {
|
|
181
|
+
return await this.ensureContextThreadLink(context, contextIdentifier);
|
|
182
|
+
}
|
|
183
|
+
if (contextIdentifier.key) {
|
|
184
|
+
const thread = await this.getOrCreateThread({ key: contextIdentifier.key });
|
|
185
|
+
return await this.createContextForThread(thread);
|
|
186
|
+
}
|
|
187
|
+
const thread = await this.getOrCreateThread(contextIdentifier.id ? { key: String(contextIdentifier.id) } : null);
|
|
188
|
+
return await this.createContextForThread(thread, contextIdentifier.id);
|
|
189
|
+
}
|
|
190
|
+
async createContextForThread(thread, contextId) {
|
|
191
|
+
const contextData = {
|
|
192
|
+
createdAt: new Date(),
|
|
193
|
+
content: {},
|
|
194
|
+
status: "open",
|
|
195
|
+
};
|
|
196
|
+
const newContextId = contextId ?? id();
|
|
197
|
+
await this.db.transact([
|
|
198
|
+
this.db.tx.thread_contexts[newContextId].create(contextData),
|
|
199
|
+
this.db.tx.thread_contexts[newContextId].link({ thread: thread.id }),
|
|
200
|
+
]);
|
|
201
|
+
const ctx = await this.getContext({ id: newContextId });
|
|
202
|
+
if (!ctx)
|
|
203
|
+
throw new Error("InstantStore: failed to create context");
|
|
204
|
+
return ctx;
|
|
205
|
+
}
|
|
206
|
+
async getContext(contextIdentifier) {
|
|
207
|
+
try {
|
|
208
|
+
if (contextIdentifier.id) {
|
|
209
|
+
const res = await this.db.query({
|
|
210
|
+
thread_contexts: {
|
|
211
|
+
$: { where: { id: contextIdentifier.id }, limit: 1 },
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
const row = res?.thread_contexts?.[0];
|
|
215
|
+
if (!row)
|
|
216
|
+
return null;
|
|
217
|
+
const thread = await this.getThreadByContextId(String(row.id));
|
|
218
|
+
return this.normalizeContext(row, thread);
|
|
219
|
+
}
|
|
220
|
+
if (contextIdentifier.key) {
|
|
221
|
+
const thread = await this.getThread({ key: contextIdentifier.key });
|
|
222
|
+
if (!thread)
|
|
223
|
+
return null;
|
|
224
|
+
return await this.getContextByThreadId(thread.id);
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
throw new Error("InstantStore: Error getting context: " + error.message);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async updateContextContent(contextIdentifier, content) {
|
|
233
|
+
const context = await this.getContext(contextIdentifier);
|
|
234
|
+
if (!context?.id)
|
|
235
|
+
throw new Error("InstantStore: context not found");
|
|
236
|
+
await this.db.transact([
|
|
237
|
+
this.db.tx.thread_contexts[context.id].update({
|
|
238
|
+
content: content,
|
|
239
|
+
updatedAt: new Date(),
|
|
240
|
+
}),
|
|
241
|
+
]);
|
|
242
|
+
const ctx = await this.getContext(contextIdentifier);
|
|
243
|
+
if (!ctx)
|
|
244
|
+
throw new Error("InstantStore: context not found after update");
|
|
245
|
+
return ctx;
|
|
246
|
+
}
|
|
247
|
+
async updateContextStatus(contextIdentifier, status) {
|
|
248
|
+
const context = await this.getContext(contextIdentifier);
|
|
249
|
+
if (!context?.id)
|
|
250
|
+
throw new Error("InstantStore: context not found");
|
|
251
|
+
const txs = [
|
|
252
|
+
this.db.tx.thread_contexts[context.id].update({
|
|
253
|
+
status,
|
|
254
|
+
updatedAt: new Date(),
|
|
255
|
+
}),
|
|
256
|
+
];
|
|
257
|
+
if (context.threadId) {
|
|
258
|
+
txs.push(this.db.tx.thread_threads[context.threadId].update({
|
|
259
|
+
status: (status === "closed" ? "closed" : status),
|
|
260
|
+
updatedAt: new Date(),
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
263
|
+
await this.db.transact(txs);
|
|
264
|
+
}
|
|
265
|
+
async resolveThreadContext(contextIdentifier) {
|
|
266
|
+
const context = await this.getContext(contextIdentifier);
|
|
267
|
+
if (!context?.id)
|
|
268
|
+
throw new Error("InstantStore: context not found");
|
|
269
|
+
let thread = context.threadId
|
|
270
|
+
? await this.getThread({ id: context.threadId })
|
|
271
|
+
: await this.getThreadByContextId(context.id);
|
|
272
|
+
if (!thread) {
|
|
273
|
+
const healed = await this.ensureContextThreadLink(context, contextIdentifier);
|
|
274
|
+
thread = healed.threadId ? await this.getThread({ id: healed.threadId }) : null;
|
|
275
|
+
}
|
|
276
|
+
if (!thread)
|
|
277
|
+
throw new Error("InstantStore: thread not found for context");
|
|
278
|
+
return { context: { ...context, threadId: thread.id }, thread };
|
|
279
|
+
}
|
|
280
|
+
async saveItem(contextIdentifier, event) {
|
|
281
|
+
const { context, thread } = await this.resolveThreadContext(contextIdentifier);
|
|
282
|
+
const txs = [
|
|
283
|
+
this.db.tx.thread_items[event.id].update({
|
|
284
|
+
...event,
|
|
285
|
+
status: "stored",
|
|
286
|
+
}),
|
|
287
|
+
];
|
|
288
|
+
txs.push(this.db.tx.thread_items[event.id].link({ context: context.id }));
|
|
289
|
+
txs.push(this.db.tx.thread_items[event.id].link({ thread: thread.id }));
|
|
290
|
+
await this.db.transact(txs);
|
|
291
|
+
const persisted = await this.getItem(event.id);
|
|
292
|
+
if (!persisted)
|
|
293
|
+
throw new Error("InstantStore: failed to read event after save");
|
|
294
|
+
return persisted;
|
|
295
|
+
}
|
|
296
|
+
async updateItem(eventId, event) {
|
|
297
|
+
await this.db.transact([this.db.tx.thread_items[eventId].update(event)]);
|
|
298
|
+
const persisted = await this.getItem(eventId);
|
|
299
|
+
if (!persisted)
|
|
300
|
+
throw new Error("InstantStore: event not found after update");
|
|
301
|
+
return persisted;
|
|
302
|
+
}
|
|
303
|
+
async getItem(eventId) {
|
|
304
|
+
const res = await this.db.query({
|
|
305
|
+
thread_items: {
|
|
306
|
+
$: { where: { id: eventId } },
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
return res.thread_items?.[0] ?? null;
|
|
310
|
+
}
|
|
311
|
+
async getItems(contextIdentifier) {
|
|
312
|
+
const { thread } = await this.resolveThreadContext(contextIdentifier);
|
|
313
|
+
let res;
|
|
314
|
+
try {
|
|
315
|
+
res = await this.db.query({
|
|
316
|
+
thread_items: {
|
|
317
|
+
$: {
|
|
318
|
+
where: { thread: thread.id },
|
|
319
|
+
// Keep query constraints minimal to avoid hard dependency on Instant orderable indexes.
|
|
320
|
+
// Ordering is applied in-memory below.
|
|
321
|
+
limit: 1000,
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
res = await this.db.query({
|
|
328
|
+
thread_items: {
|
|
329
|
+
$: {
|
|
330
|
+
where: { "thread.id": thread.id },
|
|
331
|
+
limit: 1000,
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
const items = (res.thread_items ?? []);
|
|
337
|
+
const toEpoch = (value) => {
|
|
338
|
+
if (value instanceof Date)
|
|
339
|
+
return value.getTime();
|
|
340
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
341
|
+
const ms = new Date(value).getTime();
|
|
342
|
+
return Number.isFinite(ms) ? ms : 0;
|
|
343
|
+
}
|
|
344
|
+
return 0;
|
|
345
|
+
};
|
|
346
|
+
return [...items].sort((a, b) => {
|
|
347
|
+
const ta = toEpoch(a?.createdAt ?? a?.updatedAt);
|
|
348
|
+
const tb = toEpoch(b?.createdAt ?? b?.updatedAt);
|
|
349
|
+
if (ta !== tb)
|
|
350
|
+
return ta - tb;
|
|
351
|
+
return String(a?.id ?? "").localeCompare(String(b?.id ?? ""));
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
async createExecution(contextIdentifier, triggerEventId, reactionEventId) {
|
|
355
|
+
const { context, thread } = await this.resolveThreadContext(contextIdentifier);
|
|
356
|
+
const executionId = id();
|
|
357
|
+
const execCreate = this.db.tx.thread_executions[executionId].create({
|
|
358
|
+
createdAt: new Date(),
|
|
359
|
+
status: "executing",
|
|
360
|
+
});
|
|
361
|
+
const txs = [execCreate];
|
|
362
|
+
txs.push(this.db.tx.thread_executions[executionId].link({ context: context.id }));
|
|
363
|
+
txs.push(this.db.tx.thread_executions[executionId].link({ thread: thread.id }));
|
|
364
|
+
txs.push(this.db.tx.thread_contexts[context.id].link({ currentExecution: executionId }));
|
|
365
|
+
txs.push(this.db.tx.thread_executions[executionId].link({ trigger: triggerEventId }));
|
|
366
|
+
txs.push(this.db.tx.thread_executions[executionId].link({ reaction: reactionEventId }));
|
|
367
|
+
txs.push(this.db.tx.thread_threads[thread.id].update({
|
|
368
|
+
status: "streaming",
|
|
369
|
+
updatedAt: new Date(),
|
|
370
|
+
}));
|
|
371
|
+
await this.db.transact(txs);
|
|
372
|
+
return { id: executionId };
|
|
373
|
+
}
|
|
374
|
+
async completeExecution(contextIdentifier, executionId, status) {
|
|
375
|
+
const { context, thread } = await this.resolveThreadContext(contextIdentifier);
|
|
376
|
+
const txs = [];
|
|
377
|
+
txs.push(this.db.tx.thread_executions[executionId].update({ status, updatedAt: new Date() }));
|
|
378
|
+
// Update context status back to "open" when execution completes
|
|
379
|
+
txs.push(this.db.tx.thread_contexts[context.id].update({ status: "open", updatedAt: new Date() }));
|
|
380
|
+
txs.push(this.db.tx.thread_threads[thread.id].update({
|
|
381
|
+
status: status === "failed" ? "failed" : "open",
|
|
382
|
+
updatedAt: new Date(),
|
|
383
|
+
}));
|
|
384
|
+
await this.db.transact(txs);
|
|
385
|
+
}
|
|
386
|
+
async createStep(params) {
|
|
387
|
+
const stepId = id();
|
|
388
|
+
const eventId = id();
|
|
389
|
+
const txs = [
|
|
390
|
+
this.db.tx.thread_steps[stepId].create({
|
|
391
|
+
createdAt: new Date(),
|
|
392
|
+
status: "running",
|
|
393
|
+
iteration: params.iteration,
|
|
394
|
+
executionId: params.executionId,
|
|
395
|
+
eventId,
|
|
396
|
+
}),
|
|
397
|
+
];
|
|
398
|
+
txs.push(this.db.tx.thread_steps[stepId].link({ execution: params.executionId }));
|
|
399
|
+
await this.db.transact(txs);
|
|
400
|
+
return { id: stepId, eventId };
|
|
401
|
+
}
|
|
402
|
+
async updateStep(stepId, patch) {
|
|
403
|
+
const update = {
|
|
404
|
+
...patch,
|
|
405
|
+
updatedAt: patch.updatedAt ?? new Date(),
|
|
406
|
+
};
|
|
407
|
+
await this.db.transact([this.db.tx.thread_steps[stepId].update(update)]);
|
|
408
|
+
}
|
|
409
|
+
async linkItemToExecution(params) {
|
|
410
|
+
await this.db.transact([
|
|
411
|
+
this.db.tx.thread_items[params.itemId].link({ execution: params.executionId }),
|
|
412
|
+
]);
|
|
413
|
+
}
|
|
414
|
+
async saveStepParts(params) {
|
|
415
|
+
const parts = Array.isArray(params.parts) ? params.parts : [];
|
|
416
|
+
if (parts.length === 0)
|
|
417
|
+
return;
|
|
418
|
+
const txs = parts.map((p, idx) => {
|
|
419
|
+
const key = `${params.stepId}:${idx}`;
|
|
420
|
+
return this.db.tx.thread_parts[lookup("key", key)]
|
|
421
|
+
.update({
|
|
422
|
+
stepId: params.stepId,
|
|
423
|
+
idx,
|
|
424
|
+
type: typeof p?.type === "string" ? String(p.type) : undefined,
|
|
425
|
+
part: p,
|
|
426
|
+
updatedAt: new Date(),
|
|
427
|
+
})
|
|
428
|
+
.link({ step: params.stepId });
|
|
429
|
+
});
|
|
430
|
+
await this.db.transact(txs);
|
|
431
|
+
}
|
|
432
|
+
async itemsToModelMessages(events) {
|
|
433
|
+
// Prefer parts-first reconstruction from thread_parts via the producing step.
|
|
434
|
+
const eventIds = events.map((e) => String(e.id)).filter(Boolean);
|
|
435
|
+
let eventsWithParts = events;
|
|
436
|
+
if (eventIds.length) {
|
|
437
|
+
try {
|
|
438
|
+
// 1) Get steps for these events (eventId is stored on thread_steps)
|
|
439
|
+
const stepsRes = await this.db.query({
|
|
440
|
+
thread_steps: {
|
|
441
|
+
$: {
|
|
442
|
+
where: { eventId: { $in: eventIds } },
|
|
443
|
+
fields: ["id", "eventId"],
|
|
444
|
+
limit: 2000,
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
const steps = stepsRes.thread_steps ?? [];
|
|
449
|
+
const stepIdByEventId = new Map();
|
|
450
|
+
const stepIds = [];
|
|
451
|
+
for (const s of steps) {
|
|
452
|
+
const sid = String(s.id);
|
|
453
|
+
const eid = String(s.eventId);
|
|
454
|
+
if (sid && eid) {
|
|
455
|
+
stepIdByEventId.set(eid, sid);
|
|
456
|
+
stepIds.push(sid);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// 2) Load parts for those steps
|
|
460
|
+
const partsByStepId = new Map();
|
|
461
|
+
if (stepIds.length) {
|
|
462
|
+
const partsRes = await this.db.query({
|
|
463
|
+
thread_parts: {
|
|
464
|
+
$: {
|
|
465
|
+
where: { stepId: { $in: stepIds } },
|
|
466
|
+
order: { idx: "asc" },
|
|
467
|
+
limit: 5000,
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
const rows = partsRes.thread_parts ?? [];
|
|
472
|
+
for (const r of rows) {
|
|
473
|
+
const sid = String(r.stepId);
|
|
474
|
+
const arr = partsByStepId.get(sid) ?? [];
|
|
475
|
+
arr.push(r.part);
|
|
476
|
+
partsByStepId.set(sid, arr);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
// 3) Attach parts onto events
|
|
480
|
+
eventsWithParts = events.map((e) => {
|
|
481
|
+
const eid = String(e.id);
|
|
482
|
+
const sid = stepIdByEventId.get(eid);
|
|
483
|
+
if (!sid)
|
|
484
|
+
return e;
|
|
485
|
+
const parts = partsByStepId.get(sid);
|
|
486
|
+
if (!parts || parts.length === 0)
|
|
487
|
+
return e;
|
|
488
|
+
return { ...e, content: { ...(e?.content ?? {}), parts } };
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
catch {
|
|
492
|
+
// If schema not pushed yet (or table absent), fallback to embedded parts.
|
|
493
|
+
eventsWithParts = events;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
// Default behavior for Instant-backed stories:
|
|
497
|
+
// - Expand file parts into derived `document.parsed` events (persisting parsed content into document_documents)
|
|
498
|
+
// - Then convert expanded events to model messages
|
|
499
|
+
const expanded = await expandEventsWithInstantDocuments({
|
|
500
|
+
db: this.db,
|
|
501
|
+
events: eventsWithParts,
|
|
502
|
+
derivedEventType: "document.parsed",
|
|
503
|
+
});
|
|
504
|
+
return await convertItemsToModelMessages(expanded);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Helper to create a ThreadRuntimeResolver that returns an InstantStore.
|
|
509
|
+
*
|
|
510
|
+
* This keeps the app-level `ekairos.ts` extremely small.
|
|
511
|
+
*/
|
|
512
|
+
export function createInstantStoreRuntime(params) {
|
|
513
|
+
const storesByOrg = new Map();
|
|
514
|
+
return async (env) => {
|
|
515
|
+
const orgId = params.getOrgId?.(env) ??
|
|
516
|
+
(typeof env?.orgId === "string" ? String(env.orgId) : "");
|
|
517
|
+
if (!orgId) {
|
|
518
|
+
throw new Error('[instant] Missing orgId in env. Provide env.orgId (or customize getOrgId).');
|
|
519
|
+
}
|
|
520
|
+
const cached = storesByOrg.get(orgId);
|
|
521
|
+
if (cached)
|
|
522
|
+
return cached;
|
|
523
|
+
const db = await params.getDb(orgId);
|
|
524
|
+
const store = new InstantStore(db);
|
|
525
|
+
const concreteDomain = params.domain?.fromDB ? params.domain.fromDB(db) : undefined;
|
|
526
|
+
const runtime = { store, db, domain: concreteDomain };
|
|
527
|
+
storesByOrg.set(orgId, runtime);
|
|
528
|
+
return runtime;
|
|
529
|
+
};
|
|
530
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { StoryEnvironment } from "./story.config.js";
|
|
2
|
+
import type { ContextEvent, ContextIdentifier } from "./story.store.js";
|
|
3
|
+
import type { StoryStreamOptions } from "./story.engine.js";
|
|
4
|
+
type StoryReactInput = ContextEvent | {
|
|
5
|
+
env?: StoryEnvironment;
|
|
6
|
+
storyKey?: string;
|
|
7
|
+
story?: {
|
|
8
|
+
react: (event: ContextEvent, params: any) => Promise<any> | any;
|
|
9
|
+
key?: string;
|
|
10
|
+
};
|
|
11
|
+
triggerEvent?: ContextEvent;
|
|
12
|
+
incomingEvent?: ContextEvent;
|
|
13
|
+
context?: ContextIdentifier | null;
|
|
14
|
+
contextIdentifier?: ContextIdentifier | null;
|
|
15
|
+
options?: StoryStreamOptions;
|
|
16
|
+
};
|
|
17
|
+
type StoryActionContext = {
|
|
18
|
+
env: StoryEnvironment;
|
|
19
|
+
storyKey?: string;
|
|
20
|
+
story?: {
|
|
21
|
+
react: (event: ContextEvent, params: any) => Promise<any> | any;
|
|
22
|
+
key?: string;
|
|
23
|
+
};
|
|
24
|
+
domain?: {
|
|
25
|
+
meta?: Record<string, unknown>;
|
|
26
|
+
} | null;
|
|
27
|
+
};
|
|
28
|
+
export declare const storyActions: {
|
|
29
|
+
react: {
|
|
30
|
+
description: string;
|
|
31
|
+
execute: (input: StoryReactInput, ctx?: StoryActionContext) => Promise<any>;
|
|
32
|
+
};
|
|
33
|
+
listStories: {
|
|
34
|
+
description: string;
|
|
35
|
+
execute: () => Promise<{
|
|
36
|
+
ok: boolean;
|
|
37
|
+
data: {
|
|
38
|
+
stories: string[];
|
|
39
|
+
};
|
|
40
|
+
}>;
|
|
41
|
+
};
|
|
42
|
+
hasStory: {
|
|
43
|
+
description: string;
|
|
44
|
+
execute: (input: {
|
|
45
|
+
storyKey?: string;
|
|
46
|
+
}) => Promise<{
|
|
47
|
+
ok: boolean;
|
|
48
|
+
error: string;
|
|
49
|
+
data?: undefined;
|
|
50
|
+
} | {
|
|
51
|
+
ok: boolean;
|
|
52
|
+
data: {
|
|
53
|
+
storyKey: string;
|
|
54
|
+
registered: boolean;
|
|
55
|
+
};
|
|
56
|
+
error?: undefined;
|
|
57
|
+
}>;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
export {};
|