yaml-flow 1.0.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 +380 -0
- package/dist/core/index.cjs +557 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +102 -0
- package/dist/core/index.d.ts +102 -0
- package/dist/core/index.js +549 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.cjs +742 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +731 -0
- package/dist/index.js.map +1 -0
- package/dist/stores/file.cjs +115 -0
- package/dist/stores/file.cjs.map +1 -0
- package/dist/stores/file.d.cts +36 -0
- package/dist/stores/file.d.ts +36 -0
- package/dist/stores/file.js +113 -0
- package/dist/stores/file.js.map +1 -0
- package/dist/stores/localStorage.cjs +77 -0
- package/dist/stores/localStorage.cjs.map +1 -0
- package/dist/stores/localStorage.d.cts +34 -0
- package/dist/stores/localStorage.d.ts +34 -0
- package/dist/stores/localStorage.js +75 -0
- package/dist/stores/localStorage.js.map +1 -0
- package/dist/stores/memory.cjs +48 -0
- package/dist/stores/memory.cjs.map +1 -0
- package/dist/stores/memory.d.cts +27 -0
- package/dist/stores/memory.d.ts +27 -0
- package/dist/stores/memory.js +46 -0
- package/dist/stores/memory.js.map +1 -0
- package/dist/types-BoWndaAJ.d.cts +237 -0
- package/dist/types-BoWndaAJ.d.ts +237 -0
- package/package.json +83 -0
- package/schema/flow.schema.json +159 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/stores/memory.ts
|
|
4
|
+
var MemoryStore = class {
|
|
5
|
+
runs = /* @__PURE__ */ new Map();
|
|
6
|
+
data = /* @__PURE__ */ new Map();
|
|
7
|
+
async saveRunState(runId, state) {
|
|
8
|
+
this.runs.set(runId, { ...state });
|
|
9
|
+
}
|
|
10
|
+
async loadRunState(runId) {
|
|
11
|
+
const state = this.runs.get(runId);
|
|
12
|
+
return state ? { ...state } : null;
|
|
13
|
+
}
|
|
14
|
+
async deleteRunState(runId) {
|
|
15
|
+
this.runs.delete(runId);
|
|
16
|
+
this.data.delete(runId);
|
|
17
|
+
}
|
|
18
|
+
async setData(runId, key, value) {
|
|
19
|
+
if (!this.data.has(runId)) {
|
|
20
|
+
this.data.set(runId, {});
|
|
21
|
+
}
|
|
22
|
+
const runData = this.data.get(runId);
|
|
23
|
+
runData[key] = value;
|
|
24
|
+
}
|
|
25
|
+
async getData(runId, key) {
|
|
26
|
+
return this.data.get(runId)?.[key];
|
|
27
|
+
}
|
|
28
|
+
async getAllData(runId) {
|
|
29
|
+
return { ...this.data.get(runId) ?? {} };
|
|
30
|
+
}
|
|
31
|
+
async clearData(runId) {
|
|
32
|
+
this.data.delete(runId);
|
|
33
|
+
}
|
|
34
|
+
async listRuns() {
|
|
35
|
+
return Array.from(this.runs.keys());
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Clear all data (useful for testing)
|
|
39
|
+
*/
|
|
40
|
+
clear() {
|
|
41
|
+
this.runs.clear();
|
|
42
|
+
this.data.clear();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/core/engine.ts
|
|
47
|
+
function generateRunId() {
|
|
48
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
49
|
+
return crypto.randomUUID();
|
|
50
|
+
}
|
|
51
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
52
|
+
const r = Math.random() * 16 | 0;
|
|
53
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
54
|
+
return v.toString(16);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
var FlowEngine = class {
|
|
58
|
+
flow;
|
|
59
|
+
handlers;
|
|
60
|
+
store;
|
|
61
|
+
components;
|
|
62
|
+
options;
|
|
63
|
+
listeners = /* @__PURE__ */ new Map();
|
|
64
|
+
aborted = false;
|
|
65
|
+
constructor(flow, handlers, options = {}) {
|
|
66
|
+
this.flow = flow;
|
|
67
|
+
this.handlers = new Map(Object.entries(handlers));
|
|
68
|
+
this.store = options.store ?? new MemoryStore();
|
|
69
|
+
this.components = options.components ?? {};
|
|
70
|
+
this.options = options;
|
|
71
|
+
if (options.signal) {
|
|
72
|
+
options.signal.addEventListener("abort", () => {
|
|
73
|
+
this.aborted = true;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
this.validateFlow();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Validate the flow configuration
|
|
80
|
+
*/
|
|
81
|
+
validateFlow() {
|
|
82
|
+
const { settings, steps, terminal_states } = this.flow;
|
|
83
|
+
if (!settings?.start_step) {
|
|
84
|
+
throw new Error("Flow must have settings.start_step defined");
|
|
85
|
+
}
|
|
86
|
+
if (!steps || Object.keys(steps).length === 0) {
|
|
87
|
+
throw new Error("Flow must have at least one step defined");
|
|
88
|
+
}
|
|
89
|
+
if (!terminal_states || Object.keys(terminal_states).length === 0) {
|
|
90
|
+
throw new Error("Flow must have at least one terminal_state defined");
|
|
91
|
+
}
|
|
92
|
+
if (!steps[settings.start_step] && !terminal_states[settings.start_step]) {
|
|
93
|
+
throw new Error(`Start step "${settings.start_step}" not found in steps or terminal_states`);
|
|
94
|
+
}
|
|
95
|
+
for (const [stepName, stepConfig] of Object.entries(steps)) {
|
|
96
|
+
for (const [result, target] of Object.entries(stepConfig.transitions)) {
|
|
97
|
+
if (!steps[target] && !terminal_states[target]) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`Step "${stepName}" transition "${result}" points to unknown step "${target}"`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Run the flow from the start
|
|
107
|
+
*/
|
|
108
|
+
async run(initialData) {
|
|
109
|
+
const runId = generateRunId();
|
|
110
|
+
const startedAt = Date.now();
|
|
111
|
+
const runState = {
|
|
112
|
+
runId,
|
|
113
|
+
flowId: this.flow.id ?? "unnamed",
|
|
114
|
+
currentStep: this.flow.settings.start_step,
|
|
115
|
+
status: "running",
|
|
116
|
+
stepHistory: [],
|
|
117
|
+
iterationCounts: {},
|
|
118
|
+
retryCounts: {},
|
|
119
|
+
startedAt,
|
|
120
|
+
updatedAt: startedAt
|
|
121
|
+
};
|
|
122
|
+
await this.store.saveRunState(runId, runState);
|
|
123
|
+
if (initialData) {
|
|
124
|
+
for (const [key, value] of Object.entries(initialData)) {
|
|
125
|
+
await this.store.setData(runId, key, value);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
this.emit({
|
|
129
|
+
type: "flow:start",
|
|
130
|
+
runId,
|
|
131
|
+
timestamp: startedAt,
|
|
132
|
+
data: { initialData }
|
|
133
|
+
});
|
|
134
|
+
try {
|
|
135
|
+
return await this.executeLoop(runId, runState, startedAt);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
138
|
+
this.emit({
|
|
139
|
+
type: "flow:error",
|
|
140
|
+
runId,
|
|
141
|
+
timestamp: Date.now(),
|
|
142
|
+
data: { error: err.message }
|
|
143
|
+
});
|
|
144
|
+
this.options.onError?.(err);
|
|
145
|
+
runState.status = "failed";
|
|
146
|
+
runState.updatedAt = Date.now();
|
|
147
|
+
await this.store.saveRunState(runId, runState);
|
|
148
|
+
return {
|
|
149
|
+
runId,
|
|
150
|
+
status: "failed",
|
|
151
|
+
data: await this.store.getAllData(runId),
|
|
152
|
+
finalStep: runState.currentStep,
|
|
153
|
+
stepHistory: runState.stepHistory,
|
|
154
|
+
durationMs: Date.now() - startedAt,
|
|
155
|
+
error: err
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Resume a paused or interrupted flow
|
|
161
|
+
*/
|
|
162
|
+
async resume(runId) {
|
|
163
|
+
const runState = await this.store.loadRunState(runId);
|
|
164
|
+
if (!runState) {
|
|
165
|
+
throw new Error(`No run found with ID: ${runId}`);
|
|
166
|
+
}
|
|
167
|
+
if (runState.status === "completed" || runState.status === "failed") {
|
|
168
|
+
throw new Error(`Cannot resume a ${runState.status} run`);
|
|
169
|
+
}
|
|
170
|
+
const startedAt = runState.startedAt;
|
|
171
|
+
runState.status = "running";
|
|
172
|
+
runState.pausedAt = void 0;
|
|
173
|
+
runState.updatedAt = Date.now();
|
|
174
|
+
await this.store.saveRunState(runId, runState);
|
|
175
|
+
this.emit({
|
|
176
|
+
type: "flow:resumed",
|
|
177
|
+
runId,
|
|
178
|
+
timestamp: Date.now(),
|
|
179
|
+
data: { currentStep: runState.currentStep }
|
|
180
|
+
});
|
|
181
|
+
return this.executeLoop(runId, runState, startedAt);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Pause a running flow
|
|
185
|
+
*/
|
|
186
|
+
async pause(runId) {
|
|
187
|
+
const runState = await this.store.loadRunState(runId);
|
|
188
|
+
if (!runState) {
|
|
189
|
+
throw new Error(`No run found with ID: ${runId}`);
|
|
190
|
+
}
|
|
191
|
+
runState.status = "paused";
|
|
192
|
+
runState.pausedAt = Date.now();
|
|
193
|
+
runState.updatedAt = Date.now();
|
|
194
|
+
await this.store.saveRunState(runId, runState);
|
|
195
|
+
this.emit({
|
|
196
|
+
type: "flow:paused",
|
|
197
|
+
runId,
|
|
198
|
+
timestamp: Date.now(),
|
|
199
|
+
data: { currentStep: runState.currentStep }
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Main execution loop
|
|
204
|
+
*/
|
|
205
|
+
async executeLoop(runId, runState, startedAt) {
|
|
206
|
+
const maxSteps = this.flow.settings.max_total_steps ?? 100;
|
|
207
|
+
const timeoutMs = this.flow.settings.timeout_ms;
|
|
208
|
+
let iterations = 0;
|
|
209
|
+
while (iterations < maxSteps) {
|
|
210
|
+
if (this.aborted) {
|
|
211
|
+
runState.status = "cancelled";
|
|
212
|
+
runState.updatedAt = Date.now();
|
|
213
|
+
await this.store.saveRunState(runId, runState);
|
|
214
|
+
return {
|
|
215
|
+
runId,
|
|
216
|
+
status: "cancelled",
|
|
217
|
+
data: await this.store.getAllData(runId),
|
|
218
|
+
finalStep: runState.currentStep,
|
|
219
|
+
stepHistory: runState.stepHistory,
|
|
220
|
+
durationMs: Date.now() - startedAt
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (timeoutMs && Date.now() - startedAt > timeoutMs) {
|
|
224
|
+
runState.status = "completed";
|
|
225
|
+
runState.updatedAt = Date.now();
|
|
226
|
+
await this.store.saveRunState(runId, runState);
|
|
227
|
+
return {
|
|
228
|
+
runId,
|
|
229
|
+
status: "timeout",
|
|
230
|
+
intent: "timeout",
|
|
231
|
+
data: await this.store.getAllData(runId),
|
|
232
|
+
finalStep: runState.currentStep,
|
|
233
|
+
stepHistory: runState.stepHistory,
|
|
234
|
+
durationMs: Date.now() - startedAt
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const currentStep = runState.currentStep;
|
|
238
|
+
const terminalState = this.flow.terminal_states[currentStep];
|
|
239
|
+
if (terminalState) {
|
|
240
|
+
runState.status = "completed";
|
|
241
|
+
runState.updatedAt = Date.now();
|
|
242
|
+
await this.store.saveRunState(runId, runState);
|
|
243
|
+
const allData = await this.store.getAllData(runId);
|
|
244
|
+
const returnData = this.extractReturnData(terminalState.return_artifacts, allData);
|
|
245
|
+
const result = {
|
|
246
|
+
runId,
|
|
247
|
+
status: "completed",
|
|
248
|
+
intent: terminalState.return_intent,
|
|
249
|
+
data: returnData,
|
|
250
|
+
finalStep: currentStep,
|
|
251
|
+
stepHistory: runState.stepHistory,
|
|
252
|
+
durationMs: Date.now() - startedAt
|
|
253
|
+
};
|
|
254
|
+
this.emit({
|
|
255
|
+
type: "flow:complete",
|
|
256
|
+
runId,
|
|
257
|
+
timestamp: Date.now(),
|
|
258
|
+
data: { ...result }
|
|
259
|
+
});
|
|
260
|
+
this.options.onComplete?.(result);
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
const stepConfig = this.flow.steps[currentStep];
|
|
264
|
+
if (!stepConfig) {
|
|
265
|
+
throw new Error(`Step "${currentStep}" not found in flow configuration`);
|
|
266
|
+
}
|
|
267
|
+
if (stepConfig.circuit_breaker) {
|
|
268
|
+
const count = runState.iterationCounts[currentStep] ?? 0;
|
|
269
|
+
if (count >= stepConfig.circuit_breaker.max_iterations) {
|
|
270
|
+
runState.currentStep = stepConfig.circuit_breaker.on_open;
|
|
271
|
+
runState.updatedAt = Date.now();
|
|
272
|
+
await this.store.saveRunState(runId, runState);
|
|
273
|
+
iterations++;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
runState.iterationCounts[currentStep] = (runState.iterationCounts[currentStep] ?? 0) + 1;
|
|
278
|
+
const stepResult = await this.executeStep(runId, currentStep, stepConfig);
|
|
279
|
+
if (stepResult.result === "failure" && stepConfig.retry) {
|
|
280
|
+
const retryCount = runState.retryCounts[currentStep] ?? 0;
|
|
281
|
+
if (retryCount < stepConfig.retry.max_attempts) {
|
|
282
|
+
runState.retryCounts[currentStep] = retryCount + 1;
|
|
283
|
+
if (stepConfig.retry.delay_ms) {
|
|
284
|
+
const delay = stepConfig.retry.backoff_multiplier ? stepConfig.retry.delay_ms * Math.pow(stepConfig.retry.backoff_multiplier, retryCount) : stepConfig.retry.delay_ms;
|
|
285
|
+
await this.sleep(delay);
|
|
286
|
+
}
|
|
287
|
+
iterations++;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const nextStep = stepConfig.transitions[stepResult.result];
|
|
292
|
+
if (!nextStep) {
|
|
293
|
+
throw new Error(
|
|
294
|
+
`No transition defined for result "${stepResult.result}" in step "${currentStep}"`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
runState.stepHistory.push(currentStep);
|
|
298
|
+
runState.currentStep = nextStep;
|
|
299
|
+
runState.updatedAt = Date.now();
|
|
300
|
+
runState.retryCounts[currentStep] = 0;
|
|
301
|
+
await this.store.saveRunState(runId, runState);
|
|
302
|
+
this.emit({
|
|
303
|
+
type: "transition",
|
|
304
|
+
runId,
|
|
305
|
+
timestamp: Date.now(),
|
|
306
|
+
data: { from: currentStep, to: nextStep, result: stepResult.result }
|
|
307
|
+
});
|
|
308
|
+
this.options.onTransition?.(currentStep, nextStep);
|
|
309
|
+
iterations++;
|
|
310
|
+
}
|
|
311
|
+
runState.status = "completed";
|
|
312
|
+
runState.updatedAt = Date.now();
|
|
313
|
+
await this.store.saveRunState(runId, runState);
|
|
314
|
+
return {
|
|
315
|
+
runId,
|
|
316
|
+
status: "max_iterations",
|
|
317
|
+
intent: "max_iterations",
|
|
318
|
+
data: await this.store.getAllData(runId),
|
|
319
|
+
finalStep: runState.currentStep,
|
|
320
|
+
stepHistory: runState.stepHistory,
|
|
321
|
+
durationMs: Date.now() - startedAt
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Execute a single step
|
|
326
|
+
*/
|
|
327
|
+
async executeStep(runId, stepName, stepConfig) {
|
|
328
|
+
const handler = this.handlers.get(stepName);
|
|
329
|
+
if (!handler) {
|
|
330
|
+
throw new Error(`No handler registered for step "${stepName}"`);
|
|
331
|
+
}
|
|
332
|
+
const allData = await this.store.getAllData(runId);
|
|
333
|
+
const input = {};
|
|
334
|
+
if (stepConfig.expects_data) {
|
|
335
|
+
for (const key of stepConfig.expects_data) {
|
|
336
|
+
input[key] = allData[key];
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
Object.assign(input, allData);
|
|
340
|
+
}
|
|
341
|
+
const context = {
|
|
342
|
+
runId,
|
|
343
|
+
stepName,
|
|
344
|
+
components: this.components,
|
|
345
|
+
store: this.store,
|
|
346
|
+
signal: this.options.signal,
|
|
347
|
+
emit: (event, data) => {
|
|
348
|
+
this.emit({
|
|
349
|
+
type: "step:complete",
|
|
350
|
+
// Custom events map to step:complete
|
|
351
|
+
runId,
|
|
352
|
+
timestamp: Date.now(),
|
|
353
|
+
data: { event, payload: data }
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
this.emit({
|
|
358
|
+
type: "step:start",
|
|
359
|
+
runId,
|
|
360
|
+
timestamp: Date.now(),
|
|
361
|
+
data: { step: stepName, input }
|
|
362
|
+
});
|
|
363
|
+
try {
|
|
364
|
+
const result = await handler(input, context);
|
|
365
|
+
if (result.data) {
|
|
366
|
+
for (const [key, value] of Object.entries(result.data)) {
|
|
367
|
+
await this.store.setData(runId, key, value);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
this.emit({
|
|
371
|
+
type: "step:complete",
|
|
372
|
+
runId,
|
|
373
|
+
timestamp: Date.now(),
|
|
374
|
+
data: { step: stepName, result: result.result, outputKeys: Object.keys(result.data ?? {}) }
|
|
375
|
+
});
|
|
376
|
+
this.options.onStep?.(stepName, result);
|
|
377
|
+
return result;
|
|
378
|
+
} catch (error) {
|
|
379
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
380
|
+
this.emit({
|
|
381
|
+
type: "step:error",
|
|
382
|
+
runId,
|
|
383
|
+
timestamp: Date.now(),
|
|
384
|
+
data: { step: stepName, error: err.message }
|
|
385
|
+
});
|
|
386
|
+
return { result: "failure", data: { error: err.message } };
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Extract data to return based on return_artifacts configuration
|
|
391
|
+
*/
|
|
392
|
+
extractReturnData(returnArtifacts, allData) {
|
|
393
|
+
if (returnArtifacts === false || returnArtifacts === void 0) {
|
|
394
|
+
return {};
|
|
395
|
+
}
|
|
396
|
+
if (typeof returnArtifacts === "string") {
|
|
397
|
+
return { [returnArtifacts]: allData[returnArtifacts] };
|
|
398
|
+
}
|
|
399
|
+
if (Array.isArray(returnArtifacts)) {
|
|
400
|
+
const result = {};
|
|
401
|
+
for (const key of returnArtifacts) {
|
|
402
|
+
result[key] = allData[key];
|
|
403
|
+
}
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
return allData;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Sleep helper
|
|
410
|
+
*/
|
|
411
|
+
sleep(ms) {
|
|
412
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Subscribe to flow events
|
|
416
|
+
*/
|
|
417
|
+
on(type, listener) {
|
|
418
|
+
if (!this.listeners.has(type)) {
|
|
419
|
+
this.listeners.set(type, /* @__PURE__ */ new Set());
|
|
420
|
+
}
|
|
421
|
+
this.listeners.get(type).add(listener);
|
|
422
|
+
return () => {
|
|
423
|
+
this.listeners.get(type)?.delete(listener);
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Emit an event
|
|
428
|
+
*/
|
|
429
|
+
emit(event) {
|
|
430
|
+
const typeListeners = this.listeners.get(event.type);
|
|
431
|
+
if (typeListeners) {
|
|
432
|
+
for (const listener of typeListeners) {
|
|
433
|
+
try {
|
|
434
|
+
listener(event);
|
|
435
|
+
} catch {
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Get the current store instance
|
|
442
|
+
*/
|
|
443
|
+
getStore() {
|
|
444
|
+
return this.store;
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
function createEngine(flow, handlers, options) {
|
|
448
|
+
return new FlowEngine(flow, handlers, options);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/core/loader.ts
|
|
452
|
+
async function parseYaml(yamlString) {
|
|
453
|
+
const yaml = await import('yaml');
|
|
454
|
+
return yaml.parse(yamlString);
|
|
455
|
+
}
|
|
456
|
+
async function loadFlowFromUrl(url) {
|
|
457
|
+
const response = await fetch(url);
|
|
458
|
+
if (!response.ok) {
|
|
459
|
+
throw new Error(`Failed to load flow from ${url}: ${response.statusText}`);
|
|
460
|
+
}
|
|
461
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
462
|
+
const text = await response.text();
|
|
463
|
+
if (contentType.includes("json") || url.endsWith(".json")) {
|
|
464
|
+
return JSON.parse(text);
|
|
465
|
+
}
|
|
466
|
+
return parseYaml(text);
|
|
467
|
+
}
|
|
468
|
+
async function loadFlowFromFile(filePath) {
|
|
469
|
+
const fs = await import('fs/promises');
|
|
470
|
+
const text = await fs.readFile(filePath, "utf-8");
|
|
471
|
+
if (filePath.endsWith(".json")) {
|
|
472
|
+
return JSON.parse(text);
|
|
473
|
+
}
|
|
474
|
+
return parseYaml(text);
|
|
475
|
+
}
|
|
476
|
+
function validateFlowConfig(flow) {
|
|
477
|
+
const errors = [];
|
|
478
|
+
if (!flow || typeof flow !== "object") {
|
|
479
|
+
return ["Flow must be an object"];
|
|
480
|
+
}
|
|
481
|
+
const f = flow;
|
|
482
|
+
if (!f.settings || typeof f.settings !== "object") {
|
|
483
|
+
errors.push('Flow must have a "settings" object');
|
|
484
|
+
} else {
|
|
485
|
+
const settings = f.settings;
|
|
486
|
+
if (typeof settings.start_step !== "string") {
|
|
487
|
+
errors.push("settings.start_step must be a string");
|
|
488
|
+
}
|
|
489
|
+
if (settings.max_total_steps !== void 0 && typeof settings.max_total_steps !== "number") {
|
|
490
|
+
errors.push("settings.max_total_steps must be a number");
|
|
491
|
+
}
|
|
492
|
+
if (settings.timeout_ms !== void 0 && typeof settings.timeout_ms !== "number") {
|
|
493
|
+
errors.push("settings.timeout_ms must be a number");
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if (!f.steps || typeof f.steps !== "object") {
|
|
497
|
+
errors.push('Flow must have a "steps" object');
|
|
498
|
+
} else {
|
|
499
|
+
const steps = f.steps;
|
|
500
|
+
for (const [stepName, stepConfig] of Object.entries(steps)) {
|
|
501
|
+
if (!stepConfig || typeof stepConfig !== "object") {
|
|
502
|
+
errors.push(`Step "${stepName}" must be an object`);
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
const step = stepConfig;
|
|
506
|
+
if (!step.transitions || typeof step.transitions !== "object") {
|
|
507
|
+
errors.push(`Step "${stepName}" must have a "transitions" object`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (!f.terminal_states || typeof f.terminal_states !== "object") {
|
|
512
|
+
errors.push('Flow must have a "terminal_states" object');
|
|
513
|
+
} else {
|
|
514
|
+
const terminals = f.terminal_states;
|
|
515
|
+
for (const [name, config] of Object.entries(terminals)) {
|
|
516
|
+
if (!config || typeof config !== "object") {
|
|
517
|
+
errors.push(`Terminal state "${name}" must be an object`);
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
const terminal = config;
|
|
521
|
+
if (typeof terminal.return_intent !== "string") {
|
|
522
|
+
errors.push(`Terminal state "${name}" must have a "return_intent" string`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return errors;
|
|
527
|
+
}
|
|
528
|
+
async function loadFlow(source) {
|
|
529
|
+
let flow;
|
|
530
|
+
if (typeof source === "string") {
|
|
531
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
532
|
+
flow = await loadFlowFromUrl(source);
|
|
533
|
+
} else if (source.includes("{")) {
|
|
534
|
+
flow = JSON.parse(source);
|
|
535
|
+
} else {
|
|
536
|
+
flow = await loadFlowFromFile(source);
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
flow = source;
|
|
540
|
+
}
|
|
541
|
+
const errors = validateFlowConfig(flow);
|
|
542
|
+
if (errors.length > 0) {
|
|
543
|
+
throw new Error(`Invalid flow configuration:
|
|
544
|
+
- ${errors.join("\n- ")}`);
|
|
545
|
+
}
|
|
546
|
+
return flow;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/stores/localStorage.ts
|
|
550
|
+
var LocalStorageStore = class {
|
|
551
|
+
prefix;
|
|
552
|
+
constructor(options = {}) {
|
|
553
|
+
this.prefix = options.prefix ?? "yamlflow";
|
|
554
|
+
if (typeof localStorage === "undefined") {
|
|
555
|
+
throw new Error("LocalStorageStore requires localStorage (browser environment)");
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
runKey(runId) {
|
|
559
|
+
return `${this.prefix}:run:${runId}`;
|
|
560
|
+
}
|
|
561
|
+
dataKey(runId) {
|
|
562
|
+
return `${this.prefix}:data:${runId}`;
|
|
563
|
+
}
|
|
564
|
+
indexKey() {
|
|
565
|
+
return `${this.prefix}:runs`;
|
|
566
|
+
}
|
|
567
|
+
async saveRunState(runId, state) {
|
|
568
|
+
localStorage.setItem(this.runKey(runId), JSON.stringify(state));
|
|
569
|
+
const runs = await this.listRuns();
|
|
570
|
+
if (!runs.includes(runId)) {
|
|
571
|
+
runs.push(runId);
|
|
572
|
+
localStorage.setItem(this.indexKey(), JSON.stringify(runs));
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
async loadRunState(runId) {
|
|
576
|
+
const raw = localStorage.getItem(this.runKey(runId));
|
|
577
|
+
return raw ? JSON.parse(raw) : null;
|
|
578
|
+
}
|
|
579
|
+
async deleteRunState(runId) {
|
|
580
|
+
localStorage.removeItem(this.runKey(runId));
|
|
581
|
+
localStorage.removeItem(this.dataKey(runId));
|
|
582
|
+
const runs = await this.listRuns();
|
|
583
|
+
const filtered = runs.filter((id) => id !== runId);
|
|
584
|
+
localStorage.setItem(this.indexKey(), JSON.stringify(filtered));
|
|
585
|
+
}
|
|
586
|
+
async setData(runId, key, value) {
|
|
587
|
+
const allData = await this.getAllData(runId);
|
|
588
|
+
allData[key] = value;
|
|
589
|
+
localStorage.setItem(this.dataKey(runId), JSON.stringify(allData));
|
|
590
|
+
}
|
|
591
|
+
async getData(runId, key) {
|
|
592
|
+
const allData = await this.getAllData(runId);
|
|
593
|
+
return allData[key];
|
|
594
|
+
}
|
|
595
|
+
async getAllData(runId) {
|
|
596
|
+
const raw = localStorage.getItem(this.dataKey(runId));
|
|
597
|
+
return raw ? JSON.parse(raw) : {};
|
|
598
|
+
}
|
|
599
|
+
async clearData(runId) {
|
|
600
|
+
localStorage.removeItem(this.dataKey(runId));
|
|
601
|
+
}
|
|
602
|
+
async listRuns() {
|
|
603
|
+
const raw = localStorage.getItem(this.indexKey());
|
|
604
|
+
return raw ? JSON.parse(raw) : [];
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Clear all flow data from localStorage
|
|
608
|
+
*/
|
|
609
|
+
clearAll() {
|
|
610
|
+
const keysToRemove = [];
|
|
611
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
612
|
+
const key = localStorage.key(i);
|
|
613
|
+
if (key?.startsWith(this.prefix + ":")) {
|
|
614
|
+
keysToRemove.push(key);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
// src/stores/file.ts
|
|
622
|
+
var FileStore = class {
|
|
623
|
+
directory;
|
|
624
|
+
fs = null;
|
|
625
|
+
path = null;
|
|
626
|
+
constructor(options) {
|
|
627
|
+
this.directory = options.directory;
|
|
628
|
+
}
|
|
629
|
+
async ensureModules() {
|
|
630
|
+
if (!this.fs || !this.path) {
|
|
631
|
+
this.fs = await import('fs/promises');
|
|
632
|
+
this.path = await import('path');
|
|
633
|
+
await this.fs.mkdir(this.directory, { recursive: true });
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
runPath(runId) {
|
|
637
|
+
return this.path.join(this.directory, `${runId}.run.json`);
|
|
638
|
+
}
|
|
639
|
+
dataPath(runId) {
|
|
640
|
+
return this.path.join(this.directory, `${runId}.data.json`);
|
|
641
|
+
}
|
|
642
|
+
async saveRunState(runId, state) {
|
|
643
|
+
await this.ensureModules();
|
|
644
|
+
await this.fs.writeFile(
|
|
645
|
+
this.runPath(runId),
|
|
646
|
+
JSON.stringify(state, null, 2),
|
|
647
|
+
"utf-8"
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
async loadRunState(runId) {
|
|
651
|
+
await this.ensureModules();
|
|
652
|
+
try {
|
|
653
|
+
const raw = await this.fs.readFile(this.runPath(runId), "utf-8");
|
|
654
|
+
return JSON.parse(raw);
|
|
655
|
+
} catch (err) {
|
|
656
|
+
if (err.code === "ENOENT") {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
throw err;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
async deleteRunState(runId) {
|
|
663
|
+
await this.ensureModules();
|
|
664
|
+
try {
|
|
665
|
+
await this.fs.unlink(this.runPath(runId));
|
|
666
|
+
} catch (err) {
|
|
667
|
+
if (err.code !== "ENOENT") throw err;
|
|
668
|
+
}
|
|
669
|
+
try {
|
|
670
|
+
await this.fs.unlink(this.dataPath(runId));
|
|
671
|
+
} catch (err) {
|
|
672
|
+
if (err.code !== "ENOENT") throw err;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
async setData(runId, key, value) {
|
|
676
|
+
await this.ensureModules();
|
|
677
|
+
const allData = await this.getAllData(runId);
|
|
678
|
+
allData[key] = value;
|
|
679
|
+
await this.fs.writeFile(
|
|
680
|
+
this.dataPath(runId),
|
|
681
|
+
JSON.stringify(allData, null, 2),
|
|
682
|
+
"utf-8"
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
async getData(runId, key) {
|
|
686
|
+
const allData = await this.getAllData(runId);
|
|
687
|
+
return allData[key];
|
|
688
|
+
}
|
|
689
|
+
async getAllData(runId) {
|
|
690
|
+
await this.ensureModules();
|
|
691
|
+
try {
|
|
692
|
+
const raw = await this.fs.readFile(this.dataPath(runId), "utf-8");
|
|
693
|
+
return JSON.parse(raw);
|
|
694
|
+
} catch (err) {
|
|
695
|
+
if (err.code === "ENOENT") {
|
|
696
|
+
return {};
|
|
697
|
+
}
|
|
698
|
+
throw err;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
async clearData(runId) {
|
|
702
|
+
await this.ensureModules();
|
|
703
|
+
try {
|
|
704
|
+
await this.fs.unlink(this.dataPath(runId));
|
|
705
|
+
} catch (err) {
|
|
706
|
+
if (err.code !== "ENOENT") throw err;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
async listRuns() {
|
|
710
|
+
await this.ensureModules();
|
|
711
|
+
try {
|
|
712
|
+
const files = await this.fs.readdir(this.directory);
|
|
713
|
+
return files.filter((f) => f.endsWith(".run.json")).map((f) => f.replace(".run.json", ""));
|
|
714
|
+
} catch (err) {
|
|
715
|
+
if (err.code === "ENOENT") {
|
|
716
|
+
return [];
|
|
717
|
+
}
|
|
718
|
+
throw err;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Clear all flow data from directory
|
|
723
|
+
*/
|
|
724
|
+
async clearAll() {
|
|
725
|
+
await this.ensureModules();
|
|
726
|
+
const runs = await this.listRuns();
|
|
727
|
+
await Promise.all(runs.map((runId) => this.deleteRunState(runId)));
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
exports.FileStore = FileStore;
|
|
732
|
+
exports.FlowEngine = FlowEngine;
|
|
733
|
+
exports.LocalStorageStore = LocalStorageStore;
|
|
734
|
+
exports.MemoryStore = MemoryStore;
|
|
735
|
+
exports.createEngine = createEngine;
|
|
736
|
+
exports.loadFlow = loadFlow;
|
|
737
|
+
exports.loadFlowFromFile = loadFlowFromFile;
|
|
738
|
+
exports.loadFlowFromUrl = loadFlowFromUrl;
|
|
739
|
+
exports.parseYaml = parseYaml;
|
|
740
|
+
exports.validateFlowConfig = validateFlowConfig;
|
|
741
|
+
//# sourceMappingURL=index.cjs.map
|
|
742
|
+
//# sourceMappingURL=index.cjs.map
|