@minded-ai/mindedjs 1.0.28 → 1.0.30

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 (143) hide show
  1. package/dist/agent.d.ts +1 -0
  2. package/dist/agent.d.ts.map +1 -0
  3. package/dist/agent.js +1 -1
  4. package/dist/agent.js.map +1 -1
  5. package/dist/checkpointer/checkpointSaverFactory.d.ts +1 -0
  6. package/dist/checkpointer/checkpointSaverFactory.d.ts.map +1 -0
  7. package/dist/cli/index.d.ts +1 -0
  8. package/dist/cli/index.d.ts.map +1 -0
  9. package/dist/edges/createDirectEdge.d.ts +1 -0
  10. package/dist/edges/createDirectEdge.d.ts.map +1 -0
  11. package/dist/edges/createLogicalRouter.d.ts +1 -0
  12. package/dist/edges/createLogicalRouter.d.ts.map +1 -0
  13. package/dist/edges/createPromptRouter.d.ts +1 -0
  14. package/dist/edges/createPromptRouter.d.ts.map +1 -0
  15. package/dist/edges/edgeFactory.d.ts +1 -0
  16. package/dist/edges/edgeFactory.d.ts.map +1 -0
  17. package/dist/events/AgentEvents.d.ts +1 -0
  18. package/dist/events/AgentEvents.d.ts.map +1 -0
  19. package/dist/events/index.d.ts +1 -0
  20. package/dist/events/index.d.ts.map +1 -0
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/llm/createLlmInstance.d.ts +1 -0
  24. package/dist/llm/createLlmInstance.d.ts.map +1 -0
  25. package/dist/nodes/addHumanInTheLoopNode.d.ts +1 -0
  26. package/dist/nodes/addHumanInTheLoopNode.d.ts.map +1 -0
  27. package/dist/nodes/addPromptNode.d.ts +1 -0
  28. package/dist/nodes/addPromptNode.d.ts.map +1 -0
  29. package/dist/nodes/addToolNode.d.ts +1 -0
  30. package/dist/nodes/addToolNode.d.ts.map +1 -0
  31. package/dist/nodes/addTriggerNode.d.ts +1 -0
  32. package/dist/nodes/addTriggerNode.d.ts.map +1 -0
  33. package/dist/nodes/nodeFactory.d.ts +1 -0
  34. package/dist/nodes/nodeFactory.d.ts.map +1 -0
  35. package/dist/platform/config.d.ts +1 -0
  36. package/dist/platform/config.d.ts.map +1 -0
  37. package/dist/platform/mindedChatOpenAI.d.ts +1 -0
  38. package/dist/platform/mindedChatOpenAI.d.ts.map +1 -0
  39. package/dist/platform/mindedCheckpointSaver.d.ts +1 -0
  40. package/dist/platform/mindedCheckpointSaver.d.ts.map +1 -0
  41. package/dist/platform/mindedConnection.d.ts +1 -0
  42. package/dist/platform/mindedConnection.d.ts.map +1 -0
  43. package/dist/platform/mindedConnectionTypes.d.ts +1 -0
  44. package/dist/platform/mindedConnectionTypes.d.ts.map +1 -0
  45. package/dist/triggers/triggerTypeToDefaultMessage.d.ts +1 -0
  46. package/dist/triggers/triggerTypeToDefaultMessage.d.ts.map +1 -0
  47. package/dist/types/Agent.types.d.ts +1 -0
  48. package/dist/types/Agent.types.d.ts.map +1 -0
  49. package/dist/types/Flows.types.d.ts +1 -0
  50. package/dist/types/Flows.types.d.ts.map +1 -0
  51. package/dist/types/LLM.types.d.ts +1 -0
  52. package/dist/types/LLM.types.d.ts.map +1 -0
  53. package/dist/types/LangGraph.types.d.ts +1 -0
  54. package/dist/types/LangGraph.types.d.ts.map +1 -0
  55. package/dist/types/Tools.types.d.ts +1 -0
  56. package/dist/types/Tools.types.d.ts.map +1 -0
  57. package/package.json +5 -1
  58. package/src/agent.ts +1 -1
  59. package/.github/workflows/CI.yml +0 -34
  60. package/.prettierrc +0 -8
  61. package/docs/.gitbook/assets/image.png +0 -0
  62. package/docs/README.md +0 -51
  63. package/docs/SUMMARY.md +0 -23
  64. package/docs/api-reference/.nojekyll +0 -1
  65. package/docs/api-reference/assets/hierarchy.js +0 -1
  66. package/docs/api-reference/assets/highlight.css +0 -120
  67. package/docs/api-reference/assets/icons.js +0 -18
  68. package/docs/api-reference/assets/icons.svg +0 -1
  69. package/docs/api-reference/assets/main.js +0 -60
  70. package/docs/api-reference/assets/navigation.js +0 -1
  71. package/docs/api-reference/assets/search.js +0 -1
  72. package/docs/api-reference/assets/style.css +0 -1640
  73. package/docs/api-reference/classes/index.Agent.html +0 -4
  74. package/docs/api-reference/enums/index.EdgeType.html +0 -4
  75. package/docs/api-reference/enums/index.NodeType.html +0 -6
  76. package/docs/api-reference/enums/index.TriggerType.html +0 -4
  77. package/docs/api-reference/enums/index.events.html +0 -3
  78. package/docs/api-reference/hierarchy.html +0 -1
  79. package/docs/api-reference/index.html +0 -310
  80. package/docs/api-reference/interfaces/index.AppToolNode.html +0 -5
  81. package/docs/api-reference/interfaces/index.AppTriggerNode.html +0 -6
  82. package/docs/api-reference/interfaces/index.Flow.html +0 -4
  83. package/docs/api-reference/interfaces/index.JunctionNode.html +0 -4
  84. package/docs/api-reference/interfaces/index.LogicalConditionEdge.html +0 -5
  85. package/docs/api-reference/interfaces/index.ManualTriggerNode.html +0 -5
  86. package/docs/api-reference/interfaces/index.PromptConditionEdge.html +0 -5
  87. package/docs/api-reference/interfaces/index.PromptNode.html +0 -6
  88. package/docs/api-reference/interfaces/index.StepForwardEdge.html +0 -4
  89. package/docs/api-reference/interfaces/index.Tool.html +0 -6
  90. package/docs/api-reference/interfaces/index.ToolNode.html +0 -5
  91. package/docs/api-reference/modules/index-1.html +0 -1
  92. package/docs/api-reference/modules/index.html +0 -1
  93. package/docs/api-reference/modules.html +0 -1
  94. package/docs/api-reference/types/index.Edge.html +0 -1
  95. package/docs/api-reference/types/index.Node.html +0 -1
  96. package/docs/api-reference/types/index.TriggerNode.html +0 -1
  97. package/docs/core-concepts/edges.md +0 -299
  98. package/docs/core-concepts/events.md +0 -165
  99. package/docs/core-concepts/flows.md +0 -74
  100. package/docs/core-concepts/memory-types.md +0 -208
  101. package/docs/core-concepts/nodes.md +0 -257
  102. package/docs/core-concepts/tools.md +0 -225
  103. package/docs/core-concepts/triggers.md +0 -87
  104. package/docs/examples/order-refund-flow.md +0 -560
  105. package/docs/getting-started/environment-configuration.md +0 -117
  106. package/docs/getting-started/installation.md +0 -40
  107. package/docs/getting-started/quick-start.md +0 -264
  108. package/docs-structure.md +0 -144
  109. package/eslint.config.js +0 -68
  110. package/examples/orderRefundAgent/flows/orderRefundFlow.yaml +0 -26
  111. package/examples/orderRefundAgent/minded.json +0 -10
  112. package/examples/orderRefundAgent/orderRefundAgent.ts +0 -64
  113. package/examples/orderRefundAgent/schema.ts +0 -7
  114. package/examples/orderRefundAgent/tools/escalateConversation.ts +0 -28
  115. package/examples/orderRefundAgent/tools/index.ts +0 -4
  116. package/examples/orderRefundAgent/tools/refundOrder.ts +0 -27
  117. package/test/can-stay-on-node/can-stay-on-node.test.ts +0 -154
  118. package/test/can-stay-on-node/flows/test-flow.yaml +0 -25
  119. package/test/cannot-stay-on-node/cannot-stay-on-node.test.ts +0 -209
  120. package/test/cannot-stay-on-node/flows/test-flow.yaml +0 -34
  121. package/test/checkpoint-saver/minded-checkpoint-saver-list.test.ts +0 -140
  122. package/test/checkpoint-saver/minded-checkpoint-saver.test.ts +0 -100
  123. package/test/edge-priority/edge-priority.test.ts +0 -182
  124. package/test/edge-priority/flows/all-three-edges/test-flow.yaml +0 -35
  125. package/test/edge-priority/flows/logical-prompt-edges/test-flow.yaml +0 -26
  126. package/test/edge-priority/flows/stepforward-logical-edges/test-flow.yaml +0 -25
  127. package/test/edge-priority/flows/stepforward-prompt-edges/test-flow.yaml +0 -25
  128. package/test/human-in-the-loop-node/flows/test-flow.yaml +0 -23
  129. package/test/human-in-the-loop-node/human-in-the-loop-node.test.ts +0 -94
  130. package/test/logical-edges/flows/logical-edge-test-flow.yaml +0 -24
  131. package/test/logical-edges/logical-edges.test.ts +0 -68
  132. package/test/no-human-in-the-loop-node/flows/test-flow.yaml +0 -23
  133. package/test/no-human-in-the-loop-node/no-human-in-the-loop-node.test.ts +0 -81
  134. package/test/prompt-edges/flows/test-flow.yaml +0 -24
  135. package/test/prompt-edges/prompt-edges.test.ts +0 -92
  136. package/test/prompt-node/flows/test-flow.yaml +0 -24
  137. package/test/prompt-node/prompt-node.test.ts +0 -88
  138. package/test/setup.ts +0 -5
  139. package/test/tool-node/flows/test-flow.yaml +0 -14
  140. package/test/tool-node/tool-node.test.ts +0 -68
  141. package/test/trigger/flows/test-flow.yaml +0 -7
  142. package/test/trigger/trigger.test.ts +0 -185
  143. package/tsconfig.json +0 -17
