@minded-ai/mindedjs 1.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/.github/workflows/CI.yml +34 -0
  2. package/.prettierrc +8 -0
  3. package/README.md +6 -0
  4. package/dist/agent.d.ts +36 -0
  5. package/dist/agent.js +199 -0
  6. package/dist/agent.js.map +1 -0
  7. package/dist/analytics.d.ts +6 -0
  8. package/dist/analytics.js +19 -0
  9. package/dist/analytics.js.map +1 -0
  10. package/dist/edges/createDirectEdge.d.ts +4 -0
  11. package/dist/edges/createDirectEdge.js +14 -0
  12. package/dist/edges/createDirectEdge.js.map +1 -0
  13. package/dist/edges/createLogicalRouter.d.ts +5 -0
  14. package/dist/edges/createLogicalRouter.js +18 -0
  15. package/dist/edges/createLogicalRouter.js.map +1 -0
  16. package/dist/edges/createPromptRouter.d.ts +7 -0
  17. package/dist/edges/createPromptRouter.js +54 -0
  18. package/dist/edges/createPromptRouter.js.map +1 -0
  19. package/dist/edges/edgeFactory.d.ts +9 -0
  20. package/dist/edges/edgeFactory.js +65 -0
  21. package/dist/edges/edgeFactory.js.map +1 -0
  22. package/dist/events/AgentEvents.d.ts +22 -0
  23. package/dist/events/AgentEvents.js +9 -0
  24. package/dist/events/AgentEvents.js.map +1 -0
  25. package/dist/events/index.d.ts +2 -0
  26. package/dist/events/index.js +5 -0
  27. package/dist/events/index.js.map +1 -0
  28. package/dist/index.d.ts +6 -0
  29. package/dist/index.js +15 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/infrastructure.ts/mindedRequest.d.ts +8 -0
  32. package/dist/infrastructure.ts/mindedRequest.js +22 -0
  33. package/dist/infrastructure.ts/mindedRequest.js.map +1 -0
  34. package/dist/llm/createLlmInstance.d.ts +2 -0
  35. package/dist/llm/createLlmInstance.js +14 -0
  36. package/dist/llm/createLlmInstance.js.map +1 -0
  37. package/dist/nodes/addHumanInTheLoopNode.d.ts +8 -0
  38. package/dist/nodes/addHumanInTheLoopNode.js +17 -0
  39. package/dist/nodes/addHumanInTheLoopNode.js.map +1 -0
  40. package/dist/nodes/addPromptNode.d.ts +15 -0
  41. package/dist/nodes/addPromptNode.js +52 -0
  42. package/dist/nodes/addPromptNode.js.map +1 -0
  43. package/dist/nodes/addToolNode.d.ts +10 -0
  44. package/dist/nodes/addToolNode.js +82 -0
  45. package/dist/nodes/addToolNode.js.map +1 -0
  46. package/dist/nodes/addTriggerNode.d.ts +6 -0
  47. package/dist/nodes/addTriggerNode.js +12 -0
  48. package/dist/nodes/addTriggerNode.js.map +1 -0
  49. package/dist/nodes/nodeFactory.d.ts +13 -0
  50. package/dist/nodes/nodeFactory.js +41 -0
  51. package/dist/nodes/nodeFactory.js.map +1 -0
  52. package/dist/platform/analytics.d.ts +6 -0
  53. package/dist/platform/analytics.js +19 -0
  54. package/dist/platform/analytics.js.map +1 -0
  55. package/dist/platform/mindedCheckpointSaver.d.ts +10 -0
  56. package/dist/platform/mindedCheckpointSaver.js +49 -0
  57. package/dist/platform/mindedCheckpointSaver.js.map +1 -0
  58. package/dist/platform/mindedConnection.d.ts +13 -0
  59. package/dist/platform/mindedConnection.js +117 -0
  60. package/dist/platform/mindedConnection.js.map +1 -0
  61. package/dist/platform/mindedConnectionTypes.d.ts +10 -0
  62. package/dist/platform/mindedConnectionTypes.js +8 -0
  63. package/dist/platform/mindedConnectionTypes.js.map +1 -0
  64. package/dist/platform/mindedRequest.d.ts +8 -0
  65. package/dist/platform/mindedRequest.js +22 -0
  66. package/dist/platform/mindedRequest.js.map +1 -0
  67. package/dist/types/Agent.types.d.ts +8 -0
  68. package/dist/types/Agent.types.js +3 -0
  69. package/dist/types/Agent.types.js.map +1 -0
  70. package/dist/types/Flows.types.d.ts +83 -0
  71. package/dist/types/Flows.types.js +24 -0
  72. package/dist/types/Flows.types.js.map +1 -0
  73. package/dist/types/LLM.types.d.ts +10 -0
  74. package/dist/types/LLM.types.js +9 -0
  75. package/dist/types/LLM.types.js.map +1 -0
  76. package/dist/types/LangGraph.types.d.ts +29 -0
  77. package/dist/types/LangGraph.types.js +20 -0
  78. package/dist/types/LangGraph.types.js.map +1 -0
  79. package/dist/types/Tools.types.d.ts +14 -0
  80. package/dist/types/Tools.types.js +3 -0
  81. package/dist/types/Tools.types.js.map +1 -0
  82. package/dist/types/Triggers.types.d.ts +1 -0
  83. package/dist/types/Triggers.types.js +3 -0
  84. package/dist/types/Triggers.types.js.map +1 -0
  85. package/docs/.gitbook/assets/image.png +0 -0
  86. package/docs/README.md +51 -0
  87. package/docs/SUMMARY.md +21 -0
  88. package/docs/api-reference/.nojekyll +1 -0
  89. package/docs/api-reference/assets/hierarchy.js +1 -0
  90. package/docs/api-reference/assets/highlight.css +120 -0
  91. package/docs/api-reference/assets/icons.js +18 -0
  92. package/docs/api-reference/assets/icons.svg +1 -0
  93. package/docs/api-reference/assets/main.js +60 -0
  94. package/docs/api-reference/assets/navigation.js +1 -0
  95. package/docs/api-reference/assets/search.js +1 -0
  96. package/docs/api-reference/assets/style.css +1640 -0
  97. package/docs/api-reference/classes/index.Agent.html +4 -0
  98. package/docs/api-reference/enums/index.EdgeType.html +4 -0
  99. package/docs/api-reference/enums/index.NodeType.html +6 -0
  100. package/docs/api-reference/enums/index.TriggerType.html +4 -0
  101. package/docs/api-reference/enums/index.events.html +3 -0
  102. package/docs/api-reference/hierarchy.html +1 -0
  103. package/docs/api-reference/index.html +310 -0
  104. package/docs/api-reference/interfaces/index.AppToolNode.html +5 -0
  105. package/docs/api-reference/interfaces/index.AppTriggerNode.html +6 -0
  106. package/docs/api-reference/interfaces/index.Flow.html +4 -0
  107. package/docs/api-reference/interfaces/index.JunctionNode.html +4 -0
  108. package/docs/api-reference/interfaces/index.LogicalConditionEdge.html +5 -0
  109. package/docs/api-reference/interfaces/index.ManualTriggerNode.html +5 -0
  110. package/docs/api-reference/interfaces/index.PromptConditionEdge.html +5 -0
  111. package/docs/api-reference/interfaces/index.PromptNode.html +6 -0
  112. package/docs/api-reference/interfaces/index.StepForwardEdge.html +4 -0
  113. package/docs/api-reference/interfaces/index.Tool.html +6 -0
  114. package/docs/api-reference/interfaces/index.ToolNode.html +5 -0
  115. package/docs/api-reference/modules/index-1.html +1 -0
  116. package/docs/api-reference/modules/index.html +1 -0
  117. package/docs/api-reference/modules.html +1 -0
  118. package/docs/api-reference/types/index.Edge.html +1 -0
  119. package/docs/api-reference/types/index.Node.html +1 -0
  120. package/docs/api-reference/types/index.TriggerNode.html +1 -0
  121. package/docs/core-concepts/edges.md +242 -0
  122. package/docs/core-concepts/events.md +161 -0
  123. package/docs/core-concepts/flows.md +74 -0
  124. package/docs/core-concepts/memory-types.md +208 -0
  125. package/docs/core-concepts/nodes.md +239 -0
  126. package/docs/core-concepts/tools.md +205 -0
  127. package/docs/examples/order-refund-flow.md +560 -0
  128. package/docs/getting-started/installation.md +34 -0
  129. package/docs/getting-started/quick-start.md +264 -0
  130. package/docs-structure.md +144 -0
  131. package/eslint.config.js +68 -0
  132. package/examples/orderRefundAgent/flows/orderRefundFlow.yaml +32 -0
  133. package/examples/orderRefundAgent/minded.json +14 -0
  134. package/examples/orderRefundAgent/orderRefundAgent.ts +58 -0
  135. package/examples/orderRefundAgent/schema.ts +7 -0
  136. package/examples/orderRefundAgent/tools/escalateConversation.ts +28 -0
  137. package/examples/orderRefundAgent/tools/index.ts +4 -0
  138. package/examples/orderRefundAgent/tools/refundOrder.ts +27 -0
  139. package/package.json +46 -0
  140. package/src/agent.ts +216 -0
  141. package/src/edges/createDirectEdge.ts +11 -0
  142. package/src/edges/createLogicalRouter.ts +16 -0
  143. package/src/edges/createPromptRouter.ts +52 -0
  144. package/src/edges/edgeFactory.ts +85 -0
  145. package/src/events/AgentEvents.ts +22 -0
  146. package/src/events/index.ts +3 -0
  147. package/src/index.ts +22 -0
  148. package/src/llm/createLlmInstance.ts +10 -0
  149. package/src/nodes/addHumanInTheLoopNode.ts +20 -0
  150. package/src/nodes/addPromptNode.ts +66 -0
  151. package/src/nodes/addToolNode.ts +95 -0
  152. package/src/nodes/addTriggerNode.ts +12 -0
  153. package/src/nodes/nodeFactory.ts +65 -0
  154. package/src/platform/analytics.ts +16 -0
  155. package/src/platform/mindedCheckpointSaver.ts +74 -0
  156. package/src/platform/mindedConnection.ts +106 -0
  157. package/src/platform/mindedConnectionTypes.ts +15 -0
  158. package/src/platform/mindedRequest.ts +28 -0
  159. package/src/types/Agent.types.ts +10 -0
  160. package/src/types/Flows.types.ts +103 -0
  161. package/src/types/LLM.types.ts +13 -0
  162. package/src/types/LangGraph.types.ts +25 -0
  163. package/src/types/Tools.types.ts +9 -0
  164. package/test/can-stay-on-node/can-stay-on-node.test.ts +148 -0
  165. package/test/can-stay-on-node/flows/test-flow.yaml +25 -0
  166. package/test/cannot-stay-on-node/cannot-stay-on-node.test.ts +201 -0
  167. package/test/cannot-stay-on-node/flows/test-flow.yaml +34 -0
  168. package/test/human-in-the-loop-node/flows/test-flow.yaml +23 -0
  169. package/test/human-in-the-loop-node/human-in-the-loop-node.test.ts +92 -0
  170. package/test/logical-edges/flows/logical-edge-test-flow.yaml +24 -0
  171. package/test/logical-edges/logical-edges.test.ts +66 -0
  172. package/test/no-human-in-the-loop-node/flows/test-flow.yaml +23 -0
  173. package/test/no-human-in-the-loop-node/no-human-in-the-loop-node.test.ts +80 -0
  174. package/test/prompt-edges/flows/test-flow.yaml +24 -0
  175. package/test/prompt-edges/prompt-edges.test.ts +90 -0
  176. package/test/prompt-node/flows/test-flow.yaml +24 -0
  177. package/test/prompt-node/prompt-node.test.ts +86 -0
  178. package/test/setup.ts +5 -0
  179. package/test/tool-node/flows/test-flow.yaml +14 -0
  180. package/test/tool-node/tool-node.test.ts +67 -0
  181. package/test/trigger/flows/test-flow.yaml +7 -0
  182. package/test/trigger/trigger.test.ts +57 -0
  183. package/tsconfig.json +17 -0
