mirror of
https://github.com/run-llama/LlamaIndexTS.git
synced 2026-07-01 22:14:03 -04:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 502e48bbc5 | |||
| 8994904b06 | |||
| 8d49887131 | |||
| b3309a10dd | |||
| 48888569dc | |||
| c816c16a97 | |||
| 94dbe381b0 | |||
| bf33bf04c3 | |||
| 8707a3e688 | |||
| be66ccc6c8 |
@@ -0,0 +1,9 @@
|
||||
---
|
||||
"@llamaindex/workflow": minor
|
||||
---
|
||||
|
||||
refactor!: migrate to llamaflow
|
||||
|
||||
- remove `outputs` in workflow. You shuld use TypeScript and define returns type to validate the workflow correctly.
|
||||
- remove `timeout` and `verbose` in workflow. Workflow now is a very lightly engine, so you should do this by youself. For example, `abortSignal.timeout`, `console.log`...
|
||||
- `workflow.run` now retunrs `ReadableStream | Promise<WorkflowEvent<Result>>`, you shouldn't use steram and promise in both time.
|
||||
@@ -10,8 +10,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^5.4.16",
|
||||
"vite-plugin-wasm": "^3.3.0"
|
||||
"vite": "^6.3.3",
|
||||
"vite-plugin-wasm": "^3.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@llamaindex/cloud": "workspace:*"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"@size-limit/preset-big-lib": "^11.1.6",
|
||||
"size-limit": "^11.1.6",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^5.4.16"
|
||||
"vite": "^6.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"llamaindex": "workspace:*"
|
||||
|
||||
@@ -11,7 +11,6 @@ const workflow = new Workflow<ContextData, string, string>();
|
||||
workflow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [StopEvent<string>],
|
||||
},
|
||||
async (context, startEvent) => {
|
||||
const input = startEvent.data;
|
||||
|
||||
@@ -11,24 +11,6 @@
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"node": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs",
|
||||
"default": "./dist/index.cjs"
|
||||
},
|
||||
"workerd": {
|
||||
"types": "./dist/index.workerd.d.ts",
|
||||
"default": "./dist/index.workerd.js"
|
||||
},
|
||||
"edge-light": {
|
||||
"types": "./dist/index.edge-light.d.ts",
|
||||
"default": "./dist/index.edge-light.js"
|
||||
},
|
||||
"browser": {
|
||||
"types": "./dist/index.browser.d.ts",
|
||||
"default": "./dist/index.browser.js"
|
||||
},
|
||||
"import": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
@@ -55,14 +37,17 @@
|
||||
"test": "vitest run"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@llamaindex/env": "workspace:*",
|
||||
"@llamaindex/core": "workspace:*",
|
||||
"@llamaindex/env": "workspace:*",
|
||||
"@types/node": "^22.9.0",
|
||||
"vitest": "^2.1.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@llamaindex/env": "workspace:*",
|
||||
"@llamaindex/core": "workspace:*",
|
||||
"@llamaindex/env": "workspace:*",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@llama-flow/llamaindex": "^0.0.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import {
|
||||
StartEvent,
|
||||
type StepContext,
|
||||
StopEvent,
|
||||
Workflow,
|
||||
WorkflowEvent,
|
||||
} from "@llama-flow/llamaindex";
|
||||
import type { ChatMessage } from "@llamaindex/core/llms";
|
||||
import { ChatMemoryBuffer } from "@llamaindex/core/memory";
|
||||
import { PromptTemplate } from "@llamaindex/core/prompts";
|
||||
import { FunctionTool } from "@llamaindex/core/tools";
|
||||
import { stringifyJSONToMessageContent } from "@llamaindex/core/utils";
|
||||
import { z } from "zod";
|
||||
import { Workflow } from "../workflow";
|
||||
import type { HandlerContext, WorkflowContext } from "../workflow-context";
|
||||
import { StartEvent, StopEvent, WorkflowEvent } from "../workflow-event";
|
||||
import type { AgentWorkflowContext, BaseWorkflowAgent } from "./base";
|
||||
import {
|
||||
AgentInput,
|
||||
@@ -115,10 +119,7 @@ export class AgentWorkflow {
|
||||
private rootAgentName: string;
|
||||
|
||||
constructor({ agents, rootAgent, verbose, timeout }: AgentWorkflowParams) {
|
||||
this.workflow = new Workflow({
|
||||
verbose: verbose ?? false,
|
||||
timeout: timeout ?? 60,
|
||||
});
|
||||
this.workflow = new Workflow();
|
||||
this.verbose = verbose ?? false;
|
||||
|
||||
// Handle AgentWorkflow cases for agents
|
||||
@@ -254,7 +255,7 @@ export class AgentWorkflow {
|
||||
}
|
||||
|
||||
private handleInputStep = async (
|
||||
ctx: HandlerContext<AgentWorkflowContext>,
|
||||
ctx: StepContext<AgentWorkflowContext>,
|
||||
event: StartEvent<AgentInputData>,
|
||||
): Promise<AgentInput> => {
|
||||
const { userInput, chatHistory } = event.data;
|
||||
@@ -290,7 +291,7 @@ export class AgentWorkflow {
|
||||
};
|
||||
|
||||
private setupAgent = async (
|
||||
ctx: HandlerContext<AgentWorkflowContext>,
|
||||
ctx: StepContext<AgentWorkflowContext>,
|
||||
event: AgentInput,
|
||||
): Promise<AgentSetup> => {
|
||||
const currentAgentName = event.data.currentAgentName;
|
||||
@@ -314,9 +315,9 @@ export class AgentWorkflow {
|
||||
};
|
||||
|
||||
private runAgentStep = async (
|
||||
ctx: HandlerContext<AgentWorkflowContext>,
|
||||
ctx: StepContext<AgentWorkflowContext>,
|
||||
event: AgentSetup,
|
||||
): Promise<AgentStepEvent> => {
|
||||
) => {
|
||||
const agent = this.agents.get(event.data.currentAgentName);
|
||||
if (!agent) {
|
||||
throw new Error("No valid agent found");
|
||||
@@ -330,17 +331,19 @@ export class AgentWorkflow {
|
||||
|
||||
const output = await agent.takeStep(ctx, event.data.input, agent.tools);
|
||||
|
||||
ctx.sendEvent(output);
|
||||
ctx.sendEvent(
|
||||
new AgentStepEvent({
|
||||
agentName: agent.name,
|
||||
response: output.data.response,
|
||||
toolCalls: output.data.toolCalls,
|
||||
}),
|
||||
);
|
||||
|
||||
return new AgentStepEvent({
|
||||
agentName: agent.name,
|
||||
response: output.data.response,
|
||||
toolCalls: output.data.toolCalls,
|
||||
});
|
||||
ctx.sendEvent(output);
|
||||
};
|
||||
|
||||
private parseAgentOutput = async (
|
||||
ctx: HandlerContext<AgentWorkflowContext>,
|
||||
ctx: StepContext<AgentWorkflowContext>,
|
||||
event: AgentStepEvent,
|
||||
): Promise<ToolCallsEvent | StopEvent<{ result: string }>> => {
|
||||
const { agentName, response, toolCalls } = event.data;
|
||||
@@ -374,7 +377,7 @@ export class AgentWorkflow {
|
||||
};
|
||||
|
||||
private executeToolCalls = async (
|
||||
ctx: HandlerContext<AgentWorkflowContext>,
|
||||
ctx: StepContext<AgentWorkflowContext>,
|
||||
event: ToolCallsEvent,
|
||||
): Promise<ToolResultsEvent | StopEvent<{ result: string }>> => {
|
||||
const { agentName, toolCalls } = event.data;
|
||||
@@ -423,7 +426,7 @@ export class AgentWorkflow {
|
||||
};
|
||||
|
||||
private processToolResults = async (
|
||||
ctx: HandlerContext<AgentWorkflowContext>,
|
||||
ctx: StepContext<AgentWorkflowContext>,
|
||||
event: ToolResultsEvent,
|
||||
): Promise<AgentInput | StopEvent<{ result: string }>> => {
|
||||
const { agentName, results } = event.data;
|
||||
@@ -491,7 +494,6 @@ export class AgentWorkflow {
|
||||
this.workflow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<AgentInputData>],
|
||||
outputs: [AgentInput],
|
||||
},
|
||||
this.handleInputStep,
|
||||
);
|
||||
@@ -499,7 +501,6 @@ export class AgentWorkflow {
|
||||
this.workflow.addStep(
|
||||
{
|
||||
inputs: [AgentInput],
|
||||
outputs: [AgentSetup],
|
||||
},
|
||||
this.setupAgent,
|
||||
);
|
||||
@@ -507,7 +508,6 @@ export class AgentWorkflow {
|
||||
this.workflow.addStep(
|
||||
{
|
||||
inputs: [AgentSetup],
|
||||
outputs: [AgentStepEvent],
|
||||
},
|
||||
this.runAgentStep,
|
||||
);
|
||||
@@ -515,7 +515,6 @@ export class AgentWorkflow {
|
||||
this.workflow.addStep(
|
||||
{
|
||||
inputs: [AgentStepEvent],
|
||||
outputs: [ToolCallsEvent, StopEvent],
|
||||
},
|
||||
this.parseAgentOutput,
|
||||
);
|
||||
@@ -523,7 +522,6 @@ export class AgentWorkflow {
|
||||
this.workflow.addStep(
|
||||
{
|
||||
inputs: [ToolCallsEvent],
|
||||
outputs: [ToolResultsEvent, StopEvent],
|
||||
},
|
||||
this.executeToolCalls,
|
||||
);
|
||||
@@ -531,7 +529,6 @@ export class AgentWorkflow {
|
||||
this.workflow.addStep(
|
||||
{
|
||||
inputs: [ToolResultsEvent],
|
||||
outputs: [AgentInput, StopEvent],
|
||||
},
|
||||
this.processToolResults,
|
||||
);
|
||||
@@ -541,7 +538,7 @@ export class AgentWorkflow {
|
||||
|
||||
private callTool(
|
||||
toolCall: AgentToolCall,
|
||||
ctx: HandlerContext<AgentWorkflowContext>,
|
||||
ctx: StepContext<AgentWorkflowContext>,
|
||||
) {
|
||||
const tool = this.agents
|
||||
.get(toolCall.data.agentName)
|
||||
@@ -563,7 +560,7 @@ export class AgentWorkflow {
|
||||
chatHistory?: ChatMessage[];
|
||||
context?: AgentWorkflowContext;
|
||||
},
|
||||
): WorkflowContext<AgentInputData, string, AgentWorkflowContext> {
|
||||
) {
|
||||
if (this.agents.size === 0) {
|
||||
throw new Error("No agents added to workflow");
|
||||
}
|
||||
@@ -577,15 +574,13 @@ export class AgentWorkflow {
|
||||
nextAgentName: null,
|
||||
};
|
||||
|
||||
const result = this.workflow.run(
|
||||
return this.workflow.run(
|
||||
{
|
||||
userInput: userInput,
|
||||
chatHistory: params?.chatHistory,
|
||||
},
|
||||
contextData,
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { StepContext } from "@llama-flow/llamaindex";
|
||||
import type { BaseToolWithCall, ChatMessage, LLM } from "@llamaindex/core/llms";
|
||||
import { BaseMemory } from "@llamaindex/core/memory";
|
||||
import type { HandlerContext } from "../workflow-context";
|
||||
import type { AgentOutput, AgentToolCallResult } from "./events";
|
||||
|
||||
export type AgentWorkflowContext = {
|
||||
@@ -28,7 +28,7 @@ export interface BaseWorkflowAgent {
|
||||
* Using memory directly to get messages instead of requiring them to be passed in
|
||||
*/
|
||||
takeStep(
|
||||
ctx: HandlerContext<AgentWorkflowContext>,
|
||||
ctx: StepContext<AgentWorkflowContext>,
|
||||
llmInput: ChatMessage[],
|
||||
tools: BaseToolWithCall[],
|
||||
): Promise<AgentOutput>;
|
||||
@@ -37,7 +37,7 @@ export interface BaseWorkflowAgent {
|
||||
* Handle results from tool calls
|
||||
*/
|
||||
handleToolCallResults(
|
||||
ctx: HandlerContext<AgentWorkflowContext>,
|
||||
ctx: StepContext<AgentWorkflowContext>,
|
||||
results: AgentToolCallResult[],
|
||||
): Promise<void>;
|
||||
|
||||
@@ -45,7 +45,7 @@ export interface BaseWorkflowAgent {
|
||||
* Finalize the agent's output
|
||||
*/
|
||||
finalize(
|
||||
ctx: HandlerContext<AgentWorkflowContext>,
|
||||
ctx: StepContext<AgentWorkflowContext>,
|
||||
output: AgentOutput,
|
||||
memory: BaseMemory,
|
||||
): Promise<AgentOutput>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { WorkflowEvent } from "@llama-flow/llamaindex";
|
||||
import type { JSONValue } from "@llamaindex/core/global";
|
||||
import type { ChatMessage, ToolResult } from "@llamaindex/core/llms";
|
||||
import { WorkflowEvent } from "../workflow-event";
|
||||
|
||||
export class AgentToolCall extends WorkflowEvent<{
|
||||
agentName: string;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { StepContext } from "@llama-flow/llamaindex";
|
||||
import type { JSONObject } from "@llamaindex/core/global";
|
||||
import { Settings } from "@llamaindex/core/global";
|
||||
import {
|
||||
@@ -7,7 +8,6 @@ import {
|
||||
type ChatResponseChunk,
|
||||
} from "@llamaindex/core/llms";
|
||||
import { BaseMemory } from "@llamaindex/core/memory";
|
||||
import type { HandlerContext } from "../workflow-context";
|
||||
import { AgentWorkflow } from "./agent-workflow";
|
||||
import { type AgentWorkflowContext, type BaseWorkflowAgent } from "./base";
|
||||
import {
|
||||
@@ -110,7 +110,7 @@ export class FunctionAgent implements BaseWorkflowAgent {
|
||||
}
|
||||
|
||||
async takeStep(
|
||||
ctx: HandlerContext<AgentWorkflowContext>,
|
||||
ctx: StepContext<AgentWorkflowContext>,
|
||||
llmInput: ChatMessage[],
|
||||
tools: BaseToolWithCall[],
|
||||
): Promise<AgentOutput> {
|
||||
@@ -170,7 +170,7 @@ export class FunctionAgent implements BaseWorkflowAgent {
|
||||
}
|
||||
|
||||
async handleToolCallResults(
|
||||
ctx: HandlerContext<AgentWorkflowContext>,
|
||||
ctx: StepContext<AgentWorkflowContext>,
|
||||
results: AgentToolCallResult[],
|
||||
): Promise<void> {
|
||||
const scratchpad: ChatMessage[] = ctx.data.scratchpad;
|
||||
@@ -196,7 +196,7 @@ export class FunctionAgent implements BaseWorkflowAgent {
|
||||
}
|
||||
|
||||
async finalize(
|
||||
ctx: HandlerContext<AgentWorkflowContext>,
|
||||
ctx: StepContext<AgentWorkflowContext>,
|
||||
output: AgentOutput,
|
||||
memory: BaseMemory,
|
||||
): Promise<AgentOutput> {
|
||||
|
||||
@@ -1,8 +1,2 @@
|
||||
export * from "@llama-flow/llamaindex";
|
||||
export * from "./agent/index.js";
|
||||
export {
|
||||
WorkflowContext,
|
||||
type HandlerContext,
|
||||
type StepHandler,
|
||||
} from "./workflow-context.js";
|
||||
export { StartEvent, StopEvent, WorkflowEvent } from "./workflow-event.js";
|
||||
export { Workflow, type StepParameters } from "./workflow.js";
|
||||
|
||||
@@ -1,628 +0,0 @@
|
||||
import { CustomEvent, randomUUID } from "@llamaindex/env";
|
||||
import {
|
||||
type AnyWorkflowEventConstructor,
|
||||
StartEvent,
|
||||
type StartEventConstructor,
|
||||
StopEvent,
|
||||
type StopEventConstructor,
|
||||
WorkflowEvent,
|
||||
} from "./workflow-event";
|
||||
|
||||
export type StepHandler<
|
||||
Data = unknown,
|
||||
Inputs extends [
|
||||
AnyWorkflowEventConstructor | StartEventConstructor,
|
||||
...(AnyWorkflowEventConstructor | StopEventConstructor)[],
|
||||
] = [AnyWorkflowEventConstructor | StartEventConstructor],
|
||||
Out extends (AnyWorkflowEventConstructor | StopEventConstructor)[] = [],
|
||||
> = (
|
||||
context: HandlerContext<Data>,
|
||||
...events: {
|
||||
[K in keyof Inputs]: InstanceType<Inputs[K]>;
|
||||
}
|
||||
) => Promise<
|
||||
Out extends []
|
||||
? void
|
||||
: {
|
||||
[K in keyof Out]: InstanceType<Out[K]>;
|
||||
}[number]
|
||||
>;
|
||||
|
||||
export type ReadonlyStepMap<Data> = ReadonlyMap<
|
||||
StepHandler<Data, never, never>,
|
||||
{
|
||||
inputs: AnyWorkflowEventConstructor[];
|
||||
outputs: AnyWorkflowEventConstructor[];
|
||||
}
|
||||
>;
|
||||
|
||||
type GlobalEvent = typeof globalThis.Event;
|
||||
|
||||
export type Wait = () => Promise<void>;
|
||||
|
||||
export type ContextParams<Start, Stop, Data> = {
|
||||
startEvent: StartEvent<Start>;
|
||||
contextData: Data;
|
||||
steps: ReadonlyStepMap<Data>;
|
||||
timeout: number | null;
|
||||
verbose: boolean;
|
||||
wait: Wait;
|
||||
|
||||
queue: QueueProtocol[] | undefined;
|
||||
pendingInputQueue: WorkflowEvent<unknown>[] | undefined;
|
||||
resolved: StopEvent<Stop> | null | undefined;
|
||||
rejected: Error | null | undefined;
|
||||
};
|
||||
|
||||
function flattenEvents(
|
||||
acceptEventTypes: AnyWorkflowEventConstructor[],
|
||||
inputEvents: WorkflowEvent<unknown>[],
|
||||
): WorkflowEvent<unknown>[] {
|
||||
const eventMap = new Map<
|
||||
AnyWorkflowEventConstructor,
|
||||
WorkflowEvent<unknown>
|
||||
>();
|
||||
|
||||
for (const event of inputEvents) {
|
||||
for (const acceptType of acceptEventTypes) {
|
||||
if (event instanceof acceptType && !eventMap.has(acceptType)) {
|
||||
eventMap.set(acceptType, event);
|
||||
break; // Once matched, no need to check other accept types
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(eventMap.values());
|
||||
}
|
||||
|
||||
export type HandlerContext<Data = unknown> = {
|
||||
get data(): Data;
|
||||
sendEvent(event: WorkflowEvent<unknown>): void;
|
||||
requireEvent<T extends AnyWorkflowEventConstructor>(
|
||||
event: T,
|
||||
): Promise<InstanceType<T>>;
|
||||
};
|
||||
|
||||
export type QueueProtocol =
|
||||
| {
|
||||
type: "event";
|
||||
event: WorkflowEvent<unknown>;
|
||||
}
|
||||
| {
|
||||
type: "requestEvent";
|
||||
id: string;
|
||||
requestEvent: AnyWorkflowEventConstructor;
|
||||
};
|
||||
|
||||
export class WorkflowContext<Start = string, Stop = string, Data = unknown>
|
||||
implements
|
||||
AsyncIterable<WorkflowEvent<unknown>, unknown, void>,
|
||||
Promise<StopEvent<Stop>>
|
||||
{
|
||||
readonly #steps: ReadonlyStepMap<Data>;
|
||||
|
||||
readonly #startEvent: StartEvent<Start>;
|
||||
readonly #queue: QueueProtocol[] = [];
|
||||
readonly #queueEventTarget = new EventTarget();
|
||||
readonly #wait: Wait;
|
||||
|
||||
#timeout: number | null = null;
|
||||
#verbose: boolean = false;
|
||||
#data: Data;
|
||||
|
||||
#stepCache: WeakMap<
|
||||
WorkflowEvent<unknown>,
|
||||
[
|
||||
step: Set<StepHandler<Data, never, never>>,
|
||||
stepInputs: WeakMap<
|
||||
StepHandler<Data, never, never>,
|
||||
AnyWorkflowEventConstructor[]
|
||||
>,
|
||||
stepOutputs: WeakMap<
|
||||
StepHandler<Data, never, never>,
|
||||
AnyWorkflowEventConstructor[]
|
||||
>,
|
||||
]
|
||||
> = new Map();
|
||||
|
||||
#getStepFunction(
|
||||
event: WorkflowEvent<unknown>,
|
||||
): [
|
||||
step: Set<StepHandler<Data, never, never>>,
|
||||
stepInputs: WeakMap<
|
||||
StepHandler<Data, never, never>,
|
||||
AnyWorkflowEventConstructor[]
|
||||
>,
|
||||
stepOutputs: WeakMap<
|
||||
StepHandler<Data, never, never>,
|
||||
AnyWorkflowEventConstructor[]
|
||||
>,
|
||||
] {
|
||||
if (this.#stepCache.has(event)) {
|
||||
return this.#stepCache.get(event)!;
|
||||
}
|
||||
const set = new Set<StepHandler<Data, never, never>>();
|
||||
const stepInputs = new WeakMap<
|
||||
StepHandler<Data, never, never>,
|
||||
AnyWorkflowEventConstructor[]
|
||||
>();
|
||||
const stepOutputs = new WeakMap<
|
||||
StepHandler<Data, never, never>,
|
||||
AnyWorkflowEventConstructor[]
|
||||
>();
|
||||
const res: [
|
||||
step: Set<StepHandler<Data, never, never>>,
|
||||
stepInputs: WeakMap<
|
||||
StepHandler<Data, never, never>,
|
||||
AnyWorkflowEventConstructor[]
|
||||
>,
|
||||
stepOutputs: WeakMap<
|
||||
StepHandler<Data, never, never>,
|
||||
AnyWorkflowEventConstructor[]
|
||||
>,
|
||||
] = [set, stepInputs, stepOutputs];
|
||||
this.#stepCache.set(event, res);
|
||||
for (const [step, { inputs, outputs }] of this.#steps) {
|
||||
if (inputs.some((input) => event instanceof input)) {
|
||||
set.add(step);
|
||||
stepInputs.set(step, inputs);
|
||||
stepOutputs.set(step, outputs);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
constructor(params: ContextParams<Start, Stop, Data>) {
|
||||
this.#steps = params.steps;
|
||||
this.#startEvent = params.startEvent;
|
||||
if (typeof params.timeout === "number") {
|
||||
this.#timeout = params.timeout;
|
||||
}
|
||||
this.#data = params.contextData;
|
||||
this.#verbose = params.verbose ?? false;
|
||||
this.#wait = params.wait;
|
||||
|
||||
// push start event to the queue
|
||||
const [step] = this.#getStepFunction(this.#startEvent);
|
||||
if (step.size === 0) {
|
||||
throw new TypeError("No step found for start event");
|
||||
}
|
||||
|
||||
// restore from snapshot
|
||||
if (params.queue) {
|
||||
params.queue.forEach((protocol) => {
|
||||
this.#queue.push(protocol);
|
||||
});
|
||||
} else {
|
||||
this.#sendEvent(this.#startEvent);
|
||||
}
|
||||
if (params.pendingInputQueue) {
|
||||
this.#pendingInputQueue = params.pendingInputQueue;
|
||||
}
|
||||
if (params.resolved) {
|
||||
this.#resolved = params.resolved;
|
||||
}
|
||||
if (params.rejected) {
|
||||
this.#rejected = params.rejected;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure it will only be called once
|
||||
#iterator: AsyncIterableIterator<WorkflowEvent<unknown>> | null = null;
|
||||
#signal: AbortSignal | null = null;
|
||||
|
||||
get #iteratorSingleton(): AsyncIterableIterator<WorkflowEvent<unknown>> {
|
||||
if (this.#iterator === null) {
|
||||
this.#iterator = this.#createStreamEvents();
|
||||
}
|
||||
return this.#iterator;
|
||||
}
|
||||
|
||||
[Symbol.asyncIterator](): AsyncIterableIterator<WorkflowEvent<unknown>> {
|
||||
return this.#iteratorSingleton;
|
||||
}
|
||||
|
||||
#sendEvent = (event: WorkflowEvent<unknown>): void => {
|
||||
this.#queue.push({
|
||||
type: "event",
|
||||
event,
|
||||
});
|
||||
};
|
||||
|
||||
#requireEvent = async <T extends AnyWorkflowEventConstructor>(
|
||||
event: T,
|
||||
): Promise<InstanceType<T>> => {
|
||||
const requestId = randomUUID();
|
||||
this.#queue.push({
|
||||
type: "requestEvent",
|
||||
id: requestId,
|
||||
requestEvent: event,
|
||||
});
|
||||
return new Promise((resolve) => {
|
||||
const handler = (event: InstanceType<GlobalEvent>) => {
|
||||
if (event instanceof CustomEvent) {
|
||||
const { id } = event.detail;
|
||||
if (requestId === id) {
|
||||
this.#queueEventTarget.removeEventListener("update", handler);
|
||||
resolve(event.detail.event);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.#queueEventTarget.addEventListener("update", handler);
|
||||
});
|
||||
};
|
||||
|
||||
#pendingInputQueue: WorkflowEvent<unknown>[] = [];
|
||||
|
||||
// if strict mode is enabled, it will throw an error if there's input or output events are not expected
|
||||
#strict = false;
|
||||
|
||||
strict() {
|
||||
this.#strict = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
get data(): Data {
|
||||
return this.#data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream events from the start event
|
||||
*
|
||||
* Note that this function will stop once there's no more future events,
|
||||
* if you want stop immediately once reach a StopEvent, you should handle it in the other side.
|
||||
* @private
|
||||
*/
|
||||
#createStreamEvents(): AsyncIterableIterator<WorkflowEvent<unknown>> {
|
||||
const isPendingEvents = new WeakSet<WorkflowEvent<unknown>>();
|
||||
const pendingTasks = new Set<Promise<WorkflowEvent<unknown> | void>>();
|
||||
const enqueuedEvents = new Set<WorkflowEvent<unknown>>();
|
||||
const stream = new ReadableStream<WorkflowEvent<unknown>>({
|
||||
start: async (controller) => {
|
||||
while (true) {
|
||||
const eventProtocol = this.#queue.shift();
|
||||
if (eventProtocol) {
|
||||
switch (eventProtocol.type) {
|
||||
case "requestEvent": {
|
||||
const { id, requestEvent } = eventProtocol;
|
||||
const acceptableInput = this.#pendingInputQueue.find(
|
||||
(event) => event instanceof requestEvent,
|
||||
);
|
||||
if (acceptableInput) {
|
||||
// remove the event from the queue, in case of infinite loop
|
||||
const protocolIdx = this.#queue.findIndex(
|
||||
(protocol) =>
|
||||
protocol.type === "event" &&
|
||||
protocol.event === acceptableInput,
|
||||
);
|
||||
if (protocolIdx !== -1) {
|
||||
this.#queue.splice(protocolIdx, 1);
|
||||
}
|
||||
this.#pendingInputQueue.splice(
|
||||
this.#pendingInputQueue.indexOf(acceptableInput),
|
||||
1,
|
||||
);
|
||||
this.#queueEventTarget.dispatchEvent(
|
||||
new CustomEvent("update", {
|
||||
detail: { id, event: acceptableInput },
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
// push back to the queue as there are not enough events
|
||||
this.#queue.push(eventProtocol);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "event": {
|
||||
const { event } = eventProtocol;
|
||||
if (isPendingEvents.has(event)) {
|
||||
// this event is still processing
|
||||
this.#sendEvent(event);
|
||||
} else {
|
||||
if (!enqueuedEvents.has(event)) {
|
||||
controller.enqueue(event);
|
||||
enqueuedEvents.add(event);
|
||||
}
|
||||
const [steps, inputsMap, outputsMap] =
|
||||
this.#getStepFunction(event);
|
||||
const nextEventPromises: Promise<WorkflowEvent<unknown> | void>[] =
|
||||
[...steps]
|
||||
.map((step) => {
|
||||
const inputs = [...(inputsMap.get(step) ?? [])];
|
||||
const acceptableInputs: WorkflowEvent<unknown>[] =
|
||||
this.#pendingInputQueue.filter((event) =>
|
||||
inputs.some((input) => event instanceof input),
|
||||
);
|
||||
const events: WorkflowEvent<unknown>[] = flattenEvents(
|
||||
inputs,
|
||||
[event, ...acceptableInputs],
|
||||
);
|
||||
// remove the event from the queue, in case of infinite loop
|
||||
events.forEach((event) => {
|
||||
const protocolIdx = this.#queue.findIndex(
|
||||
(protocol) =>
|
||||
protocol.type === "event" &&
|
||||
protocol.event === event,
|
||||
);
|
||||
if (protocolIdx !== -1) {
|
||||
this.#queue.splice(protocolIdx, 1);
|
||||
}
|
||||
});
|
||||
if (events.length !== inputs.length) {
|
||||
if (this.#verbose) {
|
||||
console.log(
|
||||
`Not enough inputs for step ${step.name}, waiting for more events`,
|
||||
);
|
||||
}
|
||||
// not enough to run the step, push back to the queue
|
||||
this.#sendEvent(event);
|
||||
isPendingEvents.add(event);
|
||||
return null;
|
||||
}
|
||||
if (isPendingEvents.has(event)) {
|
||||
isPendingEvents.delete(event);
|
||||
}
|
||||
if (this.#verbose) {
|
||||
console.log(
|
||||
`Running step ${step.name} with inputs ${events}`,
|
||||
);
|
||||
}
|
||||
const data = this.data;
|
||||
return (step as StepHandler<Data>)
|
||||
.call(
|
||||
null,
|
||||
{
|
||||
get data() {
|
||||
return data;
|
||||
},
|
||||
sendEvent: this.#sendEvent,
|
||||
requireEvent: this.#requireEvent,
|
||||
},
|
||||
// @ts-expect-error IDK why
|
||||
...events.sort((a, b) => {
|
||||
const aIndex = inputs.indexOf(
|
||||
a.constructor as AnyWorkflowEventConstructor,
|
||||
);
|
||||
const bIndex = inputs.indexOf(
|
||||
b.constructor as AnyWorkflowEventConstructor,
|
||||
);
|
||||
return aIndex - bIndex;
|
||||
}),
|
||||
)
|
||||
.then((nextEvent: void | WorkflowEvent<unknown>) => {
|
||||
if (nextEvent === undefined) {
|
||||
return;
|
||||
}
|
||||
if (this.#verbose) {
|
||||
console.log(
|
||||
`Step ${step.name} completed, next event is ${nextEvent}`,
|
||||
);
|
||||
}
|
||||
const outputs = outputsMap.get(step) ?? [];
|
||||
if (
|
||||
!outputs.some(
|
||||
(output) => nextEvent.constructor === output,
|
||||
)
|
||||
) {
|
||||
if (this.#strict) {
|
||||
const error = Error(
|
||||
`Step ${step.name} returned an unexpected output event ${nextEvent}`,
|
||||
);
|
||||
controller.error(error);
|
||||
} else {
|
||||
console.warn(
|
||||
`Step ${step.name} returned an unexpected output event ${nextEvent}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!(nextEvent instanceof StopEvent)) {
|
||||
this.#pendingInputQueue.unshift(nextEvent);
|
||||
this.#sendEvent(nextEvent);
|
||||
}
|
||||
return nextEvent;
|
||||
});
|
||||
})
|
||||
.filter((promise) => promise !== null);
|
||||
nextEventPromises.forEach((promise) => {
|
||||
pendingTasks.add(promise);
|
||||
promise
|
||||
.catch((err) => {
|
||||
console.error("Error in step", err);
|
||||
})
|
||||
.finally(() => {
|
||||
pendingTasks.delete(promise);
|
||||
});
|
||||
});
|
||||
Promise.race(nextEventPromises)
|
||||
.then((fastestNextEvent) => {
|
||||
if (fastestNextEvent === undefined) {
|
||||
return;
|
||||
}
|
||||
if (!enqueuedEvents.has(fastestNextEvent)) {
|
||||
controller.enqueue(fastestNextEvent);
|
||||
enqueuedEvents.add(fastestNextEvent);
|
||||
}
|
||||
return fastestNextEvent;
|
||||
})
|
||||
.then(async (fastestNextEvent) =>
|
||||
Promise.all(nextEventPromises).then((nextEvents) => {
|
||||
const events = nextEvents.filter(
|
||||
(event) => event !== undefined,
|
||||
);
|
||||
for (const nextEvent of events) {
|
||||
// do not enqueue the same event twice
|
||||
if (fastestNextEvent !== nextEvent) {
|
||||
if (!enqueuedEvents.has(nextEvent)) {
|
||||
controller.enqueue(nextEvent);
|
||||
enqueuedEvents.add(nextEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.catch((err) => {
|
||||
// when the step raise an error, should go back to the previous step
|
||||
this.#sendEvent(event);
|
||||
isPendingEvents.add(event);
|
||||
controller.error(err);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.#queue.length === 0 && pendingTasks.size === 0) {
|
||||
if (this.#verbose) {
|
||||
console.log("No more events in the queue");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
await this.#wait();
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
return stream[Symbol.asyncIterator]();
|
||||
}
|
||||
|
||||
with<Initial extends Data>(
|
||||
data: Initial,
|
||||
): WorkflowContext<Start, Stop, Initial> {
|
||||
return new WorkflowContext({
|
||||
startEvent: this.#startEvent,
|
||||
wait: this.#wait,
|
||||
contextData: data,
|
||||
steps: this.#steps,
|
||||
timeout: this.#timeout,
|
||||
verbose: this.#verbose,
|
||||
queue: this.#queue,
|
||||
pendingInputQueue: this.#pendingInputQueue,
|
||||
resolved: this.#resolved,
|
||||
rejected: this.#rejected,
|
||||
});
|
||||
}
|
||||
|
||||
// PromiseLike implementation, this is following the Promise/A+ spec
|
||||
// It will consume the iterator and resolve the promise once it reaches the StopEvent
|
||||
// If you want to customize the behavior, you can use the async iterator directly
|
||||
#resolved: StopEvent<Stop> | null = null;
|
||||
#rejected: Error | null = null;
|
||||
|
||||
async then<TResult1, TResult2 = never>(
|
||||
onfulfilled?:
|
||||
| ((value: StopEvent<Stop>) => TResult1 | PromiseLike<TResult1>)
|
||||
| null
|
||||
| undefined,
|
||||
onrejected?:
|
||||
| ((reason: unknown) => TResult2 | PromiseLike<TResult2>)
|
||||
| null
|
||||
| undefined,
|
||||
) {
|
||||
onfulfilled ??= (value) => value as TResult1;
|
||||
onrejected ??= (reason) => {
|
||||
throw reason;
|
||||
};
|
||||
if (this.#resolved !== null) {
|
||||
return Promise.resolve(this.#resolved).then(onfulfilled, onrejected);
|
||||
} else if (this.#rejected !== null) {
|
||||
return Promise.reject(this.#rejected).then(onfulfilled, onrejected);
|
||||
}
|
||||
|
||||
if (this.#timeout !== null) {
|
||||
const timeout = this.#timeout;
|
||||
this.#signal = AbortSignal.timeout(timeout * 1000);
|
||||
}
|
||||
|
||||
this.#signal?.addEventListener("abort", () => {
|
||||
this.#rejected = new Error(
|
||||
`Operation timed out after ${this.#timeout} seconds`,
|
||||
);
|
||||
onrejected?.(this.#rejected);
|
||||
});
|
||||
try {
|
||||
for await (const event of this.#iteratorSingleton) {
|
||||
if (this.#rejected !== null) {
|
||||
return onrejected?.(this.#rejected);
|
||||
}
|
||||
if (event instanceof StartEvent) {
|
||||
if (this.#verbose) {
|
||||
console.log(`Starting workflow with event ${event}`);
|
||||
}
|
||||
}
|
||||
if (event instanceof StopEvent) {
|
||||
if (this.#verbose && this.#pendingInputQueue.length > 0) {
|
||||
// fixme: #pendingInputQueue might should be cleanup correctly?
|
||||
}
|
||||
this.#resolved = event;
|
||||
return onfulfilled?.(event);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
this.#rejected = err;
|
||||
}
|
||||
return onrejected?.(err);
|
||||
}
|
||||
const nextValue = await this.#iteratorSingleton.next();
|
||||
if (nextValue.done === false) {
|
||||
this.#rejected = new Error("Workflow did not complete");
|
||||
return onrejected?.(this.#rejected);
|
||||
}
|
||||
return onrejected?.(new Error("UNREACHABLE"));
|
||||
}
|
||||
|
||||
catch<TResult = never>(
|
||||
onrejected?:
|
||||
| ((reason: unknown) => TResult | PromiseLike<TResult>)
|
||||
| null
|
||||
| undefined,
|
||||
) {
|
||||
return this.then((v) => v, onrejected);
|
||||
}
|
||||
|
||||
finally(onfinally?: (() => void) | undefined | null) {
|
||||
return this.then(
|
||||
() => {
|
||||
onfinally?.();
|
||||
},
|
||||
() => {
|
||||
onfinally?.();
|
||||
},
|
||||
) as Promise<never>;
|
||||
}
|
||||
|
||||
[Symbol.toStringTag]: string = "Context";
|
||||
|
||||
// for worker thread
|
||||
snapshot(): ArrayBuffer {
|
||||
const state = {
|
||||
startEvent: this.#startEvent,
|
||||
queue: this.#queue,
|
||||
pendingInputQueue: this.#pendingInputQueue,
|
||||
data: this.#data,
|
||||
timeout: this.#timeout,
|
||||
verbose: this.#verbose,
|
||||
resolved: this.#resolved,
|
||||
rejected: this.#rejected,
|
||||
};
|
||||
|
||||
const jsonString = JSON.stringify(state, (_, value) => {
|
||||
// If value is an instance of a class, serialize only its properties
|
||||
if (value instanceof WorkflowEvent) {
|
||||
return { data: value.data, constructor: value.constructor.name };
|
||||
}
|
||||
// value is Subtype of WorkflowEvent
|
||||
if (
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
value?.prototype instanceof WorkflowEvent
|
||||
) {
|
||||
return { constructor: value.prototype.constructor.name };
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
return new TextEncoder().encode(jsonString).buffer;
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
export class WorkflowEvent<Data> {
|
||||
displayName: string;
|
||||
data: Data;
|
||||
|
||||
constructor(data: Data) {
|
||||
this.data = data;
|
||||
this.displayName = this.constructor.name;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.displayName;
|
||||
}
|
||||
|
||||
static or<
|
||||
A extends AnyWorkflowEventConstructor,
|
||||
B extends AnyWorkflowEventConstructor,
|
||||
>(AEvent: A, BEvent: B): A | B {
|
||||
function OrEvent() {
|
||||
throw new Error("Cannot instantiate OrEvent");
|
||||
}
|
||||
|
||||
OrEvent.prototype = Object.create(AEvent.prototype);
|
||||
|
||||
Object.getOwnPropertyNames(BEvent.prototype).forEach((property) => {
|
||||
if (!(property in OrEvent.prototype)) {
|
||||
Object.defineProperty(
|
||||
OrEvent.prototype,
|
||||
property,
|
||||
Object.getOwnPropertyDescriptor(BEvent.prototype, property)!,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
OrEvent.prototype.constructor = OrEvent;
|
||||
|
||||
Object.defineProperty(OrEvent, Symbol.hasInstance, {
|
||||
value: function (instance: unknown) {
|
||||
return instance instanceof AEvent || instance instanceof BEvent;
|
||||
},
|
||||
});
|
||||
|
||||
return OrEvent as unknown as A | B;
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type AnyWorkflowEventConstructor = new (data: any) => WorkflowEvent<any>;
|
||||
|
||||
export type StartEventConstructor<T = string> = new (data: T) => StartEvent<T>;
|
||||
export type StopEventConstructor<T = string> = new (data: T) => StopEvent<T>;
|
||||
|
||||
// These are special events that are used to control the workflow
|
||||
export class StartEvent<T = string> extends WorkflowEvent<T> {
|
||||
constructor(data: T) {
|
||||
super(data);
|
||||
}
|
||||
}
|
||||
|
||||
export class StopEvent<T = string> extends WorkflowEvent<T> {
|
||||
constructor(data: T) {
|
||||
super(data);
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
import {
|
||||
WorkflowContext,
|
||||
type HandlerContext,
|
||||
type QueueProtocol,
|
||||
type StepHandler,
|
||||
type Wait,
|
||||
} from "./workflow-context.js";
|
||||
import {
|
||||
StartEvent,
|
||||
StopEvent,
|
||||
type AnyWorkflowEventConstructor,
|
||||
type StartEventConstructor,
|
||||
type StopEventConstructor,
|
||||
} from "./workflow-event.js";
|
||||
|
||||
export type StepParameters<
|
||||
In extends AnyWorkflowEventConstructor[],
|
||||
Out extends AnyWorkflowEventConstructor[],
|
||||
> = {
|
||||
inputs: In;
|
||||
outputs: Out;
|
||||
};
|
||||
|
||||
export class Workflow<ContextData, Start, Stop> {
|
||||
#steps: Map<
|
||||
StepHandler<ContextData, never, never>,
|
||||
{
|
||||
inputs: AnyWorkflowEventConstructor[];
|
||||
outputs: AnyWorkflowEventConstructor[];
|
||||
}
|
||||
> = new Map();
|
||||
#verbose: boolean = false;
|
||||
#timeout: number | null = null;
|
||||
// fixme: allow microtask
|
||||
#nextTick: Wait = () => new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
constructor(
|
||||
params: {
|
||||
verbose?: boolean;
|
||||
timeout?: number | null;
|
||||
wait?: Wait;
|
||||
} = {},
|
||||
) {
|
||||
if (params.verbose) {
|
||||
this.#verbose = params.verbose;
|
||||
}
|
||||
if (params.timeout) {
|
||||
this.#timeout = params.timeout;
|
||||
}
|
||||
if (params.wait) {
|
||||
this.#nextTick = params.wait;
|
||||
}
|
||||
}
|
||||
|
||||
addStep<
|
||||
const In extends [
|
||||
AnyWorkflowEventConstructor | StartEventConstructor,
|
||||
...(AnyWorkflowEventConstructor | StopEventConstructor)[],
|
||||
],
|
||||
const Out extends (AnyWorkflowEventConstructor | StopEventConstructor)[],
|
||||
>(
|
||||
parameters: StepParameters<In, Out>,
|
||||
stepFn: (
|
||||
context: HandlerContext<ContextData>,
|
||||
...events: {
|
||||
[K in keyof In]: InstanceType<In[K]>;
|
||||
}
|
||||
) => Promise<
|
||||
Out extends []
|
||||
? void
|
||||
: {
|
||||
[K in keyof Out]: InstanceType<Out[K]>;
|
||||
}[number]
|
||||
>,
|
||||
): this {
|
||||
const { inputs, outputs } = parameters;
|
||||
this.#steps.set(stepFn as never, { inputs, outputs });
|
||||
return this;
|
||||
}
|
||||
|
||||
hasStep(stepFn: StepHandler): boolean {
|
||||
return this.#steps.has(stepFn);
|
||||
}
|
||||
|
||||
removeStep(stepFn: StepHandler): this {
|
||||
this.#steps.delete(stepFn);
|
||||
return this;
|
||||
}
|
||||
|
||||
run(
|
||||
event: StartEvent<Start> | Start,
|
||||
): unknown extends ContextData
|
||||
? WorkflowContext<Start, Stop, ContextData>
|
||||
: WorkflowContext<Start, Stop, ContextData | undefined>;
|
||||
run<Data extends ContextData>(
|
||||
event: StartEvent<Start> | Start,
|
||||
data: Data,
|
||||
): WorkflowContext<Start, Stop, Data>;
|
||||
run<Data extends ContextData>(
|
||||
event: StartEvent<Start> | Start,
|
||||
data?: Data,
|
||||
): WorkflowContext<Start, Stop, Data> {
|
||||
const startEvent: StartEvent<Start> =
|
||||
event instanceof StartEvent ? event : new StartEvent(event);
|
||||
|
||||
return new WorkflowContext<Start, Stop, Data>({
|
||||
startEvent,
|
||||
wait: this.#nextTick,
|
||||
contextData: data!,
|
||||
steps: new Map(this.#steps),
|
||||
timeout: this.#timeout,
|
||||
verbose: this.#verbose,
|
||||
queue: undefined,
|
||||
pendingInputQueue: undefined,
|
||||
resolved: null,
|
||||
rejected: null,
|
||||
});
|
||||
}
|
||||
|
||||
recover(data: ArrayBuffer): WorkflowContext<Start, Stop, ContextData> {
|
||||
const jsonString = new TextDecoder().decode(data);
|
||||
|
||||
const state = JSON.parse(jsonString);
|
||||
|
||||
const reconstructedStartEvent = new StartEvent<Start>(state.startEvent);
|
||||
const AllEvents = [...this.#steps]
|
||||
.map(([, { inputs, outputs }]) => [...inputs, ...(outputs ?? [])])
|
||||
.flat();
|
||||
const reconstructedQueue: QueueProtocol[] = state.queue.map(
|
||||
(protocol: QueueProtocol): QueueProtocol => {
|
||||
switch (protocol.type) {
|
||||
case "requestEvent": {
|
||||
const { requestEvent, id } = protocol;
|
||||
const EventType = AllEvents.find(
|
||||
(type) =>
|
||||
type.prototype.constructor.name ===
|
||||
(requestEvent.constructor as unknown as string),
|
||||
);
|
||||
if (!EventType) {
|
||||
throw new TypeError(
|
||||
`Event type not found: ${requestEvent.constructor}`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
type: "requestEvent",
|
||||
id,
|
||||
requestEvent: EventType,
|
||||
};
|
||||
}
|
||||
case "event": {
|
||||
const { event } = protocol;
|
||||
const EventType = AllEvents.find(
|
||||
(type) =>
|
||||
type.prototype.constructor.name ===
|
||||
(event.constructor as unknown as string),
|
||||
);
|
||||
if (!EventType) {
|
||||
throw new TypeError(`Event type not found: ${event.constructor}`);
|
||||
}
|
||||
return {
|
||||
type: "event",
|
||||
event: new EventType(event.data),
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const reconstructedPendingInputQueue = state.pendingInputQueue.map(
|
||||
(event: Record<string, unknown>) => {
|
||||
const EventType = AllEvents.find(
|
||||
(type) => type.prototype.constructor.name === event.constructor,
|
||||
);
|
||||
if (!EventType) {
|
||||
throw new TypeError(`Event type not found: ${event.constructor}`);
|
||||
}
|
||||
return new EventType(event.data);
|
||||
},
|
||||
);
|
||||
|
||||
return new WorkflowContext<Start, Stop, ContextData>({
|
||||
startEvent: reconstructedStartEvent,
|
||||
contextData: state.data,
|
||||
wait: this.#nextTick,
|
||||
steps: this.#steps, // Assuming steps do not change and are part of the class prototype or similar
|
||||
timeout: state.timeout,
|
||||
verbose: state.verbose,
|
||||
queue: reconstructedQueue,
|
||||
pendingInputQueue: reconstructedPendingInputQueue,
|
||||
resolved: state.resolved ? new StopEvent<Stop>(state.resolved) : null,
|
||||
rejected: state.rejected ? new Error(state.rejected) : null,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -128,17 +128,17 @@ describe("AgentWorkflow", () => {
|
||||
"StartEvent",
|
||||
"AgentInput",
|
||||
"AgentSetup",
|
||||
"AgentStepEvent",
|
||||
"AgentStream",
|
||||
"AgentStepEvent",
|
||||
"AgentOutput",
|
||||
"ToolCallsEvent",
|
||||
"ToolResultsEvent",
|
||||
"AgentToolCall",
|
||||
"AgentToolCallResult",
|
||||
"ToolResultsEvent",
|
||||
"AgentInput",
|
||||
"AgentSetup",
|
||||
"AgentStepEvent",
|
||||
"AgentStream",
|
||||
"AgentStepEvent",
|
||||
"AgentOutput",
|
||||
"StopEvent",
|
||||
];
|
||||
|
||||
@@ -39,7 +39,7 @@ describe("FunctionAgent", () => {
|
||||
returnDirect: false,
|
||||
},
|
||||
displayName: "test",
|
||||
} as AgentToolCallResult;
|
||||
} as unknown as AgentToolCallResult;
|
||||
|
||||
const dummyContext = {
|
||||
data: {
|
||||
|
||||
@@ -26,6 +26,7 @@ export function setupToolCallingMockLLM(
|
||||
topP: 1,
|
||||
contextWindow: 4096,
|
||||
tokenizer: undefined,
|
||||
structuredOutput: false,
|
||||
},
|
||||
});
|
||||
mockLLM.supportToolCall = true;
|
||||
|
||||
@@ -1,972 +0,0 @@
|
||||
import {
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
expectTypeOf,
|
||||
test,
|
||||
vi,
|
||||
type Mocked,
|
||||
} from "vitest";
|
||||
import type { HandlerContext, StepHandler, StepParameters } from "../src";
|
||||
import { StartEvent, StopEvent, Workflow, WorkflowEvent } from "../src";
|
||||
|
||||
class JokeEvent extends WorkflowEvent<{ joke: string }> {}
|
||||
|
||||
class AnalysisEvent extends WorkflowEvent<{ analysis: string }> {}
|
||||
|
||||
describe("type system", () => {
|
||||
test("handler", () => {
|
||||
type Parameters = StepParameters<
|
||||
[typeof StartEvent<string>],
|
||||
[typeof StopEvent<string>]
|
||||
>;
|
||||
type Handler = (
|
||||
context: HandlerContext,
|
||||
ev: StartEvent<string>,
|
||||
) => Promise<StopEvent<string>>;
|
||||
type Handler2 = (
|
||||
context: HandlerContext,
|
||||
ev: StartEvent<string>,
|
||||
) => Promise<StopEvent<number>>;
|
||||
type Handler3 = (
|
||||
context: HandlerContext,
|
||||
ev: StartEvent<string>,
|
||||
) => Promise<AnalysisEvent>;
|
||||
expectTypeOf<Parameters>().toEqualTypeOf<{
|
||||
inputs: [typeof StartEvent<string>];
|
||||
outputs: [typeof StopEvent<string>];
|
||||
}>();
|
||||
expectTypeOf<
|
||||
StepHandler<
|
||||
unknown,
|
||||
[typeof StartEvent<string>],
|
||||
[typeof StopEvent<string>]
|
||||
>
|
||||
>().toEqualTypeOf<Handler>();
|
||||
expectTypeOf<
|
||||
StepHandler<
|
||||
unknown,
|
||||
[typeof StartEvent<string>],
|
||||
[typeof StopEvent<string>]
|
||||
>
|
||||
>().not.toEqualTypeOf<Handler2>();
|
||||
expectTypeOf<
|
||||
StepHandler<
|
||||
unknown,
|
||||
[typeof StartEvent<string>],
|
||||
[typeof StopEvent<string>]
|
||||
>
|
||||
>().not.toEqualTypeOf<Handler3>();
|
||||
});
|
||||
});
|
||||
|
||||
describe("workflow basic", () => {
|
||||
let generateJoke: Mocked<
|
||||
(context: HandlerContext, ev: StartEvent) => Promise<JokeEvent>
|
||||
>;
|
||||
let critiqueJoke: Mocked<
|
||||
(context: HandlerContext, ev: JokeEvent) => Promise<StopEvent<string>>
|
||||
>;
|
||||
let analyzeJoke: Mocked<
|
||||
(context: HandlerContext, ev: JokeEvent) => Promise<AnalysisEvent>
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
generateJoke = vi.fn(async (_context, _: StartEvent) => {
|
||||
return new JokeEvent({ joke: "a joke" });
|
||||
});
|
||||
|
||||
critiqueJoke = vi.fn(async (_context, _: JokeEvent) => {
|
||||
return new StopEvent("stop");
|
||||
});
|
||||
|
||||
analyzeJoke = vi.fn(async (_context: HandlerContext, _: JokeEvent) => {
|
||||
return new AnalysisEvent({ analysis: "an analysis" });
|
||||
});
|
||||
});
|
||||
|
||||
test("workflow basic", async () => {
|
||||
const workflow = new Workflow<
|
||||
{
|
||||
foo: string;
|
||||
bar: number;
|
||||
},
|
||||
string,
|
||||
string
|
||||
>();
|
||||
workflow.addStep(
|
||||
{
|
||||
inputs: [StartEvent],
|
||||
outputs: [StopEvent],
|
||||
},
|
||||
async ({ data }, start) => {
|
||||
expect(start).toBeInstanceOf(StartEvent);
|
||||
expect(start.data).toBe("start");
|
||||
expect(data.bar).toBe(42);
|
||||
expect(data.foo).toBe("foo");
|
||||
return new StopEvent("stopped");
|
||||
},
|
||||
);
|
||||
|
||||
const result = workflow.run("start", {
|
||||
foo: "foo",
|
||||
bar: 42,
|
||||
});
|
||||
await result;
|
||||
});
|
||||
|
||||
test("run workflow", async () => {
|
||||
const jokeFlow = new Workflow<unknown, string, string>({ verbose: true });
|
||||
|
||||
jokeFlow.addStep(
|
||||
{ inputs: [StartEvent<string>], outputs: [JokeEvent] },
|
||||
generateJoke,
|
||||
);
|
||||
jokeFlow.addStep(
|
||||
{ inputs: [JokeEvent], outputs: [StopEvent] },
|
||||
critiqueJoke,
|
||||
);
|
||||
|
||||
const result = await jokeFlow.run("pirates");
|
||||
|
||||
expect(generateJoke).toHaveBeenCalledTimes(1);
|
||||
expect(critiqueJoke).toHaveBeenCalledTimes(1);
|
||||
expect(result.data).toBe("stop");
|
||||
});
|
||||
|
||||
test("stream events", async () => {
|
||||
const jokeFlow = new Workflow<unknown, string, string>({ verbose: true });
|
||||
|
||||
jokeFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [JokeEvent],
|
||||
},
|
||||
generateJoke,
|
||||
);
|
||||
jokeFlow.addStep(
|
||||
{
|
||||
inputs: [JokeEvent],
|
||||
outputs: [StopEvent],
|
||||
},
|
||||
critiqueJoke,
|
||||
);
|
||||
|
||||
const run = jokeFlow.run("pirates");
|
||||
const event = await run[Symbol.asyncIterator]().next(); // get one event to avoid testing timeout
|
||||
const result = await run;
|
||||
|
||||
expect(generateJoke).toHaveBeenCalledTimes(1);
|
||||
expect(critiqueJoke).toHaveBeenCalledTimes(1);
|
||||
expect(result.data).toBe("stop");
|
||||
expect(event).not.toBeNull();
|
||||
});
|
||||
|
||||
test("workflow timeout", async () => {
|
||||
const TIMEOUT = 1;
|
||||
const jokeFlow = new Workflow<unknown, string, string>({
|
||||
verbose: true,
|
||||
timeout: TIMEOUT,
|
||||
});
|
||||
|
||||
const longRunning = async (_context: HandlerContext, ev: StartEvent) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait for 2 seconds
|
||||
return new StopEvent("We waited 2 seconds");
|
||||
};
|
||||
|
||||
jokeFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [StopEvent],
|
||||
},
|
||||
longRunning,
|
||||
);
|
||||
const run = jokeFlow.run("Let's start");
|
||||
await expect(run).rejects.toThrow(
|
||||
`Operation timed out after ${TIMEOUT} seconds`,
|
||||
);
|
||||
});
|
||||
|
||||
test("workflow validation", async () => {
|
||||
const jokeFlow = new Workflow<unknown, string, string>({ verbose: true });
|
||||
jokeFlow.addStep(
|
||||
{ inputs: [StartEvent<string>], outputs: [StopEvent] },
|
||||
generateJoke,
|
||||
);
|
||||
jokeFlow.addStep(
|
||||
{ inputs: [JokeEvent], outputs: [StopEvent] },
|
||||
critiqueJoke,
|
||||
);
|
||||
expect(async () => {
|
||||
await jokeFlow.run("pirates").strict();
|
||||
}).rejects.toThrow(
|
||||
"Step spy returned an unexpected output event JokeEvent",
|
||||
);
|
||||
});
|
||||
|
||||
test("requireEvents - 1", async () => {
|
||||
const jokeFlow = new Workflow<unknown, string, string>({ verbose: true });
|
||||
|
||||
jokeFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [StopEvent],
|
||||
},
|
||||
async (ctx, start) => {
|
||||
ctx.sendEvent(new AnalysisEvent({ analysis: "an analysis" }));
|
||||
await ctx.requireEvent(JokeEvent);
|
||||
return new StopEvent("Report generated");
|
||||
},
|
||||
);
|
||||
|
||||
const fn = vi.fn(async () => {
|
||||
return new JokeEvent({ joke: "a joke" });
|
||||
});
|
||||
|
||||
jokeFlow.addStep(
|
||||
{
|
||||
inputs: [AnalysisEvent],
|
||||
outputs: [JokeEvent],
|
||||
},
|
||||
fn,
|
||||
);
|
||||
|
||||
const result = await jokeFlow.run("pirates");
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
expect(result.data).toBe("Report generated");
|
||||
});
|
||||
|
||||
test("run workflow with multiple in-degree", async () => {
|
||||
const jokeFlow = new Workflow<unknown, string, string>({ verbose: true });
|
||||
|
||||
jokeFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent],
|
||||
outputs: [JokeEvent],
|
||||
},
|
||||
async (context, _) => {
|
||||
context.sendEvent(
|
||||
new AnalysisEvent({
|
||||
analysis: "an analysis",
|
||||
}),
|
||||
);
|
||||
return new JokeEvent({
|
||||
joke: "a joke",
|
||||
});
|
||||
},
|
||||
);
|
||||
jokeFlow.addStep(
|
||||
{
|
||||
inputs: [JokeEvent, AnalysisEvent],
|
||||
outputs: [StopEvent<string>],
|
||||
},
|
||||
async () => {
|
||||
return new StopEvent("The analysis is insightful and helpful.");
|
||||
},
|
||||
);
|
||||
|
||||
const result = await jokeFlow.run("pirates");
|
||||
expect(result.data).toBe("The analysis is insightful and helpful.");
|
||||
});
|
||||
|
||||
test("run invalid workflow", async () => {
|
||||
const jokeFlow = new Workflow<unknown, string, string>({ verbose: true });
|
||||
|
||||
jokeFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [JokeEvent],
|
||||
},
|
||||
generateJoke,
|
||||
);
|
||||
jokeFlow.addStep(
|
||||
{
|
||||
inputs: [JokeEvent],
|
||||
outputs: [StopEvent<string>],
|
||||
},
|
||||
// @ts-expect-error it actually returns AnalysisEvent
|
||||
analyzeJoke,
|
||||
);
|
||||
jokeFlow.addStep(
|
||||
{
|
||||
inputs: [JokeEvent],
|
||||
outputs: [StopEvent<string>],
|
||||
},
|
||||
async () => {
|
||||
return new StopEvent("The analysis is insightful and helpful.");
|
||||
},
|
||||
);
|
||||
const consoleSpy = vi.spyOn(console, "warn");
|
||||
expect(consoleSpy).toHaveBeenCalledTimes(0);
|
||||
const result = await jokeFlow.run("pirates");
|
||||
expect(consoleSpy).toHaveBeenCalledTimes(1);
|
||||
consoleSpy.mockRestore();
|
||||
expect(result.data).toBe("The analysis is insightful and helpful.");
|
||||
});
|
||||
|
||||
test("run workflow with object-based StartEvent and StopEvent", async () => {
|
||||
const objectFlow = new Workflow<
|
||||
unknown,
|
||||
Person,
|
||||
{
|
||||
result: {
|
||||
greeting: string;
|
||||
};
|
||||
}
|
||||
>({ verbose: true });
|
||||
|
||||
type Person = { name: string; age: number };
|
||||
|
||||
const processObject = vi.fn(async (_context, ev: StartEvent<Person>) => {
|
||||
const { name, age } = ev.data;
|
||||
return new StopEvent({
|
||||
result: { greeting: `Hello ${name}, you are ${age} years old!` },
|
||||
});
|
||||
});
|
||||
|
||||
objectFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<Person>],
|
||||
outputs: [
|
||||
StopEvent<{
|
||||
result: {
|
||||
greeting: string;
|
||||
};
|
||||
}>,
|
||||
],
|
||||
},
|
||||
processObject,
|
||||
);
|
||||
|
||||
const result = await objectFlow.run(
|
||||
new StartEvent<Person>({ name: "Alice", age: 30 }),
|
||||
);
|
||||
|
||||
expect(processObject).toHaveBeenCalledTimes(1);
|
||||
expect(result.data.result).toEqual({
|
||||
greeting: "Hello Alice, you are 30 years old!",
|
||||
});
|
||||
});
|
||||
|
||||
test("workflow with two concurrent steps", async () => {
|
||||
const concurrentFlow = new Workflow<unknown, string, string>({
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
const step1 = vi.fn(async (_context, _ev: StartEvent) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
return new StopEvent("Step 1 completed");
|
||||
});
|
||||
|
||||
const step2 = vi.fn(async (_context, _ev: StartEvent) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
return new StopEvent("Step 2 completed");
|
||||
});
|
||||
|
||||
concurrentFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [StopEvent<string>],
|
||||
},
|
||||
step1,
|
||||
);
|
||||
concurrentFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [StopEvent<string>],
|
||||
},
|
||||
step2,
|
||||
);
|
||||
|
||||
const startTime = new Date();
|
||||
const result = await concurrentFlow.run("start");
|
||||
const endTime = new Date();
|
||||
const duration = endTime.getTime() - startTime.getTime();
|
||||
|
||||
expect(step1).toHaveBeenCalledTimes(1);
|
||||
expect(step2).toHaveBeenCalledTimes(1);
|
||||
expect(duration).toBeLessThan(200);
|
||||
expect(result.data).toBe("Step 2 completed");
|
||||
});
|
||||
|
||||
test("workflow with two concurrent cyclic steps", async () => {
|
||||
const concurrentCyclicFlow = new Workflow<unknown, string, string>({
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
class Step1Event extends WorkflowEvent<{
|
||||
result: string;
|
||||
}> {}
|
||||
|
||||
class Step2Event extends WorkflowEvent<{
|
||||
result: string;
|
||||
}> {}
|
||||
|
||||
let step2Count = 0;
|
||||
|
||||
const step1 = vi.fn(async (_context, ev: StartEvent | Step1Event) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
return new Step1Event({ result: "Step 1 completed" });
|
||||
});
|
||||
|
||||
const step2 = vi.fn(async (_context, ev: StartEvent | Step2Event) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
step2Count++;
|
||||
if (step2Count >= 5) {
|
||||
return new StopEvent("Step 2 completed 5 times");
|
||||
}
|
||||
return new Step2Event({ result: "Step 2 completed" });
|
||||
});
|
||||
|
||||
concurrentCyclicFlow.addStep(
|
||||
{
|
||||
inputs: [WorkflowEvent.or(StartEvent<string>, Step1Event)],
|
||||
outputs: [Step1Event],
|
||||
},
|
||||
step1,
|
||||
);
|
||||
concurrentCyclicFlow.addStep(
|
||||
{
|
||||
inputs: [WorkflowEvent.or(StartEvent<string>, Step2Event)],
|
||||
outputs: [Step2Event, StopEvent],
|
||||
},
|
||||
step2,
|
||||
);
|
||||
|
||||
const startTime = new Date();
|
||||
const result = await concurrentCyclicFlow.run("start");
|
||||
const endTime = new Date();
|
||||
const duration = endTime.getTime() - startTime.getTime();
|
||||
|
||||
expect(step1).toHaveBeenCalledTimes(1);
|
||||
expect(step2).toHaveBeenCalledTimes(5);
|
||||
expect(duration).toBeGreaterThanOrEqual(500); // At least 5 * 100ms for step2
|
||||
expect(duration).toBeLessThan(1000); // Less than 1 second
|
||||
expect(result.data).toBe("Step 2 completed 5 times");
|
||||
});
|
||||
|
||||
test("sendEvent", async () => {
|
||||
const myWorkflow = new Workflow<unknown, string, string>({ verbose: true });
|
||||
|
||||
class QueryEvent extends WorkflowEvent<{ query: string }> {}
|
||||
|
||||
class QueryResultEvent extends WorkflowEvent<{ result: string }> {}
|
||||
|
||||
class PendingEvent extends WorkflowEvent<void> {}
|
||||
|
||||
myWorkflow.addStep(
|
||||
{
|
||||
inputs: [StartEvent],
|
||||
outputs: [PendingEvent],
|
||||
},
|
||||
async (context: HandlerContext, events) => {
|
||||
context.sendEvent(new QueryEvent({ query: "something" }));
|
||||
return new PendingEvent();
|
||||
},
|
||||
);
|
||||
|
||||
myWorkflow.addStep(
|
||||
{
|
||||
inputs: [QueryEvent],
|
||||
outputs: [QueryResultEvent],
|
||||
},
|
||||
async (context, event) => {
|
||||
return new QueryResultEvent({ result: "query result" });
|
||||
},
|
||||
);
|
||||
|
||||
myWorkflow.addStep(
|
||||
{
|
||||
inputs: [PendingEvent, QueryResultEvent],
|
||||
outputs: [StopEvent<string>],
|
||||
},
|
||||
async (context, ev0, ev1) => {
|
||||
return new StopEvent(ev1.data.result);
|
||||
},
|
||||
);
|
||||
|
||||
const result = await myWorkflow.run("start");
|
||||
expect(result.data).toBe("query result");
|
||||
});
|
||||
|
||||
test("requireEvents - 2", async () => {
|
||||
const myWorkflow = new Workflow<unknown, string, string>({ verbose: true });
|
||||
|
||||
class QueryEvent extends WorkflowEvent<{ query: string }> {}
|
||||
|
||||
class QueryResultEvent extends WorkflowEvent<{ result: string }> {}
|
||||
|
||||
myWorkflow.addStep(
|
||||
{
|
||||
inputs: [StartEvent],
|
||||
outputs: [StopEvent<string>],
|
||||
},
|
||||
async (context: HandlerContext) => {
|
||||
context.sendEvent(new QueryEvent({ query: "something" }));
|
||||
const queryResultEvent = await context.requireEvent(QueryResultEvent);
|
||||
return new StopEvent(queryResultEvent.data.result);
|
||||
},
|
||||
);
|
||||
|
||||
myWorkflow.addStep(
|
||||
{
|
||||
inputs: [QueryEvent],
|
||||
outputs: [QueryResultEvent],
|
||||
},
|
||||
async (context, event) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
return new QueryResultEvent({ result: "query result" });
|
||||
},
|
||||
);
|
||||
|
||||
const result = await myWorkflow.run("start");
|
||||
expect(result.data).toBe("query result");
|
||||
});
|
||||
|
||||
test("allow output with send event", async () => {
|
||||
const myFlow = new Workflow<unknown, string, string>({ verbose: true });
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [],
|
||||
},
|
||||
async (context, ev) => {
|
||||
context.sendEvent(new StopEvent(`Hello ${ev.data}!`));
|
||||
},
|
||||
);
|
||||
const result = myFlow.run("world");
|
||||
expect((await result).data).toBe("Hello world!");
|
||||
});
|
||||
});
|
||||
|
||||
describe("workflow event loop", () => {
|
||||
test("basic", async () => {
|
||||
const jokeFlow = new Workflow<unknown, string, string>({ verbose: true });
|
||||
|
||||
jokeFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [StopEvent<string>],
|
||||
},
|
||||
async (_context, ev: StartEvent) => {
|
||||
return new StopEvent(`Hello ${ev.data}!`);
|
||||
},
|
||||
);
|
||||
|
||||
const result = await jokeFlow.run("world");
|
||||
expect(result.data).toBe("Hello world!");
|
||||
});
|
||||
|
||||
test("branch", async () => {
|
||||
const myFlow = new Workflow<unknown, string, string>({ verbose: true });
|
||||
|
||||
class BranchA1Event extends WorkflowEvent<{ payload: string }> {}
|
||||
|
||||
class BranchA2Event extends WorkflowEvent<{ payload: string }> {}
|
||||
|
||||
class BranchB1Event extends WorkflowEvent<{ payload: string }> {}
|
||||
|
||||
class BranchB2Event extends WorkflowEvent<{ payload: string }> {}
|
||||
|
||||
let control = false;
|
||||
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [BranchA1Event, BranchB1Event],
|
||||
},
|
||||
async (_context, ev) => {
|
||||
if (control) {
|
||||
return new BranchA1Event({ payload: ev.data });
|
||||
} else {
|
||||
return new BranchB1Event({ payload: ev.data });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [BranchA1Event],
|
||||
outputs: [BranchA2Event],
|
||||
},
|
||||
async (_context, ev) => {
|
||||
return new BranchA2Event({ payload: ev.data.payload });
|
||||
},
|
||||
);
|
||||
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [BranchB1Event],
|
||||
outputs: [BranchB2Event],
|
||||
},
|
||||
async (_context, ev) => {
|
||||
return new BranchB2Event({ payload: ev.data.payload });
|
||||
},
|
||||
);
|
||||
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [BranchA2Event],
|
||||
outputs: [StopEvent<string>],
|
||||
},
|
||||
async (_context, ev) => {
|
||||
return new StopEvent(`Branch A2: ${ev.data.payload}`);
|
||||
},
|
||||
);
|
||||
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [BranchB2Event],
|
||||
outputs: [StopEvent],
|
||||
},
|
||||
async (_context, ev) => {
|
||||
return new StopEvent(`Branch B2: ${ev.data.payload}`);
|
||||
},
|
||||
);
|
||||
|
||||
{
|
||||
const result = await myFlow.run("world");
|
||||
expect(result.data).toMatch(/Branch B2: world/);
|
||||
}
|
||||
|
||||
control = true;
|
||||
|
||||
{
|
||||
const result = await myFlow.run("world");
|
||||
expect(result.data).toMatch(/Branch A2: world/);
|
||||
}
|
||||
|
||||
{
|
||||
const context = myFlow.run("world");
|
||||
for await (const event of context) {
|
||||
if (event instanceof BranchA2Event) {
|
||||
expect(event.data.payload).toBe("world");
|
||||
}
|
||||
if (event instanceof StopEvent) {
|
||||
expect(event.data).toMatch(/Branch A2: world/);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("one event have multiple outputs", async () => {
|
||||
const myFlow = new Workflow<unknown, string, string>({ verbose: true });
|
||||
|
||||
class AEvent extends WorkflowEvent<{ payload: string }> {}
|
||||
|
||||
class BEvent extends WorkflowEvent<{ payload: string }> {}
|
||||
|
||||
class CEvent extends WorkflowEvent<{ payload: string }> {}
|
||||
|
||||
class DEvent extends WorkflowEvent<{ payload: string }> {}
|
||||
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [StopEvent<string>],
|
||||
},
|
||||
async (_context, ev) => {
|
||||
return new StopEvent("STOP");
|
||||
},
|
||||
);
|
||||
|
||||
const fn = vi.fn(async (_context, ev: StartEvent) => {
|
||||
return new AEvent({ payload: ev.data });
|
||||
});
|
||||
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [AEvent],
|
||||
},
|
||||
fn,
|
||||
);
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [AEvent],
|
||||
outputs: [BEvent, CEvent],
|
||||
},
|
||||
async (_context, ev: AEvent) => {
|
||||
return new BEvent({ payload: ev.data.payload });
|
||||
},
|
||||
);
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [AEvent],
|
||||
outputs: [CEvent],
|
||||
},
|
||||
async (_context, ev: AEvent) => {
|
||||
return new CEvent({ payload: ev.data.payload });
|
||||
},
|
||||
);
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [BEvent],
|
||||
outputs: [DEvent],
|
||||
},
|
||||
async (_context, ev: BEvent) => {
|
||||
return new DEvent({ payload: ev.data.payload });
|
||||
},
|
||||
);
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [CEvent],
|
||||
outputs: [DEvent],
|
||||
},
|
||||
async (_context, ev: CEvent) => {
|
||||
return new DEvent({ payload: ev.data.payload });
|
||||
},
|
||||
);
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [DEvent],
|
||||
outputs: [StopEvent<string>],
|
||||
},
|
||||
async (_context, ev: DEvent) => {
|
||||
return new StopEvent(`Hello ${ev.data.payload}!`);
|
||||
},
|
||||
);
|
||||
|
||||
const result = await myFlow.run("world");
|
||||
expect(result.data).toBe("STOP");
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// streaming events will allow to consume event even stop event is reached
|
||||
const stream = myFlow.run("world");
|
||||
for await (const _ of stream) {
|
||||
/* empty */
|
||||
}
|
||||
expect(fn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test("run with custom context", async () => {
|
||||
type MyContext = { name: string };
|
||||
const myFlow = new Workflow<MyContext, string, string>({ verbose: true });
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [StopEvent<string>],
|
||||
},
|
||||
async ({ data }, _: StartEvent) => {
|
||||
return new StopEvent(`Hello ${data.name}!`);
|
||||
},
|
||||
);
|
||||
|
||||
const result = await myFlow.run("world", { name: "Alice" });
|
||||
expect(result.data).toBe("Hello Alice!");
|
||||
});
|
||||
|
||||
test("run with custom context with two streaming", async () => {
|
||||
type MyContext = { name: string };
|
||||
const myFlow = new Workflow<MyContext, string, string>({ verbose: true });
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent],
|
||||
outputs: [StopEvent],
|
||||
},
|
||||
async ({ data }, _) => {
|
||||
if (data == null) {
|
||||
return new StopEvent({ result: "EMPTY" });
|
||||
}
|
||||
return new StopEvent({ result: `Hello ${data.name}!` });
|
||||
},
|
||||
);
|
||||
|
||||
const context1 = myFlow.run("world");
|
||||
const context2 = context1.with({ name: "Alice" });
|
||||
const context3 = context1.with({ name: "Bob" });
|
||||
expect(await context1).toMatchInlineSnapshot(`
|
||||
StopEvent {
|
||||
"data": {
|
||||
"result": "EMPTY",
|
||||
},
|
||||
"displayName": "StopEvent",
|
||||
}
|
||||
`);
|
||||
expect(await context2).toMatchInlineSnapshot(`
|
||||
StopEvent {
|
||||
"data": {
|
||||
"result": "Hello Alice!",
|
||||
},
|
||||
"displayName": "StopEvent",
|
||||
}
|
||||
`);
|
||||
expect(await context3).toMatchInlineSnapshot(`
|
||||
StopEvent {
|
||||
"data": {
|
||||
"result": "Hello Bob!",
|
||||
},
|
||||
"displayName": "StopEvent",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test("workflow multiple output", async () => {
|
||||
const myFlow = new Workflow<unknown, string, string>({ verbose: true });
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [StopEvent<string>, StopEvent<string>],
|
||||
},
|
||||
async (_context, ev) => {
|
||||
return new StopEvent(`Hello ${ev.data}!`);
|
||||
},
|
||||
);
|
||||
const result = await myFlow.run("world").strict();
|
||||
expect(result.data).toBe("Hello world!");
|
||||
});
|
||||
});
|
||||
|
||||
describe("snapshot", async () => {
|
||||
test("snapshot and recover", async () => {
|
||||
const myFlow = new Workflow({ verbose: true });
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [StopEvent<string>],
|
||||
},
|
||||
async (_, ev: StartEvent) => {
|
||||
return new StopEvent(`Hello ${ev.data}!`);
|
||||
},
|
||||
);
|
||||
const context = myFlow.run("world");
|
||||
const arrayBuffer = context.snapshot();
|
||||
expect(arrayBuffer).toBeInstanceOf(ArrayBuffer);
|
||||
const context2 = await myFlow.recover(arrayBuffer);
|
||||
expect(context2.data).toBe("Hello world!");
|
||||
});
|
||||
|
||||
test("snapshot in middle of workflow run ", async () => {
|
||||
const myFlow = new Workflow<
|
||||
{
|
||||
value: number;
|
||||
},
|
||||
string,
|
||||
string
|
||||
>({ verbose: true });
|
||||
|
||||
class AEvent extends WorkflowEvent<{ payload: string }> {}
|
||||
|
||||
const fn = vi.fn(async (_, ev: StartEvent) => {
|
||||
return new AEvent({ payload: ev.data });
|
||||
});
|
||||
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [AEvent],
|
||||
},
|
||||
fn,
|
||||
);
|
||||
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [AEvent],
|
||||
outputs: [StopEvent],
|
||||
},
|
||||
async ({ data }, _: AEvent) => {
|
||||
return new StopEvent(`Hello ${data.value}!`);
|
||||
},
|
||||
);
|
||||
|
||||
const context = myFlow.run("world", {
|
||||
value: 1,
|
||||
});
|
||||
for await (const event of context) {
|
||||
if (event instanceof AEvent) {
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
const arrayBuffer = context.snapshot();
|
||||
expect(arrayBuffer).toBeInstanceOf(ArrayBuffer);
|
||||
const context2 = await myFlow.recover(arrayBuffer).with({
|
||||
value: 2,
|
||||
});
|
||||
expect(context2.data).toBe("Hello 2!");
|
||||
break;
|
||||
}
|
||||
if (event instanceof StopEvent) {
|
||||
expect(event.data).toBe("Hello 1!");
|
||||
}
|
||||
}
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("error", () => {
|
||||
test("error in handler", async () => {
|
||||
const myFlow = new Workflow<boolean, string, string>({ verbose: true });
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [StopEvent<string>],
|
||||
},
|
||||
async ({ data }) => {
|
||||
if (!data) {
|
||||
throw new Error("Something went wrong");
|
||||
} else {
|
||||
return new StopEvent(`Hello ${data}!`);
|
||||
}
|
||||
},
|
||||
);
|
||||
await expect(myFlow.run("world")).rejects.toThrow("Something went wrong");
|
||||
{
|
||||
const context = myFlow.run("world");
|
||||
try {
|
||||
for await (const _ of context) {
|
||||
// do nothing
|
||||
}
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect((error as Error).message).toBe("Something went wrong");
|
||||
const snapshot = context.snapshot();
|
||||
const newContext = myFlow.recover(snapshot).with(true);
|
||||
expect((await newContext).data).toBe("Hello true!");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("recover in the middle of workflow", async () => {
|
||||
const myFlow = new Workflow<string | undefined, string, string>({
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
class AEvent extends WorkflowEvent<string> {}
|
||||
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [StartEvent<string>],
|
||||
outputs: [AEvent],
|
||||
},
|
||||
async ({ data }) => {
|
||||
if (data !== undefined) {
|
||||
throw new Error("Something went wrong");
|
||||
}
|
||||
return new AEvent("world");
|
||||
},
|
||||
);
|
||||
myFlow.addStep(
|
||||
{
|
||||
inputs: [AEvent],
|
||||
outputs: [StopEvent],
|
||||
},
|
||||
async ({ data }, ev) => {
|
||||
if (data === undefined) {
|
||||
throw new Error("Something went wrong");
|
||||
}
|
||||
return new StopEvent(`Hello, ${data}!`);
|
||||
},
|
||||
);
|
||||
// no context, so will throw error
|
||||
const context = myFlow.run("world");
|
||||
try {
|
||||
for await (const _ of context) {
|
||||
// do nothing
|
||||
}
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect((error as Error).message).toBe("Something went wrong");
|
||||
const snapshot = context.snapshot();
|
||||
const newContext = myFlow.recover(snapshot).with("Recovered Data");
|
||||
expect((await newContext).data).toBe("Hello, Recovered Data!");
|
||||
}
|
||||
});
|
||||
});
|
||||
Generated
+164
-45
@@ -67,7 +67,7 @@ importers:
|
||||
version: 8.30.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.7.3)
|
||||
vitest:
|
||||
specifier: ^3.1.1
|
||||
version: 3.1.1(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.4)(lightningcss@1.29.3)(msw@2.7.4(@types/node@22.14.1)(typescript@5.7.3))(terser@5.39.0)
|
||||
version: 3.1.1(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.4)(jiti@2.4.2)(lightningcss@1.29.3)(msw@2.7.4(@types/node@22.14.1)(typescript@5.7.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
|
||||
|
||||
apps/next:
|
||||
dependencies:
|
||||
@@ -424,11 +424,11 @@ importers:
|
||||
specifier: ^5.7.3
|
||||
version: 5.7.3
|
||||
vite:
|
||||
specifier: ^5.4.16
|
||||
version: 5.4.16(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0)
|
||||
specifier: ^6.3.3
|
||||
version: 6.3.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
|
||||
vite-plugin-wasm:
|
||||
specifier: ^3.3.0
|
||||
version: 3.4.1(vite@5.4.16(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0))
|
||||
specifier: ^3.4.1
|
||||
version: 3.4.1(vite@6.3.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))
|
||||
|
||||
e2e/examples/nextjs-agent:
|
||||
dependencies:
|
||||
@@ -551,8 +551,8 @@ importers:
|
||||
specifier: ^5.7.3
|
||||
version: 5.7.3
|
||||
vite:
|
||||
specifier: ^5.4.16
|
||||
version: 5.4.16(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0)
|
||||
specifier: ^6.3.3
|
||||
version: 6.3.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
|
||||
|
||||
e2e/examples/waku-query-engine:
|
||||
dependencies:
|
||||
@@ -1452,7 +1452,7 @@ importers:
|
||||
version: link:../../openai
|
||||
vitest:
|
||||
specifier: ^3.0.9
|
||||
version: 3.1.1(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.4)(lightningcss@1.29.3)(msw@2.7.4(@types/node@22.14.1)(typescript@5.8.3))(terser@5.39.0)
|
||||
version: 3.1.1(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.4)(jiti@2.4.2)(lightningcss@1.29.3)(msw@2.7.4(@types/node@22.14.1)(typescript@5.8.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
|
||||
|
||||
packages/providers/storage/firestore:
|
||||
dependencies:
|
||||
@@ -1979,6 +1979,9 @@ importers:
|
||||
|
||||
packages/workflow:
|
||||
dependencies:
|
||||
'@llama-flow/llamaindex':
|
||||
specifier: ^0.0.12
|
||||
version: 0.0.12(@modelcontextprotocol/sdk@1.9.0)(hono@4.7.7)(next@15.3.0(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(p-retry@6.2.1)(zod@3.24.2)
|
||||
zod:
|
||||
specifier: ^3.23.8
|
||||
version: 3.24.2
|
||||
@@ -3861,9 +3864,35 @@ packages:
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
'@llama-flow/core@0.3.9':
|
||||
resolution: {integrity: sha512-/s0L16qo/hbF8Ahe5KEjJp6YfomIizp5lyhwgwQ0bomOijP3wX6L2xHnXucPRg5RGxgzpIIlDFW1sTQkjIamqg==}
|
||||
peerDependencies:
|
||||
'@modelcontextprotocol/sdk': ^1.7.0
|
||||
hono: ^4.7.4
|
||||
next: ^15.2.2
|
||||
p-retry: ^6.2.1
|
||||
rxjs: ^7.8.2
|
||||
zod: ^3.24.2
|
||||
peerDependenciesMeta:
|
||||
'@modelcontextprotocol/sdk':
|
||||
optional: true
|
||||
hono:
|
||||
optional: true
|
||||
next:
|
||||
optional: true
|
||||
p-retry:
|
||||
optional: true
|
||||
rxjs:
|
||||
optional: true
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
'@llama-flow/docs@0.0.5':
|
||||
resolution: {integrity: sha512-iAEqqWgPnJNxm4syNSxudDE1aHYNi1eTrxHg+FjcPeZhxyksLYRzpmzUikcZv0uhxgLwRinF7UPTnH9ioKlaEw==}
|
||||
|
||||
'@llama-flow/llamaindex@0.0.12':
|
||||
resolution: {integrity: sha512-NoUCyVaZTHkdwzllcEY0mzqOkGFTemgpOBHg96fQx5ewZczsy+QpW09pJkMNsLaGQ6JJeBZ4mrBjMkgNUI57Mw==}
|
||||
|
||||
'@llamaindex/chat-ui@0.2.0':
|
||||
resolution: {integrity: sha512-9U5+9l2UVBaOG8fSuMjnere5R2QSNxCEcixMwBgt4L4b0evo8jU4ZzlSxLPunWfpn1PWFVMUwKLlSSwa1qTTyA==}
|
||||
peerDependencies:
|
||||
@@ -8180,6 +8209,14 @@ packages:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fdir@6.4.4:
|
||||
resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fecha@4.2.3:
|
||||
resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
|
||||
|
||||
@@ -12466,6 +12503,10 @@ packages:
|
||||
resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tinyglobby@0.2.13:
|
||||
resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tinypool@1.0.2:
|
||||
resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
@@ -13065,6 +13106,46 @@ packages:
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
vite@6.3.3:
|
||||
resolution: {integrity: sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==}
|
||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
|
||||
jiti: '>=1.21.0'
|
||||
less: '*'
|
||||
lightningcss: ^1.21.0
|
||||
sass: '*'
|
||||
sass-embedded: '*'
|
||||
stylus: '*'
|
||||
sugarss: '*'
|
||||
terser: ^5.16.0
|
||||
tsx: ^4.8.1
|
||||
yaml: ^2.4.2
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
jiti:
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
lightningcss:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
sass-embedded:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
tsx:
|
||||
optional: true
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
vitest@2.1.0:
|
||||
resolution: {integrity: sha512-XuuEeyNkqbfr0FtAvd9vFbInSSNY1ykCQTYQ0sj9wPy4hx+1gR7gqVNdW0AX2wrrM1wWlN5fnJDjF9xG6mYRSQ==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
@@ -15961,8 +16042,27 @@ snapshots:
|
||||
p-retry: 6.2.1
|
||||
zod: 3.24.2
|
||||
|
||||
'@llama-flow/core@0.3.9(@modelcontextprotocol/sdk@1.9.0)(hono@4.7.7)(next@15.3.0(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(p-retry@6.2.1)(zod@3.24.2)':
|
||||
optionalDependencies:
|
||||
'@modelcontextprotocol/sdk': 1.9.0
|
||||
hono: 4.7.7
|
||||
next: 15.3.0(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
p-retry: 6.2.1
|
||||
zod: 3.24.2
|
||||
|
||||
'@llama-flow/docs@0.0.5': {}
|
||||
|
||||
'@llama-flow/llamaindex@0.0.12(@modelcontextprotocol/sdk@1.9.0)(hono@4.7.7)(next@15.3.0(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(p-retry@6.2.1)(zod@3.24.2)':
|
||||
dependencies:
|
||||
'@llama-flow/core': 0.3.9(@modelcontextprotocol/sdk@1.9.0)(hono@4.7.7)(next@15.3.0(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(p-retry@6.2.1)(zod@3.24.2)
|
||||
transitivePeerDependencies:
|
||||
- '@modelcontextprotocol/sdk'
|
||||
- hono
|
||||
- next
|
||||
- p-retry
|
||||
- rxjs
|
||||
- zod
|
||||
|
||||
'@llamaindex/chat-ui@0.2.0(@types/react-dom@19.0.4(@types/react@19.0.10))(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@llamaindex/pdf-viewer': 1.3.0(@types/react@19.0.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
@@ -18828,23 +18928,23 @@ snapshots:
|
||||
msw: 2.7.4(@types/node@22.9.0)(typescript@5.8.3)
|
||||
vite: 5.4.16(@types/node@22.9.0)(lightningcss@1.29.3)(terser@5.39.0)
|
||||
|
||||
'@vitest/mocker@3.1.1(msw@2.7.4(@types/node@22.14.1)(typescript@5.7.3))(vite@5.4.16(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0))':
|
||||
'@vitest/mocker@3.1.1(msw@2.7.4(@types/node@22.14.1)(typescript@5.7.3))(vite@6.3.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.1.1
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.17
|
||||
optionalDependencies:
|
||||
msw: 2.7.4(@types/node@22.14.1)(typescript@5.7.3)
|
||||
vite: 5.4.16(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0)
|
||||
vite: 6.3.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
|
||||
|
||||
'@vitest/mocker@3.1.1(msw@2.7.4(@types/node@22.14.1)(typescript@5.8.3))(vite@5.4.16(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0))':
|
||||
'@vitest/mocker@3.1.1(msw@2.7.4(@types/node@22.14.1)(typescript@5.8.3))(vite@6.3.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.1.1
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.17
|
||||
optionalDependencies:
|
||||
msw: 2.7.4(@types/node@22.14.1)(typescript@5.8.3)
|
||||
vite: 5.4.16(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0)
|
||||
vite: 6.3.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
|
||||
|
||||
'@vitest/pretty-format@2.1.0':
|
||||
dependencies:
|
||||
@@ -20810,7 +20910,7 @@ snapshots:
|
||||
'@typescript-eslint/parser': 8.30.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3)
|
||||
eslint: 9.22.0(jiti@2.4.2)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.22.0(jiti@2.4.2)))(eslint@9.22.0(jiti@2.4.2))
|
||||
eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@9.22.0(jiti@2.4.2))
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.22.0(jiti@2.4.2))
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.22.0(jiti@2.4.2))
|
||||
eslint-plugin-react: 7.37.2(eslint@9.22.0(jiti@2.4.2))
|
||||
@@ -20840,22 +20940,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.22.0(jiti@2.4.2)))(eslint@9.22.0(jiti@2.4.2)):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.0
|
||||
enhanced-resolve: 5.18.1
|
||||
eslint: 9.22.0(jiti@2.4.2)
|
||||
fast-glob: 3.3.3
|
||||
get-tsconfig: 4.10.0
|
||||
is-bun-module: 1.3.0
|
||||
is-glob: 4.0.3
|
||||
stable-hash: 0.0.4
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.22.0(jiti@2.4.2))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@9.16.0(jiti@2.4.2)):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
@@ -20884,7 +20968,7 @@ snapshots:
|
||||
is-glob: 4.0.3
|
||||
stable-hash: 0.0.4
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.22.0(jiti@2.4.2))
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.22.0(jiti@2.4.2))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -20910,14 +20994,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.30.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.22.0(jiti@2.4.2)))(eslint@9.22.0(jiti@2.4.2)))(eslint@9.22.0(jiti@2.4.2)):
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.30.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.22.0(jiti@2.4.2)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.30.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3)
|
||||
eslint: 9.22.0(jiti@2.4.2)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.22.0(jiti@2.4.2)))(eslint@9.22.0(jiti@2.4.2))
|
||||
eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@9.22.0(jiti@2.4.2))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -20990,7 +21074,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.22.0(jiti@2.4.2)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.30.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.30.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.22.0(jiti@2.4.2)))(eslint@9.22.0(jiti@2.4.2)))(eslint@9.22.0(jiti@2.4.2))
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.30.1(eslint@9.22.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.22.0(jiti@2.4.2))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
@@ -21452,6 +21536,10 @@ snapshots:
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.2
|
||||
|
||||
fdir@6.4.4(picomatch@4.0.2):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.2
|
||||
|
||||
fecha@4.2.3: {}
|
||||
|
||||
fetch-h2@3.0.2:
|
||||
@@ -26994,6 +27082,11 @@ snapshots:
|
||||
fdir: 6.4.3(picomatch@4.0.2)
|
||||
picomatch: 4.0.2
|
||||
|
||||
tinyglobby@0.2.13:
|
||||
dependencies:
|
||||
fdir: 6.4.4(picomatch@4.0.2)
|
||||
picomatch: 4.0.2
|
||||
|
||||
tinypool@1.0.2: {}
|
||||
|
||||
tinyrainbow@1.2.0: {}
|
||||
@@ -27623,15 +27716,16 @@ snapshots:
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
vite-node@3.1.1(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0):
|
||||
vite-node@3.1.1(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.0
|
||||
es-module-lexer: 1.6.0
|
||||
pathe: 2.0.3
|
||||
vite: 5.4.16(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0)
|
||||
vite: 6.3.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- sass
|
||||
@@ -27640,10 +27734,12 @@ snapshots:
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vite-plugin-wasm@3.4.1(vite@5.4.16(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0)):
|
||||
vite-plugin-wasm@3.4.1(vite@6.3.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)):
|
||||
dependencies:
|
||||
vite: 5.4.16(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0)
|
||||
vite: 6.3.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
|
||||
|
||||
vite@5.4.16(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0):
|
||||
dependencies:
|
||||
@@ -27681,6 +27777,23 @@ snapshots:
|
||||
tsx: 4.19.3
|
||||
yaml: 2.7.1
|
||||
|
||||
vite@6.3.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1):
|
||||
dependencies:
|
||||
esbuild: 0.25.2
|
||||
fdir: 6.4.4(picomatch@4.0.2)
|
||||
picomatch: 4.0.2
|
||||
postcss: 8.5.3
|
||||
rollup: 4.38.0
|
||||
tinyglobby: 0.2.13
|
||||
optionalDependencies:
|
||||
'@types/node': 22.14.1
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.4.2
|
||||
lightningcss: 1.29.3
|
||||
terser: 5.39.0
|
||||
tsx: 4.19.3
|
||||
yaml: 2.7.1
|
||||
|
||||
vitest@2.1.0(@edge-runtime/vm@5.0.0)(@types/node@22.14.1)(happy-dom@17.4.4)(lightningcss@1.29.3)(msw@2.7.4(@types/node@22.14.1)(typescript@5.8.3))(terser@5.39.0):
|
||||
dependencies:
|
||||
'@vitest/expect': 2.1.0
|
||||
@@ -27865,10 +27978,10 @@ snapshots:
|
||||
- supports-color
|
||||
- terser
|
||||
|
||||
vitest@3.1.1(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.4)(lightningcss@1.29.3)(msw@2.7.4(@types/node@22.14.1)(typescript@5.7.3))(terser@5.39.0):
|
||||
vitest@3.1.1(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.4)(jiti@2.4.2)(lightningcss@1.29.3)(msw@2.7.4(@types/node@22.14.1)(typescript@5.7.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1):
|
||||
dependencies:
|
||||
'@vitest/expect': 3.1.1
|
||||
'@vitest/mocker': 3.1.1(msw@2.7.4(@types/node@22.14.1)(typescript@5.7.3))(vite@5.4.16(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0))
|
||||
'@vitest/mocker': 3.1.1(msw@2.7.4(@types/node@22.14.1)(typescript@5.7.3))(vite@6.3.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))
|
||||
'@vitest/pretty-format': 3.1.1
|
||||
'@vitest/runner': 3.1.1
|
||||
'@vitest/snapshot': 3.1.1
|
||||
@@ -27884,8 +27997,8 @@ snapshots:
|
||||
tinyexec: 0.3.2
|
||||
tinypool: 1.0.2
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 5.4.16(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0)
|
||||
vite-node: 3.1.1(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0)
|
||||
vite: 6.3.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
|
||||
vite-node: 3.1.1(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@edge-runtime/vm': 5.0.0
|
||||
@@ -27893,6 +28006,7 @@ snapshots:
|
||||
'@types/node': 22.14.1
|
||||
happy-dom: 17.4.4
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- msw
|
||||
@@ -27902,11 +28016,13 @@ snapshots:
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vitest@3.1.1(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.4)(lightningcss@1.29.3)(msw@2.7.4(@types/node@22.14.1)(typescript@5.8.3))(terser@5.39.0):
|
||||
vitest@3.1.1(@edge-runtime/vm@5.0.0)(@types/debug@4.1.12)(@types/node@22.14.1)(happy-dom@17.4.4)(jiti@2.4.2)(lightningcss@1.29.3)(msw@2.7.4(@types/node@22.14.1)(typescript@5.8.3))(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1):
|
||||
dependencies:
|
||||
'@vitest/expect': 3.1.1
|
||||
'@vitest/mocker': 3.1.1(msw@2.7.4(@types/node@22.14.1)(typescript@5.8.3))(vite@5.4.16(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0))
|
||||
'@vitest/mocker': 3.1.1(msw@2.7.4(@types/node@22.14.1)(typescript@5.8.3))(vite@6.3.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))
|
||||
'@vitest/pretty-format': 3.1.1
|
||||
'@vitest/runner': 3.1.1
|
||||
'@vitest/snapshot': 3.1.1
|
||||
@@ -27922,8 +28038,8 @@ snapshots:
|
||||
tinyexec: 0.3.2
|
||||
tinypool: 1.0.2
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 5.4.16(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0)
|
||||
vite-node: 3.1.1(@types/node@22.14.1)(lightningcss@1.29.3)(terser@5.39.0)
|
||||
vite: 6.3.3(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
|
||||
vite-node: 3.1.1(@types/node@22.14.1)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@edge-runtime/vm': 5.0.0
|
||||
@@ -27931,6 +28047,7 @@ snapshots:
|
||||
'@types/node': 22.14.1
|
||||
happy-dom: 17.4.4
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- msw
|
||||
@@ -27940,6 +28057,8 @@ snapshots:
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
voyageai@0.0.3-1:
|
||||
dependencies:
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import { StartEvent, StopEvent, Workflow, WorkflowEvent } from "llamaindex";
|
||||
import type { ReactNode } from "react";
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
describe("workflow integration", () => {
|
||||
type Context = {
|
||||
pending: string[];
|
||||
};
|
||||
type Start = string;
|
||||
type Stop = ReactNode;
|
||||
|
||||
test("nodejs", async () => {
|
||||
const workflow = new Workflow<never, Start, Stop>({
|
||||
wait: async () => await new Promise((resolve) => setTimeout(resolve, 0)),
|
||||
});
|
||||
workflow.addStep(
|
||||
{
|
||||
inputs: [StartEvent],
|
||||
outputs: [StopEvent],
|
||||
},
|
||||
async (_, __) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
return new StopEvent("hello");
|
||||
},
|
||||
);
|
||||
|
||||
console.log("start");
|
||||
|
||||
const run = workflow.run("start");
|
||||
await run.then((stop) => {
|
||||
expect(stop.data).toBe("hello");
|
||||
});
|
||||
});
|
||||
|
||||
test("with jsx", async () => {
|
||||
const workflow = new Workflow<never, Start, Stop>();
|
||||
workflow.addStep(
|
||||
{
|
||||
inputs: [StartEvent],
|
||||
outputs: [StopEvent],
|
||||
},
|
||||
async (_, __) => {
|
||||
return new StopEvent(<div>Hey there!</div>);
|
||||
},
|
||||
);
|
||||
|
||||
const run = workflow.run("start");
|
||||
const stop = await run;
|
||||
expect(stop.data).toEqual(<div>Hey there!</div>);
|
||||
});
|
||||
|
||||
test("with message channel", async () => {
|
||||
const workflow = new Workflow<Context, Start, Stop>();
|
||||
|
||||
class AnalysisStartEvent extends WorkflowEvent<string> {}
|
||||
|
||||
class AnalysisStopEvent extends WorkflowEvent<string> {}
|
||||
|
||||
workflow.addStep(
|
||||
{
|
||||
inputs: [StartEvent],
|
||||
outputs: [StopEvent],
|
||||
},
|
||||
async ({ data, sendEvent, requireEvent }) => {
|
||||
data.pending.push("analyzing");
|
||||
sendEvent(new AnalysisStartEvent("analysis my document"));
|
||||
const event = await requireEvent(AnalysisStopEvent);
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
data.pending.push("analysis complete");
|
||||
return new StopEvent(event.data);
|
||||
},
|
||||
);
|
||||
|
||||
workflow.addStep(
|
||||
{
|
||||
inputs: [AnalysisStartEvent],
|
||||
outputs: [AnalysisStopEvent],
|
||||
},
|
||||
async ({ data }) => {
|
||||
data.pending.push("loading document");
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
data.pending.push("document loaded");
|
||||
return new AnalysisStopEvent("analysis complete");
|
||||
},
|
||||
);
|
||||
|
||||
const run = workflow.run("start").with({
|
||||
pending: [],
|
||||
});
|
||||
await run;
|
||||
expect(run.data.pending).toEqual([
|
||||
"analyzing",
|
||||
"loading document",
|
||||
"document loaded",
|
||||
"analysis complete",
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user