@zhivex-ai/core 0.7.0 → 0.8.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 +5 -0
- package/dist/advanced-tool-registry.d.ts +112 -0
- package/dist/advanced-tool-registry.d.ts.map +1 -0
- package/dist/advanced-tool-registry.js +407 -0
- package/dist/advanced-tool-registry.js.map +1 -0
- package/dist/agent-evaluation.d.ts +176 -0
- package/dist/agent-evaluation.d.ts.map +1 -0
- package/dist/agent-evaluation.js +334 -0
- package/dist/agent-evaluation.js.map +1 -0
- package/dist/agent-store.d.ts.map +1 -1
- package/dist/agent-store.js +226 -8
- package/dist/agent-store.js.map +1 -1
- package/dist/agent-trace.d.ts +127 -0
- package/dist/agent-trace.d.ts.map +1 -0
- package/dist/agent-trace.js +331 -0
- package/dist/agent-trace.js.map +1 -0
- package/dist/agent.d.ts +12 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +469 -36
- package/dist/agent.js.map +1 -1
- package/dist/api-stability.d.ts +9 -0
- package/dist/api-stability.d.ts.map +1 -0
- package/dist/api-stability.js +261 -0
- package/dist/api-stability.js.map +1 -0
- package/dist/artifact.d.ts +165 -0
- package/dist/artifact.d.ts.map +1 -0
- package/dist/artifact.js +994 -0
- package/dist/artifact.js.map +1 -0
- package/dist/errors.d.ts +2 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -0
- package/dist/errors.js.map +1 -1
- package/dist/generate-text.d.ts.map +1 -1
- package/dist/generate-text.js +6 -2
- package/dist/generate-text.js.map +1 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/live-agent.d.ts.map +1 -1
- package/dist/live-agent.js +7 -1
- package/dist/live-agent.js.map +1 -1
- package/dist/provider-parity.d.ts +63 -0
- package/dist/provider-parity.d.ts.map +1 -0
- package/dist/provider-parity.js +175 -0
- package/dist/provider-parity.js.map +1 -0
- package/dist/runner.d.ts +111 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +635 -0
- package/dist/runner.js.map +1 -0
- package/dist/safety-policy.d.ts +65 -0
- package/dist/safety-policy.d.ts.map +1 -0
- package/dist/safety-policy.js +308 -0
- package/dist/safety-policy.js.map +1 -0
- package/dist/types.d.ts +119 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/workflow-artifacts.d.ts +28 -0
- package/dist/workflow-artifacts.d.ts.map +1 -0
- package/dist/workflow-artifacts.js +86 -0
- package/dist/workflow-artifacts.js.map +1 -0
- package/dist/workflow-evaluation-diff.d.ts +51 -0
- package/dist/workflow-evaluation-diff.d.ts.map +1 -0
- package/dist/workflow-evaluation-diff.js +141 -0
- package/dist/workflow-evaluation-diff.js.map +1 -0
- package/dist/workflow-evaluation.d.ts +94 -0
- package/dist/workflow-evaluation.d.ts.map +1 -0
- package/dist/workflow-evaluation.js +210 -0
- package/dist/workflow-evaluation.js.map +1 -0
- package/dist/workflow-state-service.d.ts +67 -0
- package/dist/workflow-state-service.d.ts.map +1 -0
- package/dist/workflow-state-service.js +498 -0
- package/dist/workflow-state-service.js.map +1 -0
- package/dist/workflow.d.ts +206 -0
- package/dist/workflow.d.ts.map +1 -0
- package/dist/workflow.js +727 -0
- package/dist/workflow.js.map +1 -0
- package/package.json +1 -1
package/dist/workflow.js
ADDED
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
import { ValidationError } from "./errors.js";
|
|
2
|
+
const randomId = (prefix) => `${prefix}_${Math.random().toString(36).slice(2, 10)}`;
|
|
3
|
+
const cloneJson = (value) => JSON.parse(JSON.stringify(value));
|
|
4
|
+
export const WORKFLOW_RUN_STATE_SCHEMA_VERSION = 1;
|
|
5
|
+
const toOutput = (state) => ({
|
|
6
|
+
status: state.status,
|
|
7
|
+
state: cloneJson(state),
|
|
8
|
+
outputs: cloneJson(state.outputs),
|
|
9
|
+
steps: cloneJson(state.steps),
|
|
10
|
+
session: state.session ? cloneJson(state.session) : undefined
|
|
11
|
+
});
|
|
12
|
+
const createBaseState = (workflow, input) => {
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
return {
|
|
15
|
+
schemaVersion: WORKFLOW_RUN_STATE_SCHEMA_VERSION,
|
|
16
|
+
workflowId: workflow.id,
|
|
17
|
+
runId: randomId("wfr"),
|
|
18
|
+
userId: input.userId,
|
|
19
|
+
sessionId: input.sessionId ?? randomId("sess"),
|
|
20
|
+
status: "running",
|
|
21
|
+
input: input.input === undefined ? undefined : cloneJson(input.input),
|
|
22
|
+
outputs: {},
|
|
23
|
+
steps: [],
|
|
24
|
+
currentStepIndex: 0,
|
|
25
|
+
metadata: input.metadata ? cloneJson(input.metadata) : undefined,
|
|
26
|
+
createdAt: now,
|
|
27
|
+
updatedAt: now
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export const normalizeWorkflowRunState = (value) => {
|
|
31
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
32
|
+
throw new ValidationError("WorkflowRunState must be an object.");
|
|
33
|
+
}
|
|
34
|
+
const state = value;
|
|
35
|
+
if (state.schemaVersion !== undefined && state.schemaVersion > WORKFLOW_RUN_STATE_SCHEMA_VERSION) {
|
|
36
|
+
throw new ValidationError(`Unsupported WorkflowRunState schemaVersion ${state.schemaVersion}.`);
|
|
37
|
+
}
|
|
38
|
+
if (typeof state.runId !== "string" ||
|
|
39
|
+
typeof state.userId !== "string" ||
|
|
40
|
+
typeof state.sessionId !== "string" ||
|
|
41
|
+
typeof state.status !== "string" ||
|
|
42
|
+
!state.outputs ||
|
|
43
|
+
typeof state.outputs !== "object" ||
|
|
44
|
+
Array.isArray(state.outputs) ||
|
|
45
|
+
!Array.isArray(state.steps) ||
|
|
46
|
+
typeof state.currentStepIndex !== "number" ||
|
|
47
|
+
typeof state.createdAt !== "number" ||
|
|
48
|
+
typeof state.updatedAt !== "number") {
|
|
49
|
+
throw new ValidationError("WorkflowRunState is missing required fields.");
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
schemaVersion: WORKFLOW_RUN_STATE_SCHEMA_VERSION,
|
|
53
|
+
workflowId: state.workflowId,
|
|
54
|
+
runId: state.runId,
|
|
55
|
+
userId: state.userId,
|
|
56
|
+
sessionId: state.sessionId,
|
|
57
|
+
status: state.status,
|
|
58
|
+
input: state.input === undefined ? undefined : cloneJson(state.input),
|
|
59
|
+
outputs: cloneJson(state.outputs),
|
|
60
|
+
steps: cloneJson(state.steps),
|
|
61
|
+
currentStepIndex: state.currentStepIndex,
|
|
62
|
+
session: state.session ? cloneJson(state.session) : undefined,
|
|
63
|
+
metadata: state.metadata ? cloneJson(state.metadata) : undefined,
|
|
64
|
+
createdAt: state.createdAt,
|
|
65
|
+
updatedAt: state.updatedAt
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
export const migrateWorkflowRunState = (value, targetVersion = WORKFLOW_RUN_STATE_SCHEMA_VERSION) => {
|
|
69
|
+
if (targetVersion !== WORKFLOW_RUN_STATE_SCHEMA_VERSION) {
|
|
70
|
+
throw new ValidationError(`Unsupported WorkflowRunState migration target ${targetVersion}.`);
|
|
71
|
+
}
|
|
72
|
+
return normalizeWorkflowRunState(value);
|
|
73
|
+
};
|
|
74
|
+
const normalizeState = (state) => normalizeWorkflowRunState(state);
|
|
75
|
+
const workflowPersistenceKey = (workflow) => workflow.persistence?.workflowKey ?? workflow.id ?? "default";
|
|
76
|
+
const workflowMetadataKey = (workflow) => workflow.persistence?.metadataKey ?? "workflowRuns";
|
|
77
|
+
const toPersistedWorkflowState = (state) => {
|
|
78
|
+
const { session: _session, ...persisted } = state;
|
|
79
|
+
return cloneJson(persisted);
|
|
80
|
+
};
|
|
81
|
+
const getWorkflowRunsMetadata = (metadata, key) => {
|
|
82
|
+
const value = metadata?.[key];
|
|
83
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
84
|
+
? cloneJson(value)
|
|
85
|
+
: {};
|
|
86
|
+
};
|
|
87
|
+
export const loadWorkflowState = async (workflow, input) => {
|
|
88
|
+
const definition = validateWorkflow(workflow);
|
|
89
|
+
const persistence = definition.persistence;
|
|
90
|
+
if (!persistence) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
if (persistence.workflowStateService) {
|
|
94
|
+
const record = await persistence.workflowStateService.loadWorkflowState({
|
|
95
|
+
appName: persistence.appName,
|
|
96
|
+
userId: input.userId,
|
|
97
|
+
sessionId: input.sessionId,
|
|
98
|
+
workflowKey: workflowPersistenceKey(definition)
|
|
99
|
+
});
|
|
100
|
+
if (!record) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
const session = await persistence.sessionService.loadSession({
|
|
104
|
+
appName: persistence.appName,
|
|
105
|
+
userId: input.userId,
|
|
106
|
+
sessionId: input.sessionId
|
|
107
|
+
});
|
|
108
|
+
return {
|
|
109
|
+
...normalizeWorkflowRunState(record.state),
|
|
110
|
+
session: session ? cloneJson(session) : undefined
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const session = await persistence.sessionService.loadSession({
|
|
114
|
+
appName: persistence.appName,
|
|
115
|
+
userId: input.userId,
|
|
116
|
+
sessionId: input.sessionId
|
|
117
|
+
});
|
|
118
|
+
const runs = getWorkflowRunsMetadata(session?.metadata, workflowMetadataKey(definition));
|
|
119
|
+
const persisted = runs[workflowPersistenceKey(definition)];
|
|
120
|
+
if (!persisted || typeof persisted !== "object" || Array.isArray(persisted)) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
...normalizeWorkflowRunState(persisted),
|
|
125
|
+
session: session ? cloneJson(session) : undefined
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
export const saveWorkflowState = async (workflow, state) => {
|
|
129
|
+
const definition = validateWorkflow(workflow);
|
|
130
|
+
const persistence = definition.persistence;
|
|
131
|
+
if (!persistence) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
const lookup = {
|
|
135
|
+
appName: persistence.appName,
|
|
136
|
+
userId: state.userId,
|
|
137
|
+
sessionId: state.sessionId
|
|
138
|
+
};
|
|
139
|
+
const existing = await persistence.sessionService.loadSession(lookup);
|
|
140
|
+
const session = existing ??
|
|
141
|
+
await persistence.sessionService.createSession({
|
|
142
|
+
...lookup,
|
|
143
|
+
metadata: {}
|
|
144
|
+
});
|
|
145
|
+
if (persistence.workflowStateService) {
|
|
146
|
+
const workflowKey = workflowPersistenceKey(definition);
|
|
147
|
+
await persistence.workflowStateService.saveWorkflowState({
|
|
148
|
+
...lookup,
|
|
149
|
+
workflowKey,
|
|
150
|
+
state: toPersistedWorkflowState(normalizeWorkflowRunState(state))
|
|
151
|
+
});
|
|
152
|
+
const next = {
|
|
153
|
+
...session,
|
|
154
|
+
updatedAt: Date.now(),
|
|
155
|
+
metadata: {
|
|
156
|
+
...(session.metadata ?? {}),
|
|
157
|
+
workflowStateRefs: {
|
|
158
|
+
...(session.metadata?.workflowStateRefs &&
|
|
159
|
+
typeof session.metadata.workflowStateRefs === "object" &&
|
|
160
|
+
!Array.isArray(session.metadata.workflowStateRefs)
|
|
161
|
+
? session.metadata.workflowStateRefs
|
|
162
|
+
: {}),
|
|
163
|
+
[workflowKey]: {
|
|
164
|
+
runId: state.runId,
|
|
165
|
+
status: state.status,
|
|
166
|
+
updatedAt: state.updatedAt
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
await persistence.sessionService.saveSession(next);
|
|
172
|
+
return cloneJson(next);
|
|
173
|
+
}
|
|
174
|
+
const metadataKey = workflowMetadataKey(definition);
|
|
175
|
+
const workflowRuns = getWorkflowRunsMetadata(session.metadata, metadataKey);
|
|
176
|
+
workflowRuns[workflowPersistenceKey(definition)] = toPersistedWorkflowState(normalizeWorkflowRunState(state));
|
|
177
|
+
const next = {
|
|
178
|
+
...session,
|
|
179
|
+
updatedAt: Date.now(),
|
|
180
|
+
metadata: {
|
|
181
|
+
...(session.metadata ?? {}),
|
|
182
|
+
[metadataKey]: workflowRuns
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
await persistence.sessionService.saveSession(next);
|
|
186
|
+
return cloneJson(next);
|
|
187
|
+
};
|
|
188
|
+
const resolveInitialState = async (workflow, input) => {
|
|
189
|
+
if (input.state) {
|
|
190
|
+
return normalizeState(input.state);
|
|
191
|
+
}
|
|
192
|
+
if (input.resumeFromPersistedState && input.sessionId) {
|
|
193
|
+
const persisted = await loadWorkflowState(workflow, {
|
|
194
|
+
userId: input.userId,
|
|
195
|
+
sessionId: input.sessionId
|
|
196
|
+
});
|
|
197
|
+
if (persisted) {
|
|
198
|
+
return normalizeState(persisted);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return createBaseState(workflow, input);
|
|
202
|
+
};
|
|
203
|
+
const finalizeWorkflowOutput = async (workflow, state) => {
|
|
204
|
+
const session = await saveWorkflowState(workflow, state);
|
|
205
|
+
if (session) {
|
|
206
|
+
state.session = session;
|
|
207
|
+
}
|
|
208
|
+
return toOutput(state);
|
|
209
|
+
};
|
|
210
|
+
const setStepResult = (state, index, result) => {
|
|
211
|
+
state.steps = [...state.steps.slice(0, index), cloneJson(result)];
|
|
212
|
+
};
|
|
213
|
+
const isParallelStep = (step) => step.kind === "parallel";
|
|
214
|
+
const isLoopStep = (step) => step.kind === "loop";
|
|
215
|
+
const resolveTaskPrompt = async (workflow, state, step, input) => {
|
|
216
|
+
if (typeof step.prompt === "string") {
|
|
217
|
+
return step.prompt;
|
|
218
|
+
}
|
|
219
|
+
return step.prompt({
|
|
220
|
+
workflowId: workflow.id,
|
|
221
|
+
runId: state.runId,
|
|
222
|
+
userId: state.userId,
|
|
223
|
+
sessionId: state.sessionId,
|
|
224
|
+
input: state.input,
|
|
225
|
+
outputs: cloneJson(state.outputs),
|
|
226
|
+
steps: cloneJson(state.steps),
|
|
227
|
+
step,
|
|
228
|
+
metadata: input.metadata
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
const createStepResult = (step, status, startedAt, output, error) => ({
|
|
232
|
+
id: step.id,
|
|
233
|
+
kind: "task",
|
|
234
|
+
status,
|
|
235
|
+
outputKey: step.outputKey,
|
|
236
|
+
outputText: output?.output.steps.at(-1)?.response?.text ?? output?.output.outputText,
|
|
237
|
+
runId: output?.output.state.runId,
|
|
238
|
+
agentStatus: output?.output.status,
|
|
239
|
+
approvals: output?.output.state.pendingApprovals.length ? cloneJson(output.output.state.pendingApprovals) : undefined,
|
|
240
|
+
startedAt,
|
|
241
|
+
finishedAt: Date.now(),
|
|
242
|
+
error: error
|
|
243
|
+
? {
|
|
244
|
+
message: error instanceof Error ? error.message : String(error)
|
|
245
|
+
}
|
|
246
|
+
: output?.output.error
|
|
247
|
+
});
|
|
248
|
+
const createParallelResult = (step, status, startedAt, children, error) => ({
|
|
249
|
+
id: step.id,
|
|
250
|
+
kind: "parallel",
|
|
251
|
+
status,
|
|
252
|
+
children: cloneJson(children),
|
|
253
|
+
startedAt,
|
|
254
|
+
finishedAt: Date.now(),
|
|
255
|
+
error,
|
|
256
|
+
metadata: step.metadata ? cloneJson(step.metadata) : undefined
|
|
257
|
+
});
|
|
258
|
+
const createLoopResult = (step, status, startedAt, children, error) => ({
|
|
259
|
+
id: step.id,
|
|
260
|
+
kind: "loop",
|
|
261
|
+
status,
|
|
262
|
+
children: cloneJson(children),
|
|
263
|
+
startedAt,
|
|
264
|
+
finishedAt: Date.now(),
|
|
265
|
+
error,
|
|
266
|
+
metadata: step.metadata ? cloneJson(step.metadata) : undefined
|
|
267
|
+
});
|
|
268
|
+
const stepStatusFromAgentStatus = (status) => {
|
|
269
|
+
if (status === "completed") {
|
|
270
|
+
return "completed";
|
|
271
|
+
}
|
|
272
|
+
if (status === "waiting_approval") {
|
|
273
|
+
return "waiting_approval";
|
|
274
|
+
}
|
|
275
|
+
return "failed";
|
|
276
|
+
};
|
|
277
|
+
const validateWorkflow = (definition) => {
|
|
278
|
+
if (!definition.steps.length) {
|
|
279
|
+
throw new Error("Workflow must include at least one step.");
|
|
280
|
+
}
|
|
281
|
+
const ids = new Set();
|
|
282
|
+
for (const step of definition.steps) {
|
|
283
|
+
if (ids.has(step.id)) {
|
|
284
|
+
throw new Error(`Workflow step id "${step.id}" is duplicated.`);
|
|
285
|
+
}
|
|
286
|
+
ids.add(step.id);
|
|
287
|
+
if (isParallelStep(step)) {
|
|
288
|
+
if (!step.steps.length) {
|
|
289
|
+
throw new Error(`Workflow parallel step "${step.id}" must include at least one child step.`);
|
|
290
|
+
}
|
|
291
|
+
for (const child of step.steps) {
|
|
292
|
+
if (isParallelStep(child)) {
|
|
293
|
+
throw new Error("Nested parallel workflow steps are not supported yet.");
|
|
294
|
+
}
|
|
295
|
+
if (ids.has(child.id)) {
|
|
296
|
+
throw new Error(`Workflow step id "${child.id}" is duplicated.`);
|
|
297
|
+
}
|
|
298
|
+
ids.add(child.id);
|
|
299
|
+
}
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (isLoopStep(step)) {
|
|
303
|
+
if (!Number.isInteger(step.maxIterations) || step.maxIterations <= 0) {
|
|
304
|
+
throw new Error(`Workflow loop step "${step.id}" must include a positive integer maxIterations.`);
|
|
305
|
+
}
|
|
306
|
+
if (step.step.kind === "parallel") {
|
|
307
|
+
throw new Error("Workflow loop steps can only wrap task steps in this release.");
|
|
308
|
+
}
|
|
309
|
+
if (step.step.kind === "loop") {
|
|
310
|
+
throw new Error("Nested loop workflow steps are not supported yet.");
|
|
311
|
+
}
|
|
312
|
+
if (ids.has(step.step.id)) {
|
|
313
|
+
throw new Error(`Workflow step id "${step.step.id}" is duplicated.`);
|
|
314
|
+
}
|
|
315
|
+
ids.add(step.step.id);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
...definition,
|
|
320
|
+
metadata: definition.metadata ? cloneJson(definition.metadata) : undefined,
|
|
321
|
+
steps: definition.steps.map((step) => isParallelStep(step)
|
|
322
|
+
? {
|
|
323
|
+
...step,
|
|
324
|
+
metadata: step.metadata ? cloneJson(step.metadata) : undefined,
|
|
325
|
+
steps: step.steps.map((child) => ({
|
|
326
|
+
...child,
|
|
327
|
+
metadata: child.metadata ? cloneJson(child.metadata) : undefined
|
|
328
|
+
}))
|
|
329
|
+
}
|
|
330
|
+
: isLoopStep(step)
|
|
331
|
+
? {
|
|
332
|
+
...step,
|
|
333
|
+
metadata: step.metadata ? cloneJson(step.metadata) : undefined,
|
|
334
|
+
step: {
|
|
335
|
+
...step.step,
|
|
336
|
+
metadata: step.step.metadata ? cloneJson(step.step.metadata) : undefined
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
: {
|
|
340
|
+
...step,
|
|
341
|
+
metadata: step.metadata ? cloneJson(step.metadata) : undefined
|
|
342
|
+
})
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
export const createWorkflow = (definition) => validateWorkflow(definition);
|
|
346
|
+
const runTaskStep = async (workflow, state, step, input, options) => {
|
|
347
|
+
const runnerInput = options.isApprovalResume
|
|
348
|
+
? {
|
|
349
|
+
userId: state.userId,
|
|
350
|
+
sessionId: state.sessionId,
|
|
351
|
+
approvals: input.approvals,
|
|
352
|
+
sessionMetadata: input.sessionMetadata,
|
|
353
|
+
eventMetadata: input.eventMetadata
|
|
354
|
+
}
|
|
355
|
+
: {
|
|
356
|
+
userId: state.userId,
|
|
357
|
+
sessionId: state.sessionId,
|
|
358
|
+
prompt: await resolveTaskPrompt(workflow, state, step, input),
|
|
359
|
+
system: step.system,
|
|
360
|
+
sessionMetadata: input.sessionMetadata,
|
|
361
|
+
eventMetadata: {
|
|
362
|
+
...(input.eventMetadata ?? {}),
|
|
363
|
+
workflowRunId: state.runId,
|
|
364
|
+
workflowStepId: step.id
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
const output = await step.runner.run(runnerInput);
|
|
368
|
+
const status = stepStatusFromAgentStatus(output.output.status);
|
|
369
|
+
return {
|
|
370
|
+
result: createStepResult(step, status, options.startedAt, output),
|
|
371
|
+
output
|
|
372
|
+
};
|
|
373
|
+
};
|
|
374
|
+
const collectStepOutputs = (outputs, step, result, output) => {
|
|
375
|
+
if (result.status === "completed" && step.outputKey) {
|
|
376
|
+
outputs[step.outputKey] = result.outputText ?? output?.output.outputText ?? "";
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
const runParallelStep = async (workflow, state, step, input, existingResult, startedAt) => {
|
|
380
|
+
const previousChildren = existingResult?.children ?? [];
|
|
381
|
+
const hasWaitingChild = previousChildren.some((child) => child.status === "waiting_approval");
|
|
382
|
+
const runnableSteps = step.steps.map((child, childIndex) => {
|
|
383
|
+
const previous = previousChildren.find((result) => result.id === child.id);
|
|
384
|
+
if (previous?.status === "completed") {
|
|
385
|
+
return Promise.resolve({ child, childIndex, result: previous, output: undefined, skipped: true });
|
|
386
|
+
}
|
|
387
|
+
if (hasWaitingChild && previous?.status !== "waiting_approval") {
|
|
388
|
+
return Promise.resolve({
|
|
389
|
+
child,
|
|
390
|
+
childIndex,
|
|
391
|
+
result: previous ?? {
|
|
392
|
+
id: child.id,
|
|
393
|
+
kind: "task",
|
|
394
|
+
status: "pending",
|
|
395
|
+
outputKey: child.outputKey,
|
|
396
|
+
metadata: child.metadata ? cloneJson(child.metadata) : undefined
|
|
397
|
+
},
|
|
398
|
+
output: undefined,
|
|
399
|
+
skipped: true
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
return runTaskStep(workflow, state, child, input, {
|
|
403
|
+
startedAt: Date.now(),
|
|
404
|
+
isApprovalResume: previous?.status === "waiting_approval" && Boolean(input.approvals?.length)
|
|
405
|
+
})
|
|
406
|
+
.then(({ result, output }) => ({ child, childIndex, result, output, skipped: false }))
|
|
407
|
+
.catch((error) => ({
|
|
408
|
+
child,
|
|
409
|
+
childIndex,
|
|
410
|
+
result: createStepResult(child, "failed", Date.now(), undefined, error),
|
|
411
|
+
output: undefined,
|
|
412
|
+
skipped: false
|
|
413
|
+
}));
|
|
414
|
+
});
|
|
415
|
+
const settled = await Promise.allSettled(runnableSteps);
|
|
416
|
+
const childRuns = settled.map((settledResult, index) => {
|
|
417
|
+
if (settledResult.status === "fulfilled") {
|
|
418
|
+
return settledResult.value;
|
|
419
|
+
}
|
|
420
|
+
const child = step.steps[index];
|
|
421
|
+
return {
|
|
422
|
+
child,
|
|
423
|
+
childIndex: index,
|
|
424
|
+
result: createStepResult(child, "failed", Date.now(), undefined, settledResult.reason),
|
|
425
|
+
output: undefined,
|
|
426
|
+
skipped: false
|
|
427
|
+
};
|
|
428
|
+
});
|
|
429
|
+
const children = childRuns
|
|
430
|
+
.sort((left, right) => left.childIndex - right.childIndex)
|
|
431
|
+
.map((childRun) => childRun.result);
|
|
432
|
+
for (const childRun of childRuns) {
|
|
433
|
+
if (!childRun.skipped) {
|
|
434
|
+
collectStepOutputs(state.outputs, childRun.child, childRun.result, childRun.output);
|
|
435
|
+
if (childRun.output?.session) {
|
|
436
|
+
state.session = childRun.output.session;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
const waiting = children.some((child) => child.status === "waiting_approval");
|
|
441
|
+
const failed = children.some((child) => child.status === "failed");
|
|
442
|
+
const status = waiting ? "waiting_approval" : failed ? "failed" : "completed";
|
|
443
|
+
const firstError = children.find((child) => child.error)?.error;
|
|
444
|
+
return {
|
|
445
|
+
result: createParallelResult(step, step.failFast && failed ? "failed" : status, startedAt, children, firstError),
|
|
446
|
+
session: state.session
|
|
447
|
+
};
|
|
448
|
+
};
|
|
449
|
+
const shouldStopLoop = async (workflow, state, step, input, iteration, result) => {
|
|
450
|
+
if (!step.until) {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
return step.until({
|
|
454
|
+
workflowId: workflow.id,
|
|
455
|
+
runId: state.runId,
|
|
456
|
+
userId: state.userId,
|
|
457
|
+
sessionId: state.sessionId,
|
|
458
|
+
input: state.input,
|
|
459
|
+
outputs: cloneJson(state.outputs),
|
|
460
|
+
steps: cloneJson(state.steps),
|
|
461
|
+
step,
|
|
462
|
+
iteration,
|
|
463
|
+
result: cloneJson(result),
|
|
464
|
+
metadata: input.metadata
|
|
465
|
+
});
|
|
466
|
+
};
|
|
467
|
+
const runLoopStep = async (workflow, state, step, input, existingResult, startedAt) => {
|
|
468
|
+
const previousChildren = existingResult?.children ?? [];
|
|
469
|
+
const waitingIndex = previousChildren.findIndex((child) => child.status === "waiting_approval");
|
|
470
|
+
const children = waitingIndex >= 0
|
|
471
|
+
? previousChildren.slice(0, waitingIndex)
|
|
472
|
+
: previousChildren.filter((child) => child.status === "completed");
|
|
473
|
+
const firstIteration = waitingIndex >= 0 ? waitingIndex : children.length;
|
|
474
|
+
for (let iteration = firstIteration; iteration < step.maxIterations; iteration += 1) {
|
|
475
|
+
const childStartedAt = Date.now();
|
|
476
|
+
const isApprovalResume = waitingIndex === iteration && Boolean(input.approvals?.length);
|
|
477
|
+
const { result, output } = await runTaskStep(workflow, state, step.step, input, {
|
|
478
|
+
startedAt: childStartedAt,
|
|
479
|
+
isApprovalResume
|
|
480
|
+
}).catch((error) => ({
|
|
481
|
+
result: createStepResult(step.step, "failed", childStartedAt, undefined, error),
|
|
482
|
+
output: undefined
|
|
483
|
+
}));
|
|
484
|
+
children[iteration] = result;
|
|
485
|
+
if (output?.session) {
|
|
486
|
+
state.session = output.session;
|
|
487
|
+
}
|
|
488
|
+
if (result.status === "completed") {
|
|
489
|
+
collectStepOutputs(state.outputs, step.step, result, output);
|
|
490
|
+
if (await shouldStopLoop(workflow, state, step, input, iteration, result)) {
|
|
491
|
+
return {
|
|
492
|
+
result: createLoopResult(step, "completed", startedAt, children),
|
|
493
|
+
session: state.session
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
result: createLoopResult(step, result.status === "waiting_approval" ? "waiting_approval" : "failed", startedAt, children, result.error),
|
|
500
|
+
session: state.session
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
result: createLoopResult(step, "completed", startedAt, children),
|
|
505
|
+
session: state.session
|
|
506
|
+
};
|
|
507
|
+
};
|
|
508
|
+
export const runWorkflow = async (workflow, input) => {
|
|
509
|
+
const definition = validateWorkflow(workflow);
|
|
510
|
+
const state = await resolveInitialState(definition, input);
|
|
511
|
+
state.status = "running";
|
|
512
|
+
state.updatedAt = Date.now();
|
|
513
|
+
const waitingIndex = state.steps.findIndex((step) => step.status === "waiting_approval");
|
|
514
|
+
if (waitingIndex >= 0 && !input.approvals?.length) {
|
|
515
|
+
state.status = "waiting_approval";
|
|
516
|
+
state.currentStepIndex = waitingIndex;
|
|
517
|
+
state.updatedAt = Date.now();
|
|
518
|
+
return finalizeWorkflowOutput(definition, state);
|
|
519
|
+
}
|
|
520
|
+
const startIndex = waitingIndex >= 0 ? waitingIndex : state.currentStepIndex;
|
|
521
|
+
for (let index = startIndex; index < definition.steps.length; index += 1) {
|
|
522
|
+
const step = definition.steps[index];
|
|
523
|
+
const startedAt = Date.now();
|
|
524
|
+
state.currentStepIndex = index;
|
|
525
|
+
const existingResult = state.steps[index];
|
|
526
|
+
setStepResult(state, index, isParallelStep(step)
|
|
527
|
+
? {
|
|
528
|
+
id: step.id,
|
|
529
|
+
kind: "parallel",
|
|
530
|
+
status: "running",
|
|
531
|
+
children: existingResult?.children,
|
|
532
|
+
metadata: step.metadata ? cloneJson(step.metadata) : undefined,
|
|
533
|
+
startedAt
|
|
534
|
+
}
|
|
535
|
+
: isLoopStep(step)
|
|
536
|
+
? {
|
|
537
|
+
id: step.id,
|
|
538
|
+
kind: "loop",
|
|
539
|
+
status: "running",
|
|
540
|
+
children: existingResult?.children,
|
|
541
|
+
metadata: step.metadata ? cloneJson(step.metadata) : undefined,
|
|
542
|
+
startedAt
|
|
543
|
+
}
|
|
544
|
+
: {
|
|
545
|
+
id: step.id,
|
|
546
|
+
kind: "task",
|
|
547
|
+
status: "running",
|
|
548
|
+
outputKey: step.outputKey,
|
|
549
|
+
metadata: step.metadata ? cloneJson(step.metadata) : undefined,
|
|
550
|
+
startedAt
|
|
551
|
+
});
|
|
552
|
+
try {
|
|
553
|
+
const stepRun = isParallelStep(step)
|
|
554
|
+
? await runParallelStep(definition, state, step, input, existingResult, startedAt)
|
|
555
|
+
: isLoopStep(step)
|
|
556
|
+
? await runLoopStep(definition, state, step, input, existingResult, startedAt)
|
|
557
|
+
: await runTaskStep(definition, state, step, input, {
|
|
558
|
+
startedAt,
|
|
559
|
+
isApprovalResume: index === waitingIndex && Boolean(input.approvals?.length)
|
|
560
|
+
});
|
|
561
|
+
const { result } = stepRun;
|
|
562
|
+
const output = "output" in stepRun ? stepRun.output : undefined;
|
|
563
|
+
const session = "session" in stepRun ? stepRun.session : undefined;
|
|
564
|
+
if (output?.session) {
|
|
565
|
+
state.session = output.session;
|
|
566
|
+
}
|
|
567
|
+
else if (session) {
|
|
568
|
+
state.session = session;
|
|
569
|
+
}
|
|
570
|
+
const status = result.status;
|
|
571
|
+
setStepResult(state, index, result);
|
|
572
|
+
if (status === "completed") {
|
|
573
|
+
if (!isParallelStep(step) && !isLoopStep(step)) {
|
|
574
|
+
collectStepOutputs(state.outputs, step, result, output);
|
|
575
|
+
}
|
|
576
|
+
state.currentStepIndex = index + 1;
|
|
577
|
+
state.updatedAt = Date.now();
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
state.status = status === "waiting_approval" ? "waiting_approval" : "failed";
|
|
581
|
+
state.currentStepIndex = index;
|
|
582
|
+
state.updatedAt = Date.now();
|
|
583
|
+
return finalizeWorkflowOutput(definition, state);
|
|
584
|
+
}
|
|
585
|
+
catch (error) {
|
|
586
|
+
setStepResult(state, index, isParallelStep(step)
|
|
587
|
+
? createParallelResult(step, "failed", startedAt, existingResult?.children ?? [], {
|
|
588
|
+
message: error instanceof Error ? error.message : String(error)
|
|
589
|
+
})
|
|
590
|
+
: isLoopStep(step)
|
|
591
|
+
? createLoopResult(step, "failed", startedAt, existingResult?.children ?? [], {
|
|
592
|
+
message: error instanceof Error ? error.message : String(error)
|
|
593
|
+
})
|
|
594
|
+
: createStepResult(step, "failed", startedAt, undefined, error));
|
|
595
|
+
state.status = "failed";
|
|
596
|
+
state.currentStepIndex = index;
|
|
597
|
+
state.updatedAt = Date.now();
|
|
598
|
+
return finalizeWorkflowOutput(definition, state);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
state.status = "completed";
|
|
602
|
+
state.currentStepIndex = definition.steps.length;
|
|
603
|
+
state.updatedAt = Date.now();
|
|
604
|
+
return finalizeWorkflowOutput(definition, state);
|
|
605
|
+
};
|
|
606
|
+
export const replayWorkflowRun = (state) => {
|
|
607
|
+
const timeline = [
|
|
608
|
+
{
|
|
609
|
+
type: "workflow-start",
|
|
610
|
+
runId: state.runId,
|
|
611
|
+
workflowId: state.workflowId,
|
|
612
|
+
createdAt: state.createdAt
|
|
613
|
+
}
|
|
614
|
+
];
|
|
615
|
+
for (const step of state.steps) {
|
|
616
|
+
if (step.kind === "parallel") {
|
|
617
|
+
timeline.push({
|
|
618
|
+
type: "parallel-start",
|
|
619
|
+
stepId: step.id,
|
|
620
|
+
startedAt: step.startedAt
|
|
621
|
+
});
|
|
622
|
+
for (const child of step.children ?? []) {
|
|
623
|
+
if (child.approvals?.length) {
|
|
624
|
+
timeline.push({
|
|
625
|
+
type: "approval-required",
|
|
626
|
+
stepId: child.id,
|
|
627
|
+
approvals: cloneJson(child.approvals)
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
timeline.push({
|
|
631
|
+
type: "parallel-step-finish",
|
|
632
|
+
stepId: child.id,
|
|
633
|
+
parentStepId: step.id,
|
|
634
|
+
status: child.status,
|
|
635
|
+
outputText: child.outputText,
|
|
636
|
+
error: child.error,
|
|
637
|
+
finishedAt: child.finishedAt
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
timeline.push({
|
|
641
|
+
type: "parallel-finish",
|
|
642
|
+
stepId: step.id,
|
|
643
|
+
status: step.status,
|
|
644
|
+
finishedAt: step.finishedAt
|
|
645
|
+
});
|
|
646
|
+
if (step.approvals?.length) {
|
|
647
|
+
timeline.push({
|
|
648
|
+
type: "approval-required",
|
|
649
|
+
stepId: step.id,
|
|
650
|
+
approvals: cloneJson(step.approvals)
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
if (step.kind === "loop") {
|
|
656
|
+
timeline.push({
|
|
657
|
+
type: "loop-start",
|
|
658
|
+
stepId: step.id,
|
|
659
|
+
startedAt: step.startedAt
|
|
660
|
+
});
|
|
661
|
+
for (const [index, child] of (step.children ?? []).entries()) {
|
|
662
|
+
if (child.approvals?.length) {
|
|
663
|
+
timeline.push({
|
|
664
|
+
type: "approval-required",
|
|
665
|
+
stepId: child.id,
|
|
666
|
+
approvals: cloneJson(child.approvals)
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
timeline.push({
|
|
670
|
+
type: "loop-iteration-finish",
|
|
671
|
+
stepId: child.id,
|
|
672
|
+
parentStepId: step.id,
|
|
673
|
+
iteration: index,
|
|
674
|
+
status: child.status,
|
|
675
|
+
outputText: child.outputText,
|
|
676
|
+
error: child.error,
|
|
677
|
+
finishedAt: child.finishedAt
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
timeline.push({
|
|
681
|
+
type: "loop-finish",
|
|
682
|
+
stepId: step.id,
|
|
683
|
+
status: step.status,
|
|
684
|
+
finishedAt: step.finishedAt
|
|
685
|
+
});
|
|
686
|
+
if (step.approvals?.length) {
|
|
687
|
+
timeline.push({
|
|
688
|
+
type: "approval-required",
|
|
689
|
+
stepId: step.id,
|
|
690
|
+
approvals: cloneJson(step.approvals)
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
timeline.push({
|
|
696
|
+
type: "step-start",
|
|
697
|
+
stepId: step.id,
|
|
698
|
+
startedAt: step.startedAt
|
|
699
|
+
});
|
|
700
|
+
if (step.approvals?.length) {
|
|
701
|
+
timeline.push({
|
|
702
|
+
type: "approval-required",
|
|
703
|
+
stepId: step.id,
|
|
704
|
+
approvals: cloneJson(step.approvals)
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
timeline.push({
|
|
708
|
+
type: "step-finish",
|
|
709
|
+
stepId: step.id,
|
|
710
|
+
status: step.status,
|
|
711
|
+
outputText: step.outputText,
|
|
712
|
+
error: step.error,
|
|
713
|
+
finishedAt: step.finishedAt
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
timeline.push({
|
|
717
|
+
type: "workflow-finish",
|
|
718
|
+
status: state.status,
|
|
719
|
+
updatedAt: state.updatedAt
|
|
720
|
+
});
|
|
721
|
+
return {
|
|
722
|
+
status: state.status,
|
|
723
|
+
timeline,
|
|
724
|
+
outputs: cloneJson(state.outputs)
|
|
725
|
+
};
|
|
726
|
+
};
|
|
727
|
+
//# sourceMappingURL=workflow.js.map
|