package/src/agent.ts ADDED
@@ -0,0 +1,216 @@
1
+ import { Flow, NodeType, Node } from './types/Flows.types';
2
+ import { Tool } from './types/Tools.types';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ import { BaseLanguageModel } from '@langchain/core/language_models/base';
5
+ import { Command, StateGraph } from '@langchain/langgraph';
6
+ import { MemorySaver, BaseCheckpointSaver } from '@langchain/langgraph';
7
+ import { nodeFactory } from './nodes/nodeFactory';
8
+ import { CompiledGraph, PreCompiledGraph, stateAnnotation } from './types/LangGraph.types';
9
+ import { edgeFactory } from './edges/edgeFactory';
10
+ import { AgentEventRequestPayloads, AgentEventResponsePayloads, AgentEvents } from './events/AgentEvents';
11
+ import { z } from 'zod';
12
+
13
+ import { MindedConnection } from './platform/mindedConnection';
14
+ import { MindedConnectionSocketMessageType } from './platform/mindedConnectionTypes';
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+ import * as yaml from 'js-yaml';
18
+ import { MindedSDKConfig } from './types/Agent.types';
19
+ import { createLlmInstance } from './llm/createLlmInstance';
20
+ import { config } from 'dotenv';
21
+ import { resolve } from 'path';
22
+
23
+ config({ path: resolve(__dirname, '../.env') });
24
+
25
+ type CreateAgentParams<Memory> = {
26
+ memorySchema: z.ZodSchema;
27
+ config: MindedSDKConfig;
28
+ tools: Tool<any, Memory>[];
29
+ platformToken?: string;
30
+ memorySaver?: BaseCheckpointSaver;
31
+ };
32
+ export class Agent {
33
+ private memorySchema: z.ZodSchema;
34
+ private flows: Flow[];
35
+ private tools: Tool<any, any>[];
36
+ private platformToken?: string;
37
+ private llm: BaseLanguageModel;
38
+ private mindedConnection: MindedConnection | null = null;
39
+ // Langgraph memory saver. In memory for local development, Custom for Platform
40
+ private checkpointer: BaseCheckpointSaver;
41
+ // Langgraph compiled graph
42
+ private compiledGraph: CompiledGraph;
43
+
44
+ // Event handlers storage keyed by event name. Handlers can optionally return a value.
45
+ private eventHandlers: {
46
+ [K in keyof AgentEventRequestPayloads<z.infer<typeof this.memorySchema>>]?: Array<
47
+ (payload: AgentEventRequestPayloads<z.infer<typeof this.memorySchema>>[K]) => any | Promise<any>
48
+ >;
49
+ } = {};
50
+
51
+ constructor(params: CreateAgentParams<z.infer<typeof this.memorySchema>>) {
52
+ const { config, tools, platformToken, memorySaver, memorySchema } = params;
53
+ this.memorySchema = memorySchema;
54
+ this.flows = this.loadFlowsFromDirectory(config.flows);
55
+ this.tools = tools;
56
+ this.platformToken = platformToken;
57
+ this.llm = createLlmInstance(config.llm);
58
+ this.checkpointer = memorySaver || new MemorySaver();
59
+ this.validate();
60
+ this.compiledGraph = this.initializeGraph();
61
+
62
+ if (process.env.NODE_ENV !== 'test' && process.env.NO_PLATFORM_CONNECT !== 'true') {
63
+ this.mindedConnection = new MindedConnection();
64
+ this.mindedConnection.start();
65
+ this.mindedConnection.on(MindedConnectionSocketMessageType.OnAppTrigger, (trigger) => {
66
+ console.log('Received trigger:', trigger);
67
+ });
68
+ }
69
+ }
70
+
71
+ private loadFlowsFromDirectory(flowsDirectories: string[]): Flow[] {
72
+ const flows: Flow[] = [];
73
+ for (const flowsDirectory of flowsDirectories) {
74
+ if (!fs.existsSync(flowsDirectory)) {
75
+ throw new Error(`Flows directory does not exist: ${flowsDirectory}`);
76
+ }
77
+
78
+ const files = fs.readdirSync(flowsDirectory);
79
+
80
+ for (const file of files) {
81
+ if (file.endsWith('.yaml') || file.endsWith('.yml')) {
82
+ const filePath = path.join(flowsDirectory, file);
83
+ try {
84
+ const fileContent = fs.readFileSync(filePath, 'utf8');
85
+ const parsedFlow = yaml.load(fileContent) as Flow;
86
+
87
+ // Validate that the parsed flow has the required structure
88
+ if (!parsedFlow.name || !parsedFlow.nodes || !parsedFlow.edges) {
89
+ throw new Error(`Invalid flow structure in ${file}. Flow must have name, nodes, and edges.`);
90
+ }
91
+
92
+ flows.push(parsedFlow);
93
+ } catch (error) {
94
+ throw new Error(`Failed to parse flow file ${file}: ${error}`);
95
+ }
96
+ }
97
+ }
98
+
99
+ if (flows.length === 0) {
100
+ throw new Error(`No YAML flow files found in directory: ${flowsDirectory}`);
101
+ }
102
+ }
103
+
104
+ return flows;
105
+ }
106
+
107
+ private validate() {
108
+ if (this.flows.length === 0) {
109
+ throw new Error('No flows provided');
110
+ }
111
+ }
112
+
113
+ private initializeGraph(): CompiledGraph {
114
+ const nodes = this.flows.flatMap((flow) => flow.nodes);
115
+ const edges = this.flows.flatMap((flow) => flow.edges);
116
+
117
+ // Initialize the graph
118
+ const graph = new StateGraph(stateAnnotation).addNode('init', this.init.bind(this)).addEdge('__start__', 'init') as PreCompiledGraph;
119
+
120
+ // Add nodes and create nodes object
121
+ const nodesObject: { [key: string]: Node } = {};
122
+ nodes.forEach((node) => {
123
+ // Add to nodes object
124
+ nodesObject[node.name] = node;
125
+ // Add to graph
126
+ nodeFactory({
127
+ graph,
128
+ node,
129
+ tools: this.tools,
130
+ llm: this.llm,
131
+ emit: this.emit.bind(this),
132
+ });
133
+ });
134
+
135
+ // Add edges
136
+ edgeFactory({ graph, edges, nodes: nodesObject, llm: this.llm });
137
+ nodes.forEach((node) => {
138
+ if (node.type === NodeType.TRIGGER) {
139
+ graph.addEdge('__start__', node.name as any);
140
+ }
141
+ });
142
+
143
+ // Compile the graph
144
+ return graph.compile({ checkpointer: this.checkpointer }) as CompiledGraph;
145
+ }
146
+
147
+ private init(): typeof stateAnnotation.State {
148
+ return {
149
+ messages: [],
150
+ memory: {} as z.infer<typeof this.memorySchema>,
151
+ triggerMetadata: null,
152
+ interruptedNode: null,
153
+ };
154
+ }
155
+
156
+ public async invoke({ triggerBody, triggerName, sessionId }: { triggerBody: any; triggerName: string; sessionId?: string }) {
157
+ const session = sessionId || uuidv4();
158
+ const results = await this.emit(AgentEvents.TRIGGER_EVENT, {
159
+ triggerName,
160
+ triggerBody,
161
+ });
162
+ const handlerResult = results.find((r) => r !== undefined);
163
+ if (handlerResult === false) {
164
+ console.log(`Disqualified trigger ${triggerName}`);
165
+ throw new Error('Disqualified trigger');
166
+ }
167
+ const memoryUpdate = handlerResult?.memory || {};
168
+ const messages = handlerResult?.messages ?? [];
169
+ console.log(`Invoking trigger ${triggerName} with session ${session}`);
170
+ const config = { configurable: { thread_id: session, recursionLimit: 3 } };
171
+ const triggerNode = this.compiledGraph.builder.nodes[triggerName];
172
+ if (!triggerNode) {
173
+ throw new Error(`Trigger node not found: ${triggerName}`);
174
+ }
175
+ const state = await this.compiledGraph.getState(config);
176
+ // Resume interruption
177
+ if (state.tasks?.[0]?.interrupts?.length > 0) {
178
+ const res = await this.compiledGraph.invoke(
179
+ new Command({
180
+ resume: { memory: memoryUpdate, messages },
181
+ }),
182
+ config,
183
+ );
184
+ return res;
185
+ } else {
186
+ const res = await this.compiledGraph.invoke({ messages, memory: memoryUpdate }, config);
187
+ return res;
188
+ }
189
+ }
190
+
191
+ // Public API for registering event listeners
192
+ public on<E extends keyof AgentEventRequestPayloads<z.infer<typeof this.memorySchema>>>(
193
+ event: E,
194
+ handler: (
195
+ payload: AgentEventRequestPayloads<z.infer<typeof this.memorySchema>>[E],
196
+ ) => Promise<AgentEventResponsePayloads<z.infer<typeof this.memorySchema>>[E] | void>,
197
+ ): void {
198
+ if (!this.eventHandlers[event]) {
199
+ this.eventHandlers[event] = [];
200
+ }
201
+ // We can safely cast here since we ensured the array exists
202
+ (this.eventHandlers[event] as Array<typeof handler>).push(handler);
203
+ }
204
+
205
+ // Internal method to emit events to the registered listeners
206
+ private async emit<E extends keyof AgentEventRequestPayloads<z.infer<typeof this.memorySchema>>>(
207
+ event: E,
208
+ payload: AgentEventRequestPayloads<z.infer<typeof this.memorySchema>>[E],
209
+ ): Promise<AgentEventResponsePayloads<z.infer<typeof this.memorySchema>>[E][]> {
210
+ if (!this.eventHandlers[event]) {
211
+ return [];
212
+ }
213
+ const results = await Promise.all(this.eventHandlers[event]!.map(async (handler) => handler(payload)));
214
+ return results;
215
+ }
216
+ }
@@ -0,0 +1,11 @@
1
+ import { StepForwardEdge } from '../types/Flows.types';
2
+
3
+ export const createDirectEdge = ({ edges }: { edges: StepForwardEdge[] }) => {
4
+ return async () => {
5
+ // For direct edges, we just return the target of the first edge
6
+ // since there's no conditional logic needed
7
+ const edge = edges[0];
8
+ console.log(`Executing edge ${JSON.stringify(edge)}`);
9
+ return edge.target;
10
+ };
11
+ };
@@ -0,0 +1,16 @@
1
+ import { LogicalConditionEdge } from '../types/Flows.types';
2
+ import { stateAnnotation } from '../types/LangGraph.types';
3
+
4
+ export const createLogicalRouter = ({ edges }: { edges: LogicalConditionEdge[] }) => {
5
+ return async (state: typeof stateAnnotation.State) => {
6
+ console.log(`Executing logical router. Edges: ${JSON.stringify(edges)}`);
7
+ for (const edge of edges) {
8
+ const conditionFn = eval(edge.condition);
9
+ const result = await conditionFn({ memory: state.memory });
10
+ if (result) {
11
+ return edge.target;
12
+ }
13
+ }
14
+ return edges[0].source;
15
+ };
16
+ };
@@ -0,0 +1,52 @@
1
+ import { createReactAgent } from '@langchain/langgraph/prebuilt';
2
+ import { z } from 'zod';
3
+ import { PromptConditionEdge } from '../types/Flows.types';
4
+ import { BaseLanguageModel } from '@langchain/core/language_models/base';
5
+ import { AIMessage } from '@langchain/core/messages';
6
+ import { stateAnnotation } from '../types/LangGraph.types';
7
+
8
+ export const createPromptRouter = ({ edges, llm }: { edges: PromptConditionEdge[]; llm: BaseLanguageModel }) => {
9
+ const agent = createReactAgent({
10
+ llm,
11
+ tools: [],
12
+ responseFormat: z.object({
13
+ nextNodeId: z.string(),
14
+ reasoning: z.string(),
15
+ }),
16
+ });
17
+ return async (state: typeof stateAnnotation.State) => {
18
+ console.log(`Executing prompt router. Edges: ${JSON.stringify(edges)}`);
19
+ const result = await agent.invoke({
20
+ messages: [
21
+ new AIMessage({
22
+ content: `You are a router that decides which node to take in a flow based on the current memory state, history and available node ids.
23
+
24
+ Your task is to analyze the current memory state, history and available nodes and determine the most appropriate next node in the flow, the returned nodeId should be only the id without the prompt.
25
+
26
+
27
+ Available nodes in the format of {nodeId}: {prompt}:
28
+ ${edges.map((edge) => `- ${edge.target}: ${edge.prompt}`).join('\n')}
29
+
30
+ Current memory state:
31
+ ${JSON.stringify(state.memory)}
32
+
33
+ Previous messages:
34
+ ${JSON.stringify(state.messages)}
35
+
36
+ Based on the memory state and available nodes, determine the most appropriate next node id.
37
+ `,
38
+ }),
39
+ ],
40
+ });
41
+ try {
42
+ const nextNode = result.structuredResponse.nextNodeId;
43
+ console.log(`Next node: ${nextNode}`);
44
+ if (!nextNode) {
45
+ throw new Error('No next node id returned from router');
46
+ }
47
+ return nextNode;
48
+ } catch (e: any) {
49
+ throw new Error(`Failed to parse router response: ${result.structuredResponse}; Error: ${e.message}`);
50
+ }
51
+ };
52
+ };
@@ -0,0 +1,85 @@
1
+ import { Edge, EdgeType, Node, PromptConditionEdge, LogicalConditionEdge, StepForwardEdge } from '../types/Flows.types';
2
+ import { PreCompiledGraph } from '../types/LangGraph.types';
3
+ import { BaseLanguageModel } from '@langchain/core/language_models/base';
4
+ import { createPromptRouter } from './createPromptRouter';
5
+ import { createLogicalRouter } from './createLogicalRouter';
6
+ import { createDirectEdge } from './createDirectEdge';
7
+ import { addHumanInTheLoopNode, buildHumanInTheLoopNodeName } from '../nodes/addHumanInTheLoopNode';
8
+
9
+ type EdgeProcessor<T extends Edge> = {
10
+ edges: T[];
11
+ router: (params: { edges: T[]; llm: BaseLanguageModel }) => any;
12
+ };
13
+
14
+ export const edgeFactory = ({
15
+ graph,
16
+ edges,
17
+ nodes,
18
+ llm,
19
+ }: {
20
+ graph: PreCompiledGraph;
21
+ edges: Edge[];
22
+ nodes: Record<string, Node>;
23
+ llm: BaseLanguageModel;
24
+ }) => {
25
+ /**
26
+ * Processes edges with enhanced node capabilities (humanInTheLoop, canStayOnNode)
27
+ * This elegant abstraction handles both prompt and logical edges uniformly
28
+ */
29
+ const processEdges = <T extends Edge>(edgeProcessor: EdgeProcessor<T>): void => {
30
+ const { edges: filteredEdges, router } = edgeProcessor;
31
+ const uniqueSources = [...new Set(filteredEdges.map((edge) => edge.source))];
32
+
33
+ uniqueSources.forEach((source) => {
34
+ const edgesForSource: T[] = [...filteredEdges.filter((edge) => edge.source === source)];
35
+ let effectiveSource = source;
36
+
37
+ // Handle human-in-the-loop enhancement
38
+ if (nodes[source]?.humanInTheLoop) {
39
+ addHumanInTheLoopNode({ graph, attachedToNodeName: source });
40
+ effectiveSource = buildHumanInTheLoopNodeName(source);
41
+ }
42
+
43
+ // Handle stay-on-node capability
44
+ if (nodes[source]?.canStayOnNode) {
45
+ const stayEdge = {
46
+ source,
47
+ target: source,
48
+ type: EdgeType.PROMPT_CONDITION,
49
+ prompt: 'stay at current node if still relevant',
50
+ } as T;
51
+
52
+ edgesForSource.push(stayEdge);
53
+ }
54
+
55
+ graph.addConditionalEdges(effectiveSource as any, router({ edges: edgesForSource, llm }));
56
+ });
57
+ };
58
+
59
+ // Direct edges - simple and straightforward
60
+ const directEdges = edges.filter((edge) => edge.type === EdgeType.STEP_FORWARD) as StepForwardEdge[];
61
+ if (directEdges.length > 0) {
62
+ processEdges({
63
+ edges: directEdges,
64
+ router: createDirectEdge,
65
+ });
66
+ }
67
+
68
+ // Prompt condition edges - enhanced with AI-driven routing
69
+ const promptEdges = edges.filter((edge) => edge.type === EdgeType.PROMPT_CONDITION) as PromptConditionEdge[];
70
+ if (promptEdges.length > 0) {
71
+ processEdges({
72
+ edges: promptEdges,
73
+ router: createPromptRouter,
74
+ });
75
+ }
76
+
77
+ // Logical condition edges - enhanced with programmatic routing
78
+ const logicalEdges = edges.filter((edge) => edge.type === EdgeType.LOGICAL_CONDITION) as LogicalConditionEdge[];
79
+ if (logicalEdges.length > 0) {
80
+ processEdges({
81
+ edges: logicalEdges,
82
+ router: createLogicalRouter,
83
+ });
84
+ }
85
+ };
@@ -0,0 +1,22 @@
1
+ import { BaseMessage } from '@langchain/core/messages';
2
+
3
+ export enum AgentEvents {
4
+ AI_MESSAGE = 'AI_MESSAGE',
5
+ TRIGGER_EVENT = 'TRIGGER_EVENT',
6
+ }
7
+
8
+ export type AgentEventRequestPayloads<Memory> = {
9
+ [AgentEvents.AI_MESSAGE]: {
10
+ message: string;
11
+ memory: Memory;
12
+ };
13
+ [AgentEvents.TRIGGER_EVENT]: {
14
+ triggerName: string;
15
+ triggerBody: any;
16
+ };
17
+ };
18
+
19
+ export type AgentEventResponsePayloads<Memory> = {
20
+ [AgentEvents.AI_MESSAGE]: void;
21
+ [AgentEvents.TRIGGER_EVENT]: { messages?: BaseMessage[]; memory?: Memory } | false;
22
+ };
@@ -0,0 +1,3 @@
1
+ import { AgentEvents } from './AgentEvents';
2
+
3
+ export default AgentEvents;
package/src/index.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { Agent } from './agent';
2
+ import events from './events';
3
+
4
+ export { Agent, events };
5
+
6
+ export type {
7
+ Flow,
8
+ Node,
9
+ Edge,
10
+ TriggerNode,
11
+ AppTriggerNode,
12
+ PromptNode,
13
+ PromptConditionEdge,
14
+ LogicalConditionEdge,
15
+ StepForwardEdge,
16
+ ManualTriggerNode,
17
+ JunctionNode,
18
+ ToolNode,
19
+ AppToolNode,
20
+ } from './types/Flows.types';
21
+ export { NodeType, TriggerType, EdgeType } from './types/Flows.types';
22
+ export type { Tool } from './types/Tools.types';
@@ -0,0 +1,10 @@
1
+ import { LLMConfig, LLMProviders, LLMProvider } from "../types/LLM.types";
2
+
3
+ export const createLlmInstance = (llmConfig: LLMConfig) => {
4
+ const { name, properties } = llmConfig;
5
+ const LLMClass = LLMProviders[name as LLMProvider];
6
+ if (!LLMClass) {
7
+ throw new Error(`Unsupported LLM provider: ${name}`);
8
+ }
9
+ return new LLMClass(properties);
10
+ };
@@ -0,0 +1,20 @@
1
+ import { interrupt } from '@langchain/langgraph';
2
+ import { PreCompiledGraph } from '../types/LangGraph.types';
3
+ import { RunnableLike } from '@langchain/core/runnables';
4
+
5
+ type AddHumanInTheLoopNodeParams = {
6
+ graph: PreCompiledGraph;
7
+ attachedToNodeName: string;
8
+ };
9
+
10
+ export const buildHumanInTheLoopNodeName = (nodeName: string) => `${nodeName}_humanInTheLoop`;
11
+
12
+ export const addHumanInTheLoopNode = async ({ graph, attachedToNodeName }: AddHumanInTheLoopNodeParams) => {
13
+ const callback: RunnableLike = async () => {
14
+ console.log(`Executing "human in the loop" node for the attached node${attachedToNodeName}`);
15
+ const value = interrupt('input from human in the loop');
16
+ return value;
17
+ };
18
+ graph.addNode(`${attachedToNodeName}_humanInTheLoop`, callback);
19
+ graph.addEdge(attachedToNodeName as any, buildHumanInTheLoopNodeName(attachedToNodeName) as any);
20
+ };
@@ -0,0 +1,66 @@
1
+ import { RunnableLike } from '@langchain/core/runnables';
2
+ import { PromptNode } from '../types/Flows.types';
3
+ import { PreCompiledGraph, stateAnnotation } from '../types/LangGraph.types';
4
+ import { z } from 'zod';
5
+ import { BaseLanguageModel } from '@langchain/core/language_models/base';
6
+ import { createReactAgent } from '@langchain/langgraph/prebuilt';
7
+ import { SystemMessage } from '@langchain/core/messages';
8
+ import { Tool } from '../types/Tools.types';
9
+ import { tool as langchainTool } from '@langchain/core/tools';
10
+ import { AgentEventRequestPayloads, AgentEvents } from '../events/AgentEvents';
11
+ import { EmitSignature } from '../types/Agent.types';
12
+ import { createLlmInstance } from '../llm/createLlmInstance';
13
+
14
+ type AddPromptNodeParams = {
15
+ graph: PreCompiledGraph;
16
+ node: PromptNode;
17
+ llm: BaseLanguageModel;
18
+ tools: Tool<any, any>[];
19
+ emit: EmitSignature<any, keyof AgentEventRequestPayloads<any>>;
20
+ };
21
+
22
+ export const addPromptNode = async ({ graph, node, llm, tools, emit }: AddPromptNodeParams) => {
23
+ const callback: RunnableLike = async (state: typeof stateAnnotation.State) => {
24
+ console.log(`Executing prompt node ${node.name}`);
25
+ const llmToUse = node.llmConfig ? createLlmInstance(node.llmConfig) : llm;
26
+
27
+ const globalTools = tools
28
+ .filter((tool) => tool.isGlobal)
29
+ .map((tool) =>
30
+ langchainTool((input: z.infer<typeof tool.input>) => tool.execute({ input, memory: state.memory }), {
31
+ name: tool.name,
32
+ description: tool.description,
33
+ schema: tool.input,
34
+ }),
35
+ );
36
+
37
+ const agent = createReactAgent({
38
+ llm: llmToUse,
39
+ tools: globalTools,
40
+ responseFormat: z.object({
41
+ waitForUserResponse: z.boolean(),
42
+ done: z.boolean(),
43
+ }),
44
+ });
45
+ const message = `
46
+ - This is a node in a workflow.
47
+ - User instructions: ${node.prompt}
48
+ - Workflow memory: ${JSON.stringify(state.memory)}
49
+ - Conversation history: ${state.messages.map((message) => `${message.getType()}: ${message.content}`).join('\n')}
50
+ `;
51
+ const result = await agent.invoke({
52
+ messages: [new SystemMessage(message)],
53
+ });
54
+ const lastMessage = result.messages[result.messages.length - 1];
55
+ if (lastMessage.getType() === 'ai') {
56
+ await emit(AgentEvents.AI_MESSAGE, {
57
+ message: lastMessage.content as string,
58
+ memory: state.memory,
59
+ });
60
+ }
61
+ return {
62
+ messages: [lastMessage],
63
+ };
64
+ };
65
+ graph.addNode(node.name, callback);
66
+ };
@@ -0,0 +1,95 @@
1
+ import { BaseLanguageModel } from "@langchain/core/language_models/base";
2
+ import { ToolNode } from "../types/Flows.types";
3
+ import { Tool } from "../types/Tools.types";
4
+ import { tool as langchainTool } from "@langchain/core/tools";
5
+ import { PreCompiledGraph, stateAnnotation } from "../types/LangGraph.types";
6
+ import { AIMessage, BaseMessage, SystemMessage, ToolMessage } from "@langchain/core/messages";
7
+ import { createReactAgent } from "@langchain/langgraph/prebuilt";
8
+ import { RunnableLike } from "@langchain/core/runnables";
9
+ import { z } from "zod";
10
+
11
+ export const addToolNode = async <Memory>({
12
+ graph,
13
+ node,
14
+ tools,
15
+ llm,
16
+ }: {
17
+ graph: PreCompiledGraph;
18
+ node: ToolNode;
19
+ tools: Tool<any, any>[];
20
+ llm: BaseLanguageModel;
21
+ }) => {
22
+ const toolNode = node as ToolNode;
23
+ const matchedTool = tools.find((tool) => tool.name === toolNode.toolName);
24
+ if (!matchedTool) {
25
+ throw new Error(`Tool not found: ${toolNode.toolName}`);
26
+ }
27
+ const callback: RunnableLike = async (state: typeof stateAnnotation.State) => {
28
+ try {
29
+ console.log(`Executing tool node ${toolNode.name}`);
30
+
31
+ const executeWrapper = async (input: z.infer<typeof matchedTool.input>) => {
32
+ try {
33
+ const response = await matchedTool.execute({ input, memory: state.memory });
34
+ return response || {};
35
+ } catch (error) {
36
+ console.error('Error executing tool', error);
37
+ throw error;
38
+ }
39
+ };
40
+ const tool = langchainTool(executeWrapper, {
41
+ name: matchedTool.name,
42
+ description: matchedTool.description,
43
+ schema: matchedTool.input,
44
+ });
45
+ const prompt = `
46
+ Context:
47
+ messages: ${JSON.stringify(state.messages)}
48
+ workflow memory: ${JSON.stringify(state.memory)}
49
+ Instructions:
50
+ Call immediately tool ${tool.name}(...)
51
+ ${toolNode.prompt && `Tool execution instructions: ${toolNode.prompt}`}
52
+ `;
53
+ const toolCallingAgent = createReactAgent({
54
+ llm,
55
+ tools: [tool],
56
+ });
57
+ const response = await toolCallingAgent.invoke({ messages: [new SystemMessage(prompt)] });
58
+ const toolCallMessage = getLastToolCallMessage(response.messages);
59
+ const toolResponseMessage = getLastToolMessage(response.messages);
60
+ if (!toolCallMessage || !toolResponseMessage) {
61
+ throw new Error('Tool call or tool response message not found');
62
+ }
63
+ const toolMemory = extractToolMemoryResponse<Memory>(toolResponseMessage);
64
+ return { memory: toolMemory, messages: [toolCallMessage, toolResponseMessage] };
65
+ } catch (error) {
66
+ console.error('Error executing tool node', error);
67
+ throw error;
68
+ }
69
+ };
70
+ graph.addNode(node.name, callback);
71
+ };
72
+
73
+ const getLastToolCallMessage = (messages: BaseMessage[]): BaseMessage => {
74
+ const toolCallMessages = messages.filter((message) => (message as AIMessage).tool_calls?.length || 0 > 0);
75
+ const lastToolCallMessage = toolCallMessages[toolCallMessages.length - 1];
76
+ return lastToolCallMessage;
77
+ };
78
+
79
+ const getLastToolMessage = (messages: BaseMessage[]): ToolMessage => {
80
+ const toolMessages = messages.filter((message) => message.getType() === 'tool');
81
+ const lastToolMessage = toolMessages[toolMessages.length - 1];
82
+ return lastToolMessage as ToolMessage;
83
+ };
84
+
85
+ const extractToolMemoryResponse = <Memory>(toolMessage: ToolMessage): Partial<Memory> => {
86
+ try {
87
+ const parsed = JSON.parse(toolMessage.content as string);
88
+ if (typeof parsed === 'object' && parsed !== null && 'memory' in parsed) {
89
+ return parsed.memory;
90
+ }
91
+ } catch (error) {
92
+ console.error('Error parsing tool memory response', error);
93
+ }
94
+ return {};
95
+ };
@@ -0,0 +1,12 @@
1
+ import { RunnableLike } from '@langchain/core/runnables';
2
+ import { TriggerNode } from '../types/Flows.types';
3
+ import { PreCompiledGraph } from '../types/LangGraph.types';
4
+
5
+ export const addTriggerNode = async ({ graph, node }: { graph: PreCompiledGraph; node: TriggerNode }) => {
6
+ const callback: RunnableLike = async () => {
7
+ console.log(`Executing trigger node ${node.name}`);
8
+ return;
9
+ };
10
+
11
+ graph.addNode(node.name, callback);
12
+ };