@@ -1,154 +0,0 @@
1
- import { expect } from 'chai';
2
- import { Agent } from '../../src/agent';
3
- import { z } from 'zod';
4
- import { v4 as uuidv4 } from 'uuid';
5
- import path from 'path';
6
- import { AgentEvents } from '../../src/events/AgentEvents';
7
- import { HumanMessage } from '@langchain/core/messages';
8
-
9
- const memorySchema = z.object({
10
- success: z.boolean(),
11
- stayCount: z.number().optional(),
12
- });
13
-
14
- describe('Can Stay On Node', function () {
15
- this.timeout(10000); // Set timeout to 10 seconds for all tests in this suite
16
-
17
- let agent: Agent;
18
-
19
- beforeEach(() => {
20
- const flowsPath = path.join(__dirname, 'flows');
21
- const toolsPath = path.join(__dirname, 'tools');
22
- agent = new Agent({
23
- memorySchema,
24
- config: {
25
- tools: [toolsPath],
26
- flows: [flowsPath],
27
- llm: {
28
- name: 'ChatOpenAI',
29
- properties: {
30
- model: 'gpt-4o-mini',
31
- },
32
- },
33
- },
34
- tools: [
35
- {
36
- name: 'successTool',
37
- description: 'Success Tool that marks completion',
38
- input: z.object({
39
- name: z.string(),
40
- }),
41
- execute: async () => ({
42
- memory: {
43
- success: true,
44
- },
45
- }),
46
- },
47
- ],
48
- });
49
-
50
- // Add trigger event handler
51
- agent.on(AgentEvents.TRIGGER_EVENT, async ({ triggerName, triggerBody }) => {
52
- if (triggerName === 'Trigger') {
53
- return {
54
- memory: {},
55
- messages: [new HumanMessage(triggerBody.message)],
56
- };
57
- }
58
- });
59
- });
60
-
61
- it('should stay on the same node multiple times when instructed to stay, then continue', async function () {
62
- this.timeout(20000); // Set timeout to 40 seconds for this test
63
-
64
- const sessionId = uuidv4();
65
-
66
- // First invocation - triggers the flow and reaches the human-in-the-loop pause
67
- const result1 = await agent.invoke({
68
- triggerBody: {
69
- message: 'Initial input for processing',
70
- },
71
- triggerName: 'Trigger',
72
- sessionId: sessionId,
73
- appName: 'test',
74
- });
75
-
76
- // Should pause at human-in-the-loop, not reach success tool yet
77
- expect(result1.memory.success).to.not.equal(true);
78
- expect(result1.messages).to.have.length.greaterThan(0);
79
-
80
- // Second invocation - provide "stay" instruction
81
- const result2 = await agent.invoke({
82
- triggerBody: {
83
- message: 'stay',
84
- },
85
- triggerName: 'Trigger',
86
- sessionId: sessionId,
87
- appName: 'test',
88
- });
89
-
90
- // Should stay on the node, pause again, not reach success tool
91
- expect(result2.memory.success).to.not.equal(true);
92
- expect(result2.messages).to.have.length.greaterThan(0);
93
-
94
- // Third invocation - provide another "stay" instruction
95
- const result3 = await agent.invoke({
96
- triggerBody: {
97
- message: 'stay',
98
- },
99
- triggerName: 'Trigger',
100
- sessionId: sessionId,
101
- appName: 'test',
102
- });
103
-
104
- // Should still stay on the node, pause again
105
- expect(result3.memory.success).to.not.equal(true);
106
- expect(result3.messages).to.have.length.greaterThan(0);
107
-
108
- // Fourth invocation - provide "continue" instruction
109
- const result4 = await agent.invoke({
110
- triggerBody: {
111
- message: 'continue',
112
- },
113
- triggerName: 'Trigger',
114
- sessionId: sessionId,
115
- appName: 'test',
116
- });
117
-
118
- // Should now move to success tool and complete
119
- expect(result4.memory.success).to.equal(true);
120
- });
121
-
122
- it('should move to next node immediately when instructed to continue', async function () {
123
- this.timeout(20000); // Set timeout to 20 seconds for this test
124
-
125
- const sessionId = uuidv4();
126
-
127
- // First invocation - triggers the flow and reaches the human-in-the-loop pause
128
- const result1 = await agent.invoke({
129
- triggerBody: {
130
- message: 'Initial input for processing',
131
- },
132
- triggerName: 'Trigger',
133
- sessionId: sessionId,
134
- appName: 'test',
135
- });
136
-
137
- // Should pause at human-in-the-loop, not reach success tool yet
138
- expect(result1.memory.success).to.not.equal(true);
139
- expect(result1.messages).to.have.length.greaterThan(0);
140
-
141
- // Second invocation - provide "continue" instruction
142
- const result2 = await agent.invoke({
143
- triggerBody: {
144
- message: 'continue',
145
- },
146
- triggerName: 'Trigger',
147
- sessionId: sessionId,
148
- appName: 'test',
149
- });
150
-
151
- // Should now move to success tool and complete
152
- expect(result2.memory.success).to.equal(true);
153
- });
154
- });
@@ -1,25 +0,0 @@
1
- name: Can Stay On Node Test Flow
2
- nodes:
3
- - type: 'trigger'
4
- triggerType: 'manual'
5
- name: 'Trigger'
6
-
7
- - type: 'promptNode'
8
- name: 'DecisionNode'
9
- prompt: 'I have processed your input. Would you like me to continue to the next step, or should I stay on this node for further processing? Please respond with "continue" to move forward or "stay" to remain here for additional analysis.'
10
- canStayOnNode: true
11
- humanInTheLoop: true
12
-
13
- - type: 'tool'
14
- name: 'Success'
15
- toolName: 'successTool'
16
-
17
- edges:
18
- - source: 'Trigger'
19
- target: 'DecisionNode'
20
- type: 'stepForward'
21
-
22
- - source: 'DecisionNode'
23
- target: 'Success'
24
- type: 'promptCondition'
25
- prompt: 'move to success node if the user wants to continue or if processing is complete'
@@ -1,209 +0,0 @@
1
- import { expect } from 'chai';
2
- import { Agent } from '../../src/agent';
3
- import { z } from 'zod';
4
- import { v4 as uuidv4 } from 'uuid';
5
- import path from 'path';
6
- import { AgentEvents } from '../../src/events/AgentEvents';
7
- import { HumanMessage } from '@langchain/core/messages';
8
-
9
- const memorySchema = z.object({
10
- success: z.boolean(),
11
- alternative: z.boolean(),
12
- });
13
-
14
- describe('Cannot Stay On Node', function () {
15
- this.timeout(10000); // Set timeout to 10 seconds for all tests in this suite
16
-
17
- let agent: Agent;
18
-
19
- beforeEach(() => {
20
- const flowsPath = path.join(__dirname, 'flows');
21
- const toolsPath = path.join(__dirname, 'tools');
22
- agent = new Agent({
23
- memorySchema,
24
- config: {
25
- flows: [flowsPath],
26
- tools: [toolsPath],
27
- llm: {
28
- name: 'ChatOpenAI',
29
- properties: {
30
- model: 'gpt-4o-mini',
31
- },
32
- },
33
- },
34
- tools: [
35
- {
36
- name: 'successTool',
37
- description: 'Success Tool that marks completion',
38
- input: z.object({
39
- name: z.string(),
40
- }),
41
- execute: async () => ({
42
- memory: {
43
- success: true,
44
- alternative: false,
45
- },
46
- }),
47
- },
48
- {
49
- name: 'alternativeTool',
50
- description: 'Alternative Tool that marks alternative completion',
51
- input: z.object({
52
- name: z.string(),
53
- }),
54
- execute: async () => ({
55
- memory: {
56
- success: false,
57
- alternative: true,
58
- },
59
- }),
60
- },
61
- ],
62
- });
63
-
64
- // Add trigger event handler
65
- agent.on(AgentEvents.TRIGGER_EVENT, async ({ triggerName, triggerBody }) => {
66
- if (triggerName === 'Trigger') {
67
- return {
68
- memory: {},
69
- messages: [new HumanMessage(triggerBody.message)],
70
- };
71
- }
72
- });
73
- });
74
-
75
- it('should NOT stay on node when explicitly requested to stay (canStayOnNode is false)', async function () {
76
- this.timeout(20000);
77
-
78
- const sessionId = uuidv4();
79
-
80
- // First invocation - triggers the flow and reaches the human-in-the-loop pause
81
- const result1 = await agent.invoke({
82
- triggerBody: {
83
- message: 'Initial input for processing',
84
- },
85
- triggerName: 'Trigger',
86
- sessionId: sessionId,
87
- appName: 'test',
88
- });
89
-
90
- // Should pause at human-in-the-loop, not reach any tool yet
91
- expect(result1.memory.success).to.not.equal(true);
92
- expect(result1.memory.alternative).to.not.equal(true);
93
- expect(result1.messages).to.have.length.greaterThan(0);
94
-
95
- // Second invocation - explicitly request to stay on the node
96
- const result2 = await agent.invoke({
97
- triggerBody: {
98
- message: 'You must stay at the node! Do not move to the next step. Stay here and continue processing on this same node.',
99
- },
100
- triggerName: 'Trigger',
101
- sessionId: sessionId,
102
- appName: 'test',
103
- });
104
-
105
- // Should NOT stay on the node - should move to one of the target nodes
106
- // Since canStayOnNode is false, the router has no self-referencing edge option
107
- expect(result2.memory.success || result2.memory.alternative).to.equal(true);
108
- });
109
-
110
- it('should move to success node when user requests main workflow', async function () {
111
- this.timeout(20000);
112
-
113
- const sessionId = uuidv4();
114
-
115
- // First invocation - initial trigger
116
- const result1 = await agent.invoke({
117
- triggerBody: {
118
- message: 'Start processing',
119
- },
120
- triggerName: 'Trigger',
121
- sessionId: sessionId,
122
- appName: 'test',
123
- });
124
-
125
- expect(result1.memory.success).to.not.equal(true);
126
- expect(result1.memory.alternative).to.not.equal(true);
127
-
128
- // Second invocation - request main workflow
129
- const result2 = await agent.invoke({
130
- triggerBody: {
131
- message: 'Proceed with the main workflow',
132
- },
133
- triggerName: 'Trigger',
134
- sessionId: sessionId,
135
- appName: 'test',
136
- });
137
-
138
- // Should move to success node
139
- expect(result2.memory.success).to.equal(true);
140
- expect(result2.memory.alternative).to.equal(false);
141
- });
142
-
143
- it('should move to alternative node when user requests alternative approach', async function () {
144
- this.timeout(20000);
145
-
146
- const sessionId = uuidv4();
147
-
148
- // First invocation - initial trigger
149
- const result1 = await agent.invoke({
150
- triggerBody: {
151
- message: 'Begin workflow',
152
- },
153
- triggerName: 'Trigger',
154
- sessionId: sessionId,
155
- appName: 'test',
156
- });
157
-
158
- expect(result1.memory.success).to.not.equal(true);
159
- expect(result1.memory.alternative).to.not.equal(true);
160
-
161
- // Second invocation - request alternative approach
162
- const result2 = await agent.invoke({
163
- triggerBody: {
164
- message: 'I want an alternative approach instead',
165
- },
166
- triggerName: 'Trigger',
167
- sessionId: sessionId,
168
- appName: 'test',
169
- });
170
-
171
- // Should move to alternative node
172
- expect(result2.memory.success).to.equal(false);
173
- expect(result2.memory.alternative).to.equal(true);
174
- });
175
-
176
- it('should force progression even with strong stay requests', async function () {
177
- this.timeout(20000);
178
-
179
- const sessionId = uuidv4();
180
-
181
- // First invocation - initial trigger
182
- const result1 = await agent.invoke({
183
- triggerBody: {
184
- message: 'Process this data',
185
- },
186
- triggerName: 'Trigger',
187
- sessionId: sessionId,
188
- appName: 'test',
189
- });
190
-
191
- expect(result1.memory.success).to.not.equal(true);
192
- expect(result1.memory.alternative).to.not.equal(true);
193
-
194
- // Second invocation - very strong request to stay
195
- const result2 = await agent.invoke({
196
- triggerBody: {
197
- message:
198
- 'STAY! Do not proceed! You must remain on this current node and not move forward. Keep processing here. Do not go to any other node.',
199
- },
200
- triggerName: 'Trigger',
201
- sessionId: sessionId,
202
- appName: 'test',
203
- });
204
-
205
- // Should still be forced to move to one of the available target nodes
206
- // because there's no self-referencing edge (canStayOnNode is false)
207
- expect(result2.memory.success || result2.memory.alternative).to.equal(true);
208
- });
209
- });
@@ -1,34 +0,0 @@
1
- name: Cannot Stay On Node Test Flow
2
- nodes:
3
- - type: 'trigger'
4
- triggerType: 'manual'
5
- name: 'Trigger'
6
-
7
- - type: 'promptNode'
8
- name: 'ProcessingNode'
9
- prompt: 'I have processed your input. Please provide your feedback or additional instructions.'
10
- humanInTheLoop: true
11
- # Note: canStayOnNode is NOT set to true
12
-
13
- - type: 'tool'
14
- name: 'Success'
15
- toolName: 'successTool'
16
-
17
- - type: 'tool'
18
- name: 'Alternative'
19
- toolName: 'alternativeTool'
20
-
21
- edges:
22
- - source: 'Trigger'
23
- target: 'ProcessingNode'
24
- type: 'stepForward'
25
-
26
- - source: 'ProcessingNode'
27
- target: 'Success'
28
- type: 'promptCondition'
29
- prompt: 'move to success node if the user wants to proceed with the main workflow'
30
-
31
- - source: 'ProcessingNode'
32
- target: 'Alternative'
33
- type: 'promptCondition'
34
- prompt: 'move to alternative node if the user wants an alternative approach'
@@ -1,140 +0,0 @@
1
- import { expect } from 'chai';
2
- import { describe, it, beforeEach, afterEach } from 'mocha';
3
- import * as sinon from 'sinon';
4
- import { MindedCheckpointSaver } from '../../src/platform/mindedCheckpointSaver';
5
- import { Checkpoint, CheckpointTuple, CheckpointMetadata, emptyCheckpoint, CheckpointListOptions } from '@langchain/langgraph-checkpoint';
6
- import { RunnableConfig } from '@langchain/core/runnables';
7
- import { HumanMessage, AIMessage, SystemMessage } from '@langchain/core/messages';
8
- import { MindedConnection } from '../../src/platform/mindedConnection';
9
- import { MindedConnectionSocketMessageType } from '../../src/platform/mindedConnectionTypes';
10
-
11
- describe('MindedCheckpointSaver - List Method', function () {
12
- let checkpointSaver: MindedCheckpointSaver;
13
- let mindedConnection: MindedConnection;
14
- let awaitEmitStub: sinon.SinonStub;
15
-
16
- beforeEach(() => {
17
- mindedConnection = new MindedConnection();
18
- checkpointSaver = new MindedCheckpointSaver(mindedConnection);
19
- awaitEmitStub = sinon.stub(mindedConnection, 'awaitEmit');
20
- });
21
-
22
- afterEach(() => {
23
- sinon.restore();
24
- });
25
-
26
- describe('list', () => {
27
- it('should deserialize multiple checkpoints with different message types', async () => {
28
- // Create original checkpoints with proper class instances
29
- const checkpoint1: Checkpoint = {
30
- ...emptyCheckpoint(),
31
- id: 'checkpoint-1',
32
- channel_values: {
33
- messages: [new HumanMessage('First message')],
34
- },
35
- };
36
-
37
- const checkpoint2: Checkpoint = {
38
- ...emptyCheckpoint(),
39
- id: 'checkpoint-2',
40
- channel_values: {
41
- messages: [new SystemMessage('System prompt'), new AIMessage('AI response')],
42
- },
43
- };
44
-
45
- const metadata1: CheckpointMetadata = {
46
- source: 'input',
47
- step: 1,
48
- writes: {},
49
- parents: {},
50
- };
51
-
52
- const metadata2: CheckpointMetadata = {
53
- source: 'loop',
54
- step: 2,
55
- writes: {},
56
- parents: {},
57
- };
58
-
59
- // Simulate JSON serialization (what happens during HTTP transport)
60
- const jsonCheckpoint1 = JSON.parse(JSON.stringify(checkpoint1));
61
- const jsonCheckpoint2 = JSON.parse(JSON.stringify(checkpoint2));
62
- const jsonMetadata1 = JSON.parse(JSON.stringify(metadata1));
63
- const jsonMetadata2 = JSON.parse(JSON.stringify(metadata2));
64
-
65
- const mockServerResponse = {
66
- checkpoints: [
67
- {
68
- config: { configurable: { thread_id: 'test-thread', checkpoint_id: 'checkpoint-1' } },
69
- checkpoint: jsonCheckpoint1,
70
- metadata: jsonMetadata1,
71
- parentConfig: undefined,
72
- },
73
- {
74
- config: { configurable: { thread_id: 'test-thread', checkpoint_id: 'checkpoint-2' } },
75
- checkpoint: jsonCheckpoint2,
76
- metadata: jsonMetadata2,
77
- parentConfig: { configurable: { thread_id: 'test-thread', checkpoint_id: 'checkpoint-1' } },
78
- },
79
- ],
80
- };
81
-
82
- // Mock the awaitEmit response
83
- awaitEmitStub.resolves(mockServerResponse);
84
-
85
- const config: RunnableConfig = { configurable: { thread_id: 'test-thread' } };
86
- const options: CheckpointListOptions = { limit: 10 };
87
-
88
- const results: CheckpointTuple[] = [];
89
- for await (const tuple of checkpointSaver.list(config, options)) {
90
- results.push(tuple);
91
- }
92
-
93
- // Verify the awaitEmit was called correctly
94
- expect(awaitEmitStub.calledOnce).to.equal(true);
95
- expect(awaitEmitStub.firstCall.args[0]).to.equal(MindedConnectionSocketMessageType.CHECKPOINT_LIST);
96
- expect(awaitEmitStub.firstCall.args[1]).to.deep.equal({
97
- type: MindedConnectionSocketMessageType.CHECKPOINT_LIST,
98
- config,
99
- options,
100
- });
101
-
102
- // Verify we got the correct number of results
103
- expect(results).to.have.length(2);
104
-
105
- // Verify first checkpoint - JSON objects should be restored to proper classes
106
- const result1 = results[0];
107
- expect(result1.checkpoint.id).to.equal('checkpoint-1');
108
- expect(result1.metadata?.source).to.equal('input');
109
- const messages1 = result1.checkpoint.channel_values.messages as any[];
110
- expect(messages1).to.have.length(1);
111
- expect(messages1[0]).to.be.instanceOf(HumanMessage);
112
- expect(messages1[0].content).to.equal('First message');
113
-
114
- // Verify second checkpoint - JSON objects should be restored to proper classes
115
- const result2 = results[1];
116
- expect(result2.checkpoint.id).to.equal('checkpoint-2');
117
- expect(result2.metadata?.source).to.equal('loop');
118
- expect(result2.parentConfig).to.deep.equal({ configurable: { thread_id: 'test-thread', checkpoint_id: 'checkpoint-1' } });
119
- const messages2 = result2.checkpoint.channel_values.messages as any[];
120
- expect(messages2).to.have.length(2);
121
- expect(messages2[0]).to.be.instanceOf(SystemMessage);
122
- expect(messages2[1]).to.be.instanceOf(AIMessage);
123
- expect(messages2[0].content).to.equal('System prompt');
124
- expect(messages2[1].content).to.equal('AI response');
125
- });
126
-
127
- it('should handle empty checkpoint list', async () => {
128
- awaitEmitStub.resolves({ checkpoints: [] });
129
-
130
- const config: RunnableConfig = { configurable: { thread_id: 'empty-thread' } };
131
-
132
- const results: CheckpointTuple[] = [];
133
- for await (const tuple of checkpointSaver.list(config)) {
134
- results.push(tuple);
135
- }
136
-
137
- expect(results).to.have.length(0);
138
- });
139
- });
140
- });
@@ -1,100 +0,0 @@
1
- import { expect } from 'chai';
2
- import { describe, it, beforeEach, afterEach } from 'mocha';
3
- import * as sinon from 'sinon';
4
- import { MindedCheckpointSaver } from '../../src/platform/mindedCheckpointSaver';
5
- import { Checkpoint, CheckpointMetadata, emptyCheckpoint } from '@langchain/langgraph-checkpoint';
6
- import { RunnableConfig } from '@langchain/core/runnables';
7
- import { HumanMessage, AIMessage } from '@langchain/core/messages';
8
- import { MindedConnection } from '../../src/platform/mindedConnection';
9
- import { MindedConnectionSocketMessageType } from '../../src/platform/mindedConnectionTypes';
10
-
11
- describe('MindedCheckpointSaver', function () {
12
- let checkpointSaver: MindedCheckpointSaver;
13
- let mindedConnection: MindedConnection;
14
- let awaitEmitStub: sinon.SinonStub;
15
-
16
- beforeEach(() => {
17
- mindedConnection = new MindedConnection();
18
- checkpointSaver = new MindedCheckpointSaver(mindedConnection);
19
- awaitEmitStub = sinon.stub(mindedConnection, 'awaitEmit');
20
- });
21
-
22
- afterEach(() => {
23
- sinon.restore();
24
- });
25
-
26
- describe('getTuple', () => {
27
- it('should deserialize checkpoint and metadata correctly', async () => {
28
- // First, let's create proper objects and see what the serializer produces
29
- const originalHumanMessage = new HumanMessage('Hello');
30
- const originalAIMessage = new AIMessage('Hi there!');
31
-
32
- // Create a checkpoint with these messages
33
- const originalCheckpoint: Checkpoint = {
34
- ...emptyCheckpoint(),
35
- id: 'test-checkpoint-id',
36
- channel_values: {
37
- messages: [originalHumanMessage, originalAIMessage],
38
- },
39
- };
40
-
41
- const originalMetadata: CheckpointMetadata = {
42
- source: 'input',
43
- step: 1,
44
- writes: {},
45
- parents: {},
46
- };
47
-
48
- // Simulate what happens when these objects go through JSON serialization (HTTP transport)
49
- // This is what the server would actually return - plain JSON objects without class info
50
- const jsonSerializedCheckpoint = JSON.parse(JSON.stringify(originalCheckpoint));
51
- const jsonSerializedMetadata = JSON.parse(JSON.stringify(originalMetadata));
52
-
53
- const mockServerResponse = {
54
- tuple: {
55
- config: { configurable: { thread_id: 'test-thread' } },
56
- checkpoint: jsonSerializedCheckpoint,
57
- metadata: jsonSerializedMetadata,
58
- parentConfig: undefined,
59
- pendingWrites: [],
60
- },
61
- };
62
-
63
- // Mock the awaitEmit response
64
- awaitEmitStub.resolves(mockServerResponse);
65
-
66
- const config: RunnableConfig = { configurable: { thread_id: 'test-thread' } };
67
- const result = await checkpointSaver.getTuple(config);
68
-
69
- // Verify the awaitEmit was called correctly
70
- expect(awaitEmitStub.calledOnce).to.equal(true);
71
- expect(awaitEmitStub.firstCall.args[0]).to.equal(MindedConnectionSocketMessageType.CHECKPOINT_GET_TUPLE);
72
- expect(awaitEmitStub.firstCall.args[1]).to.deep.equal({
73
- type: MindedConnectionSocketMessageType.CHECKPOINT_GET_TUPLE,
74
- config,
75
- });
76
-
77
- // Verify the result structure
78
- expect(result).to.not.equal(undefined);
79
- expect(result!.config).to.deep.equal(mockServerResponse.tuple.config);
80
- expect(result!.checkpoint.id).to.equal('test-checkpoint-id');
81
-
82
- // This is the key test - the JSON objects should be restored to proper classes
83
- const messages = result!.checkpoint.channel_values.messages as any[];
84
- expect(messages).to.have.length(2);
85
- expect(messages[0]).to.be.instanceOf(HumanMessage);
86
- expect(messages[1]).to.be.instanceOf(AIMessage);
87
- expect(messages[0].content).to.equal('Hello');
88
- expect(messages[1].content).to.equal('Hi there!');
89
- });
90
-
91
- it('should return undefined when server returns no tuple', async () => {
92
- awaitEmitStub.resolves({ tuple: undefined });
93
-
94
- const config: RunnableConfig = { configurable: { thread_id: 'test-thread' } };
95
- const result = await checkpointSaver.getTuple(config);
96
-
97
- expect(result).to.equal(undefined);
98
- });
99
- });
100
- });