@minded-ai/mindedjs 1.0.96 → 1.0.97-beta-1236

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 (77) hide show
  1. package/dist/browserTask/README.md +419 -0
  2. package/dist/browserTask/browserAgent.py +632 -0
  3. package/dist/browserTask/captcha_isolated.png +0 -0
  4. package/dist/browserTask/executeBrowserTask.d.ts +2 -0
  5. package/dist/browserTask/executeBrowserTask.d.ts.map +1 -0
  6. package/dist/browserTask/executeBrowserTask.js +78 -0
  7. package/dist/browserTask/executeBrowserTask.js.map +1 -0
  8. package/dist/browserTask/executeBrowserTask.ts +78 -0
  9. package/dist/browserTask/requirements.txt +8 -0
  10. package/dist/browserTask/setup.sh +144 -0
  11. package/dist/cli/index.js +103 -1
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/nodes/addAppToolNode.js +2 -2
  14. package/dist/nodes/addAppToolNode.js.map +1 -1
  15. package/dist/nodes/addBrowserTaskNode.d.ts +11 -0
  16. package/dist/nodes/addBrowserTaskNode.d.ts.map +1 -0
  17. package/dist/nodes/addBrowserTaskNode.js +100 -0
  18. package/dist/nodes/addBrowserTaskNode.js.map +1 -0
  19. package/dist/nodes/nodeFactory.d.ts.map +1 -1
  20. package/dist/nodes/nodeFactory.js +4 -0
  21. package/dist/nodes/nodeFactory.js.map +1 -1
  22. package/dist/types/Flows.types.d.ts +9 -2
  23. package/dist/types/Flows.types.d.ts.map +1 -1
  24. package/dist/types/Flows.types.js +1 -0
  25. package/dist/types/Flows.types.js.map +1 -1
  26. package/dist/utils/logger.js +1 -1
  27. package/dist/utils/logger.js.map +1 -1
  28. package/docs/getting-started/installation.md +42 -0
  29. package/package.json +13 -5
  30. package/src/agent.ts +0 -897
  31. package/src/checkpointer/checkpointSaverFactory.ts +0 -18
  32. package/src/cli/index.ts +0 -170
  33. package/src/cli/lambdaHandlerTemplate.ts +0 -46
  34. package/src/edges/createDirectEdge.ts +0 -11
  35. package/src/edges/createLogicalRouter.ts +0 -98
  36. package/src/edges/createPromptRouter.ts +0 -210
  37. package/src/edges/edgeFactory.ts +0 -125
  38. package/src/events/AgentEvents.ts +0 -47
  39. package/src/events/index.ts +0 -3
  40. package/src/index.ts +0 -51
  41. package/src/interfaces/zendesk.ts +0 -157
  42. package/src/internalTools/appActionRunnerTool.ts +0 -75
  43. package/src/internalTools/sendPlaceholderMessage.ts +0 -27
  44. package/src/internalTools/timer.ts +0 -137
  45. package/src/llm/createLlmInstance.ts +0 -10
  46. package/src/nodes/addAppToolNode.ts +0 -95
  47. package/src/nodes/addHumanInTheLoopNode.ts +0 -25
  48. package/src/nodes/addJumpToNode.ts +0 -24
  49. package/src/nodes/addJunctionNode.ts +0 -19
  50. package/src/nodes/addPromptNode.ts +0 -117
  51. package/src/nodes/addToolNode.ts +0 -71
  52. package/src/nodes/addToolRunNode.ts +0 -74
  53. package/src/nodes/addTriggerNode.ts +0 -26
  54. package/src/nodes/nodeFactory.ts +0 -53
  55. package/src/platform/config.ts +0 -77
  56. package/src/platform/mindedChatOpenAI.ts +0 -19
  57. package/src/platform/mindedCheckpointSaver.ts +0 -146
  58. package/src/platform/mindedConnection.ts +0 -199
  59. package/src/platform/mindedConnectionTypes.ts +0 -191
  60. package/src/platform/piiGateway/gateway.ts +0 -103
  61. package/src/platform/piiGateway/index.ts +0 -5
  62. package/src/platform/piiGateway/types.ts +0 -29
  63. package/src/playbooks/playbooks.ts +0 -209
  64. package/src/triggers/triggerTypeToDefaultMessage.ts +0 -9
  65. package/src/types/Agent.types.ts +0 -67
  66. package/src/types/Flows.types.ts +0 -184
  67. package/src/types/LLM.types.ts +0 -15
  68. package/src/types/LangGraph.types.ts +0 -48
  69. package/src/types/Platform.types.ts +0 -1
  70. package/src/types/Tools.types.ts +0 -31
  71. package/src/types/Voice.types.ts +0 -4
  72. package/src/utils/extractStateMemoryResponse.ts +0 -16
  73. package/src/utils/history.ts +0 -9
  74. package/src/utils/logger.ts +0 -22
  75. package/src/utils/wait.ts +0 -1
  76. package/src/voice/elevenLabsUtils.ts +0 -81
  77. package/src/voice/voiceSession.ts +0 -295
@@ -1,18 +0,0 @@
1
- import { BaseCheckpointSaver, MemorySaver } from '@langchain/langgraph';
2
- import { MindedCheckpointSaver } from '../platform/mindedCheckpointSaver';
3
- import * as mindedConnection from '../platform/mindedConnection';
4
- import { logger } from '../utils/logger';
5
-
6
- /**
7
- * Factory function to create the appropriate checkpoint saver based on environment variables.
8
- * @returns BaseCheckpointSaver instance (either MemorySaver or MindedCheckpointSaver)
9
- */
10
- export function createCheckpointSaver(): BaseCheckpointSaver {
11
- if (!mindedConnection.isConnected()) {
12
- logger.warn('mindedConnection not connected, falling back to in-memory checkpoint saver');
13
- return new MemorySaver();
14
- }
15
-
16
- logger.debug('Using remote checkpoint saver');
17
- return new MindedCheckpointSaver();
18
- }
package/src/cli/index.ts DELETED
@@ -1,170 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import * as fs from 'fs';
4
- import * as path from 'path';
5
- import { logger } from '../utils/logger';
6
- import { execSync } from 'child_process';
7
-
8
- const ENV_FILE = '.env';
9
-
10
- function getEnvFilePath(): string {
11
- return path.join(process.cwd(), ENV_FILE);
12
- }
13
-
14
- function setToken(token: string): void {
15
- const envPath = getEnvFilePath();
16
- const tokenLine = `MINDED_CONNECTION_TOKEN=${token}`;
17
-
18
- // Check if .env file exists
19
- if (!fs.existsSync(envPath)) {
20
- // Create .env file with the token
21
- fs.writeFileSync(envPath, tokenLine + '\n');
22
- logger.info(`Created ${envPath} and added token`);
23
- } else {
24
- // Read existing .env file
25
- const envContent = fs.readFileSync(envPath, 'utf8');
26
-
27
- // Check if MINDED_CONNECTION_TOKEN already exists
28
- if (envContent.includes('MINDED_CONNECTION_TOKEN=')) {
29
- // Replace existing token
30
- const updatedContent = envContent.replace(/MINDED_CONNECTION_TOKEN=.*/, tokenLine);
31
- fs.writeFileSync(envPath, updatedContent);
32
- logger.info(`Updated MINDED_CONNECTION_TOKEN in ${envPath}`);
33
- } else {
34
- // Append token to existing file
35
- const newContent = envContent.endsWith('\n') ? envContent + tokenLine + '\n' : envContent + '\n' + tokenLine + '\n';
36
- fs.writeFileSync(envPath, newContent);
37
- logger.info(`Added MINDED_CONNECTION_TOKEN to ${envPath}`);
38
- }
39
- }
40
- }
41
-
42
- function generateLambdaHandler(): void {
43
- const mindedConfigPath = path.join(process.cwd(), 'minded.json');
44
-
45
- // Check if minded.json exists
46
- if (!fs.existsSync(mindedConfigPath)) {
47
- logger.error('minded.json not found in the current directory');
48
- process.exit(1);
49
- }
50
-
51
- // Read and parse minded.json
52
- let mindedConfig;
53
- try {
54
- const configContent = fs.readFileSync(mindedConfigPath, 'utf8');
55
- mindedConfig = JSON.parse(configContent);
56
- } catch (error) {
57
- logger.error('Failed to read or parse minded.json:', error);
58
- process.exit(1);
59
- }
60
-
61
- // Get the agent path
62
- const agentPath = mindedConfig.agent;
63
- if (!agentPath) {
64
- logger.error('No agent path found in minded.json');
65
- process.exit(1);
66
- }
67
-
68
- // Remove .ts or .js extension if present and convert to relative path
69
- const modulePath = agentPath.replace(/\.(ts|js)$/, '');
70
-
71
- // Get the path to the template file
72
- // Try multiple locations to support both development and npm package scenarios
73
- const templatePaths = [
74
- path.join(__dirname, 'lambdaHandlerTemplate.ts'), // For npm package
75
- path.join(__dirname, '..', '..', 'src', 'cli', 'lambdaHandlerTemplate.ts'), // For development
76
- ];
77
-
78
- let templateContent: string | null = null;
79
-
80
- // Try each path until we find the template
81
- for (const templatePath of templatePaths) {
82
- try {
83
- if (fs.existsSync(templatePath)) {
84
- templateContent = fs.readFileSync(templatePath, 'utf8');
85
- logger.debug(`Found template at ${templatePath}`);
86
- break;
87
- }
88
- } catch {
89
- // Continue to next path
90
- }
91
- }
92
-
93
- if (!templateContent) {
94
- logger.error('Could not find Lambda handler template file');
95
- process.exit(1);
96
- }
97
-
98
- try {
99
- // Replace the placeholder with the actual module path
100
- templateContent = templateContent.replace(/{MODULE_PATH}/g, modulePath);
101
-
102
- // Write the Lambda handler to index.ts at the root
103
- const outputPath = path.join(process.cwd(), 'index.ts');
104
- fs.writeFileSync(outputPath, templateContent);
105
- logger.info(`Generated Lambda handler at ${outputPath}`);
106
-
107
- // Compile the index.ts file using TypeScript
108
- logger.info('Compiling the Lambda handler...');
109
-
110
- try {
111
- // Read tsconfig.json to get the outDir
112
- let outDir = 'dist'; // Default value
113
- const tsconfigPath = path.join(process.cwd(), 'tsconfig.json');
114
-
115
- if (fs.existsSync(tsconfigPath)) {
116
- try {
117
- const tsconfigContent = fs.readFileSync(tsconfigPath, 'utf8');
118
- const tsconfig = JSON.parse(tsconfigContent);
119
-
120
- if (tsconfig.compilerOptions && tsconfig.compilerOptions.outDir) {
121
- outDir = tsconfig.compilerOptions.outDir;
122
- logger.debug(`Using outDir from tsconfig.json: ${outDir}`);
123
- }
124
- } catch {
125
- logger.warn('Failed to parse tsconfig.json, using default outDir: dist');
126
- }
127
- }
128
-
129
- // Check if output directory exists, create if it doesn't
130
- const distDir = path.join(process.cwd(), outDir);
131
- if (!fs.existsSync(distDir)) {
132
- fs.mkdirSync(distDir, { recursive: true });
133
- }
134
-
135
- // Compile index.ts to the configured output directory
136
- execSync(`npx tsc --target ES2018 --module CommonJS --esModuleInterop ${outputPath} --outDir ${outDir}`, {
137
- stdio: 'pipe',
138
- cwd: process.cwd(),
139
- });
140
-
141
- logger.info(`Successfully compiled Lambda handler to ${outDir}/index.js`);
142
- } catch (compileError) {
143
- logger.error({ message: 'Failed to compile Lambda handler', error: compileError });
144
- }
145
- } catch (error) {
146
- logger.error({ message: 'Failed to generate Lambda handler', error: error });
147
- process.exit(1);
148
- }
149
- }
150
-
151
- function main() {
152
- const args = process.argv.slice(2);
153
- const command = args[0];
154
-
155
- if (command === 'token') {
156
- const token = args[1];
157
- if (!token) {
158
- logger.error('Please provide a token');
159
- process.exit(1);
160
- }
161
- setToken(token);
162
- } else if (command === 'generate-lambda-ts-handler') {
163
- generateLambdaHandler();
164
- } else {
165
- logger.error('Unknown command. Available commands: token, generate-lambda-ts-handler');
166
- process.exit(1);
167
- }
168
- }
169
-
170
- main();
@@ -1,46 +0,0 @@
1
- // This is the template for the Lambda handler
2
- // The {MODULE_PATH} placeholder will be replaced with the actual path
3
-
4
- export const handler = async (event: any, context: any) => {
5
- const modulePath = '{MODULE_PATH}'; // Module path placeholder - DO NOT CHANGE THE {MODULE_PATH} TOKEN
6
- // eslint-disable-next-line @typescript-eslint/no-require-imports
7
- const resolvedPath = require.resolve(modulePath);
8
- delete require.cache[resolvedPath];
9
- // eslint-disable-next-line @typescript-eslint/no-require-imports
10
- const agent = require(modulePath).default;
11
- const { agentFunctionName, agentFunctionBody } = event.body ? JSON.parse(event.body) : event;
12
-
13
- if (!agent[agentFunctionName]) {
14
- throw new Error('Unknown agentFunctionName: ' + agentFunctionName);
15
- }
16
-
17
- const response = await agent[agentFunctionName](agentFunctionBody);
18
-
19
- // Safely stringify the response. If it fails (e.g., due to circular refs
20
- // or non-serialisable class instances), fall back to null.
21
- let responseBody = null;
22
- try {
23
- JSON.stringify(response);
24
- responseBody = response;
25
- } catch {
26
- // ignored - responseBody stays null
27
- }
28
-
29
- if (agentFunctionName === 'startVoiceSession') {
30
- const wait = (ms: number) => new Promise((r) => setTimeout(r, ms));
31
- while (context.getRemainingTimeInMillis() > 1000) {
32
- console.log('Lambda remaining time in seconds: ', context.getRemainingTimeInMillis() / 1000);
33
- await wait(1000); // sleep 1 s
34
- }
35
- }
36
-
37
- return {
38
- statusCode: 200,
39
- headers: {
40
- 'Content-Type': 'application/json',
41
- 'Access-Control-Allow-Origin': '*',
42
- 'Access-Control-Allow-Credentials': true,
43
- },
44
- body: JSON.stringify(responseBody),
45
- };
46
- };
@@ -1,11 +0,0 @@
1
- import { StepForwardEdge } from '../types/Flows.types';
2
- import { logger } from '../utils/logger';
3
-
4
- export const createDirectEdge = (edge: StepForwardEdge) => {
5
- return async () => {
6
- // For direct edges, we just return the target of the first edge
7
- // since there's no conditional logic needed
8
- logger.info(`Executing edge ${JSON.stringify(edge)}`);
9
- return edge.target;
10
- };
11
- };
@@ -1,98 +0,0 @@
1
- import { LogicalConditionEdge } from '../types/Flows.types';
2
- import { stateAnnotation } from '../types/LangGraph.types';
3
- import { logger } from '../utils/logger';
4
- import * as vm from 'vm';
5
-
6
- // Default timeout for condition evaluation (in milliseconds)
7
- const CONDITION_TIMEOUT = 5000; // 5 seconds
8
-
9
- export const createLogicalRouter = ({ edges }: { edges: LogicalConditionEdge[] }) => {
10
- return async (state: typeof stateAnnotation.State) => {
11
- logger.debug(`Evaluating logical conditions for ${edges.length} edges`);
12
-
13
- // Separate regular conditions from "else" conditions
14
- const regularEdges = edges.filter(edge => edge.condition.trim() !== 'else');
15
- const elseEdges = edges.filter(edge => edge.condition.trim() === 'else');
16
-
17
- // First, evaluate all regular conditions
18
- for (const edge of regularEdges) {
19
- try {
20
- // Customer is responsible for providing valid JavaScript syntax
21
- // We execute their condition in a sandboxed VM with timeout protection
22
- const conditionCode = `
23
- (function({ state }) {
24
- return ${edge.condition}
25
- })
26
- `;
27
-
28
- // Create a sandboxed context with limited access
29
- const context = vm.createContext({
30
- // Provide safe utilities that customers might need
31
- console: {
32
- log: (...args: any[]) => logger.debug('Customer condition log:', ...args),
33
- error: (...args: any[]) => logger.debug('Customer condition error:', ...args),
34
- },
35
- // Add other safe globals if needed
36
- JSON,
37
- Math,
38
- Date,
39
- // Prevent access to dangerous globals
40
- process: undefined,
41
- require: undefined,
42
- global: undefined,
43
- Buffer: undefined,
44
- });
45
-
46
- // Compile and run the condition with timeout
47
- const script = new vm.Script(conditionCode, {
48
- filename: `edge-condition-${edge.source}-to-${edge.target}`,
49
- });
50
-
51
- const conditionFn = script.runInContext(context, {
52
- timeout: CONDITION_TIMEOUT,
53
- });
54
-
55
- // Execute the customer's condition with the current state
56
- const result = await Promise.race([
57
- Promise.resolve(conditionFn({ state })),
58
- new Promise((_, reject) => setTimeout(() => reject(new Error('Condition execution timeout')), CONDITION_TIMEOUT)),
59
- ]);
60
-
61
- if (result === true) {
62
- logger.info(`Condition matched for edge ${edge.source} → ${edge.target}`);
63
- return edge.target;
64
- }
65
-
66
- } catch (error) {
67
- // Provide detailed error information back to the customer
68
- const errorMessage = error instanceof Error ? error.message : String(error);
69
- const conditionPreview = edge.condition.length > 100 ? `${edge.condition.substring(0, 100)}...` : edge.condition;
70
-
71
- logger.error({
72
- msg: `Error evaluating condition for edge ${edge.source} → ${edge.target}`,
73
- condition: conditionPreview,
74
- error: errorMessage,
75
- edgeIndex: edges.indexOf(edge),
76
- timeout: CONDITION_TIMEOUT,
77
- });
78
-
79
- // Continue to next edge instead of failing completely
80
- // This allows other conditions to be evaluated even if one fails
81
- continue;
82
- }
83
- }
84
-
85
- // If no regular conditions matched, check for "else" conditions
86
- if (elseEdges.length > 0) {
87
- logger.info(`No regular conditions matched, evaluating ${elseEdges.length} else condition(s)`);
88
- // Return the first "else" condition's target
89
- const elseEdge = elseEdges[0];
90
- logger.info(`Else condition matched for edge ${elseEdge.source} → ${elseEdge.target}`);
91
- return elseEdge.target;
92
- }
93
-
94
- // If no conditions matched or all failed, return to the source node
95
- logger.info('No conditions matched');
96
- return null;
97
- };
98
- };
@@ -1,210 +0,0 @@
1
- import { z } from 'zod';
2
- import { PromptConditionEdge } from '../types/Flows.types';
3
- import { BaseLanguageModel } from '@langchain/core/language_models/base';
4
- import { SystemMessage } from '@langchain/core/messages';
5
- import { stateAnnotation } from '../types/LangGraph.types';
6
- import { logger } from '../utils/logger';
7
-
8
- const ROUTER_PROMPT = `
9
- You are a routing agent that decides which node to take in a flow based on the current state.
10
- Based on the conversation history and memory state, you should classify the next step for the conversation.
11
- Each step has a nodeId and a condition. You should decide to move to the right step based on the condition.
12
-
13
- Here are the available options and their conditions:
14
- {steps}
15
-
16
- Current memory state:
17
- {memory}
18
-
19
- Recent messages:
20
- {messages}
21
-
22
- You MUST respond with the exact nodeId from the options above. Do not use step numbers or any other identifiers.
23
-
24
- You should output the result in the following JSON format:
25
- {
26
- "nextNodeId": "exact_node_name_from_options_above",
27
- "reasoning": "I think the next step should be exact_node_name_from_options_above because of the following reasons: ..."
28
- }
29
-
30
- Return JSON and nothing more.
31
- `;
32
-
33
- const ROUTER_PROMPT_WITHOUT_REASONING = `
34
- You are a routing agent that decides which node to take in a flow based on the current state.
35
- Based on the conversation history and memory state, you should classify the next step for the conversation.
36
- Each step has a nodeId and a condition. You should decide to move to the right step based on the condition.
37
-
38
- Here are the available options and their conditions:
39
- {steps}
40
-
41
- Current memory state:
42
- {memory}
43
-
44
- Recent messages:
45
- {messages}
46
-
47
- You MUST respond with the exact nodeId from the options above. Do not use step numbers or any other identifiers.
48
-
49
- You should output the result in the following JSON format:
50
- {
51
- "nextNodeId": "exact_node_name_from_options_above"
52
- }
53
-
54
- Return JSON and nothing more.
55
- `;
56
-
57
- export const createPromptRouter = ({
58
- edges,
59
- llm,
60
- includeReasoning = true,
61
- maxRetries = 3,
62
- canStayInCurrentNode = false,
63
- currentNodeName,
64
- }: {
65
- edges: PromptConditionEdge[];
66
- llm: BaseLanguageModel;
67
- includeReasoning?: boolean;
68
- maxRetries?: number;
69
- canStayInCurrentNode?: boolean;
70
- currentNodeName?: string;
71
- }) => {
72
- return async (state: typeof stateAnnotation.State) => {
73
- logger.info(`Executing prompt router. Edges: ${JSON.stringify(edges)}`);
74
-
75
- // If canStayInCurrentNode is true and there are no edges, return current node immediately
76
- if (canStayInCurrentNode && edges.length === 0 && currentNodeName) {
77
- logger.info(`No edges available and canStayInCurrentNode is true, staying in current node: ${currentNodeName}`);
78
- return currentNodeName;
79
- }
80
-
81
- // Prepare the steps string
82
- let stepsStr = '';
83
-
84
- // Add "stay in current node" option if enabled
85
- if (canStayInCurrentNode && currentNodeName) {
86
- stepsStr = `- Node: "${currentNodeName}" (stay in current node)\n`;
87
- }
88
-
89
- // Add the edge options
90
- stepsStr += edges.map((edge) => `- Node: "${edge.target}" - Condition: "${edge.prompt}"`).join('\n');
91
-
92
- // Prepare recent messages (last 10 messages for context)
93
- const recentMessages = state.messages.slice(-10);
94
- const messagesStr = recentMessages
95
- .map((msg) => {
96
- const role = msg._getType();
97
- const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
98
- return `${role}: ${content}`;
99
- })
100
- .join('\n');
101
-
102
- // Select prompt based on reasoning preference
103
- let routerPrompt = includeReasoning ? ROUTER_PROMPT : ROUTER_PROMPT_WITHOUT_REASONING;
104
-
105
- // Replace placeholders in prompt
106
- routerPrompt = routerPrompt
107
- .replace('{steps}', stepsStr)
108
- .replace('{memory}', JSON.stringify(state.memory, null, 2))
109
- .replace('{messages}', messagesStr);
110
-
111
- // Define response schema
112
- const responseSchema = includeReasoning
113
- ? z.object({
114
- nextNodeId: z.string(),
115
- reasoning: z.string(),
116
- })
117
- : z.object({
118
- nextNodeId: z.string(),
119
- });
120
-
121
- let attempts = 0;
122
- let lastError: Error | null = null;
123
-
124
- while (attempts < maxRetries) {
125
- try {
126
- attempts++;
127
-
128
- // Create messages for the LLM
129
- const messages = [new SystemMessage(routerPrompt)];
130
-
131
- // If this is a retry, add the error message
132
- if (lastError) {
133
- messages.push(
134
- new SystemMessage(`Previous attempt failed with error: ${lastError.message}. Please provide a valid JSON response.`),
135
- );
136
- }
137
-
138
- // Call the LLM with structured output
139
- const response = await llm.invoke(messages);
140
-
141
- // Extract content from response
142
- let content = '';
143
- if (typeof response.content === 'string') {
144
- content = response.content;
145
- } else if (response.content && typeof response.content === 'object' && 'text' in response.content) {
146
- content = (response.content as any).text;
147
- }
148
-
149
- // Try to parse JSON from the response
150
- let parsedResponse;
151
- try {
152
- // Clean the response - remove markdown code blocks if present
153
- content = content
154
- .replace(/```json\n?/g, '')
155
- .replace(/```\n?/g, '')
156
- .trim();
157
- parsedResponse = JSON.parse(content);
158
- } catch {
159
- throw new Error(`Failed to parse JSON response: ${content}`);
160
- }
161
-
162
- // Validate the response
163
- const validatedResponse = responseSchema.parse(parsedResponse);
164
-
165
- // Check if the selected node is valid
166
- const validTargets = edges.map((e) => e.target);
167
- if (canStayInCurrentNode && currentNodeName) {
168
- validTargets.push(currentNodeName);
169
- }
170
-
171
- if (!validTargets.includes(validatedResponse.nextNodeId)) {
172
- throw new Error(`Invalid nextNodeId: ${validatedResponse.nextNodeId}. Must be one of: ${validTargets.join(', ')}`);
173
- }
174
-
175
- const decision = validatedResponse.nextNodeId === currentNodeName ? 'stay in current node' : validatedResponse.nextNodeId;
176
- const reasoning = includeReasoning && 'reasoning' in validatedResponse ? ` - Reasoning: ${validatedResponse.reasoning}` : '';
177
- logger.info({ msg: `Router decision: ${decision}`, reasoning });
178
-
179
- return validatedResponse.nextNodeId;
180
- } catch (error) {
181
- lastError = error instanceof Error ? error : new Error(String(error));
182
- logger.warn({
183
- message: `Prompt router attempt ${attempts} failed`,
184
- error: lastError.message,
185
- attempt: attempts,
186
- maxRetries,
187
- });
188
-
189
- if (attempts >= maxRetries) {
190
- // If all retries failed, return the first available edge as fallback
191
- const fallbackNode = edges[0]?.target;
192
- logger.error({
193
- message: 'Prompt router reached max retries, using fallback',
194
- fallbackNode,
195
- lastError: lastError.message,
196
- });
197
-
198
- if (!fallbackNode) {
199
- throw new Error('No edges available for routing');
200
- }
201
-
202
- return fallbackNode;
203
- }
204
- }
205
- }
206
-
207
- // This should never be reached, but TypeScript needs it
208
- throw new Error('Unexpected error in prompt router');
209
- };
210
- };
@@ -1,125 +0,0 @@
1
- import { Edge, EdgeType, Node, PromptConditionEdge, LogicalConditionEdge, StepForwardEdge, NodeType, ToolNode } 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
- import { addToolRunNode, buildToolRunNodeName } from '../nodes/addToolRunNode';
9
- import { Tool } from '../types/Tools.types';
10
- import { Agent } from '../agent';
11
- import { logger } from '../utils/logger';
12
-
13
- type EdgesBySource = {
14
- stepForward?: StepForwardEdge;
15
- logical: LogicalConditionEdge[];
16
- prompt: PromptConditionEdge[];
17
- };
18
-
19
- export const edgeFactory = ({
20
- graph,
21
- edges,
22
- nodes,
23
- tools,
24
- llm,
25
- agent,
26
- }: {
27
- graph: PreCompiledGraph;
28
- edges: Edge[];
29
- nodes: Record<string, Node>;
30
- tools: Tool<any, any>[];
31
- llm: BaseLanguageModel;
32
- agent: Agent;
33
- }) => {
34
- /**
35
- * Creates a combined router that handles edge priority: step forward > logical > prompt
36
- */
37
- const createCombinedRouter = (sourceNode: string, edgesBySource: EdgesBySource, originalNode: Node) => {
38
- return async (state: any) => {
39
- // Priority 1: Step forward edge (max 1)
40
- if (edgesBySource.stepForward) {
41
- const directRouter = createDirectEdge(edgesBySource.stepForward);
42
- return await directRouter();
43
- }
44
-
45
- // Priority 2: Logical condition edges
46
- if (edgesBySource.logical.length > 0) {
47
- const logicalRouter = createLogicalRouter({ edges: edgesBySource.logical });
48
- const result = await logicalRouter(state);
49
- if (result) {
50
- return result;
51
- } else {
52
- logger.debug('No logical conditions matched, continuing to prompt conditions');
53
- }
54
- }
55
-
56
- // Priority 3: Prompt condition edges
57
- if (edgesBySource.prompt.length > 0) {
58
- // Use the original node to check canStayOnNode property
59
- const canStayInCurrentNode = originalNode?.canStayOnNode === true;
60
-
61
- const promptRouter = createPromptRouter({
62
- edges: edgesBySource.prompt,
63
- llm,
64
- canStayInCurrentNode,
65
- currentNodeName: originalNode?.name || sourceNode,
66
- });
67
- return await promptRouter(state);
68
- }
69
-
70
- // Fallback: stay at current source node
71
- const source = originalNode?.name || sourceNode;
72
- logger.info(`No conditions matched, returning to source: ${source}`);
73
- return source;
74
- };
75
- };
76
-
77
- /**
78
- * Processes all edges for a specific source node with enhanced capabilities
79
- */
80
- const processEdgesForNode = (source: string, edgesBySource: EdgesBySource): void => {
81
- let effectiveSource = source;
82
- const originalNode = nodes[source];
83
-
84
- // Handle human-in-the-loop enhancement
85
- if (originalNode?.humanInTheLoop) {
86
- addHumanInTheLoopNode({ graph, attachedToNodeName: source });
87
- effectiveSource = buildHumanInTheLoopNodeName(source);
88
- }
89
-
90
- if (originalNode?.type === NodeType.TOOL) {
91
- addToolRunNode({ graph, tools, llm, toolNode: originalNode as ToolNode, attachedToNodeName: source, agent });
92
- effectiveSource = buildToolRunNodeName(source);
93
- }
94
-
95
- // Create and add the combined conditional edge with original node reference
96
- const combinedRouter = createCombinedRouter(effectiveSource, edgesBySource, originalNode);
97
- graph.addConditionalEdges(effectiveSource as any, combinedRouter);
98
- };
99
-
100
- // Get all unique source nodes and categorize their edges in a single pass
101
- const edgesBySource = edges.reduce((acc, edge) => {
102
- if (!acc[edge.source]) {
103
- acc[edge.source] = {
104
- stepForward: undefined,
105
- logical: [],
106
- prompt: [],
107
- };
108
- }
109
-
110
- if (edge.type === EdgeType.STEP_FORWARD) {
111
- acc[edge.source].stepForward = edge as StepForwardEdge;
112
- } else if (edge.type === EdgeType.LOGICAL_CONDITION) {
113
- acc[edge.source].logical.push(edge as LogicalConditionEdge);
114
- } else if (edge.type === EdgeType.PROMPT_CONDITION) {
115
- acc[edge.source].prompt.push(edge as PromptConditionEdge);
116
- }
117
-
118
- return acc;
119
- }, {} as Record<string, EdgesBySource>);
120
-
121
- // Process edges for each source node
122
- Object.entries(edgesBySource).forEach(([source, categorizedEdges]) => {
123
- processEdgesForNode(source, categorizedEdges);
124
- });
125
- };