@ema.co/mcp-toolkit 2026.1.27 → 2026.1.28-2
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.
Potentially problematic release.
This version of @ema.co/mcp-toolkit might be problematic. Click here for more details.
- package/dist/mcp/handlers/data/index.js +3 -0
- package/dist/mcp/handlers/persona/create.js +16 -0
- package/dist/mcp/handlers/persona/list.js +9 -4
- package/dist/mcp/handlers/persona/update.js +24 -2
- package/dist/mcp/handlers/workflow/deploy.js +20 -2
- package/dist/mcp/handlers/workflow/generate.js +39 -2
- package/dist/mcp/handlers/workflow/index.js +8 -3
- package/dist/mcp/handlers/workflow/modify.js +34 -7
- package/dist/mcp/handlers/workflow/validate.js +85 -0
- package/dist/mcp/handlers/workflow/validation.js +160 -0
- package/dist/mcp/resources.js +286 -4
- package/dist/mcp/server.js +16 -3
- package/dist/mcp/tools.js +32 -11
- package/dist/sdk/client.js +36 -9
- package/dist/sdk/ema-client.js +32 -4
- package/dist/sdk/index.js +3 -1
- package/dist/sdk/knowledge.js +5 -5
- package/dist/sdk/structural-rules.js +498 -0
- package/dist/sdk/workflow-generator.js +2 -1
- package/dist/sdk/workflow-intent.js +28 -96
- package/dist/sdk/workflow-path-enumerator.js +278 -0
- package/dist/sdk/workflow-static-validator.js +291 -0
- package/dist/sdk/workflow-validation-types.js +7 -0
- package/docs/README.md +14 -0
- package/docs/go-validator-analysis.md +323 -0
- package/docs/rule-format-specification.md +346 -0
- package/docs/validation-contract.md +397 -0
- package/docs/validation-error-format.md +326 -0
- package/package.json +1 -1
- package/dist/mcp/workflow-operations.js +0 -100
- package/dist/sdk/workflow-fixer.js +0 -48
- package/docs/dashboard-operations.md +0 -281
- package/docs/ema-user-guide.md +0 -1201
- package/docs/email-patterns.md +0 -120
- package/docs/mcp-tools-guide.md +0 -575
|
@@ -156,22 +156,8 @@ export function detectInputType(input) {
|
|
|
156
156
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
157
157
|
// Natural Language Parsing
|
|
158
158
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
{ pattern: /billing|payment|invoice/i, name: "Billing", handler: "search" },
|
|
162
|
-
{ pattern: /technical|support|help/i, name: "Technical Support", handler: "search" },
|
|
163
|
-
{ pattern: /schedule|appointment|book/i, name: "Scheduling", handler: "tool" },
|
|
164
|
-
{ pattern: /ticket|incident|request/i, name: "Ticket Creation", handler: "tool" },
|
|
165
|
-
{ pattern: /status|check|lookup/i, name: "Status Check", handler: "search" },
|
|
166
|
-
];
|
|
167
|
-
const TOOL_PATTERNS = [
|
|
168
|
-
{ pattern: /servicenow/i, namespace: "service_now", action: "Create_Incident" },
|
|
169
|
-
{ pattern: /salesforce/i, namespace: "salesforce", action: "Create_Case" },
|
|
170
|
-
{ pattern: /jira/i, namespace: "jira", action: "Create_Issue" },
|
|
171
|
-
{ pattern: /slack/i, namespace: "slack", action: "Send_Message" },
|
|
172
|
-
{ pattern: /email/i, namespace: "email", action: "Send_Email" },
|
|
173
|
-
{ pattern: /calendar|schedule/i, namespace: "calendar", action: "Check_Availability" },
|
|
174
|
-
];
|
|
159
|
+
// INTENT_PATTERNS and TOOL_PATTERNS removed - were only used by parseNaturalLanguage()
|
|
160
|
+
// which violated LLM-driven architecture. Agent should understand intent naturally.
|
|
175
161
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
176
162
|
// Action Chain Patterns - End-to-End Semantic Flows
|
|
177
163
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -529,83 +515,26 @@ export function detectActionChains(text) {
|
|
|
529
515
|
}
|
|
530
516
|
return detected.filter(Boolean);
|
|
531
517
|
}
|
|
532
|
-
|
|
533
|
-
{ pattern: /voice|call|phone/i, type: "voice" },
|
|
534
|
-
{ pattern: /document|batch|upload/i, type: "dashboard" },
|
|
535
|
-
];
|
|
518
|
+
// PERSONA_TYPE_PATTERNS removed - no longer used after parseNaturalLanguage() removal
|
|
536
519
|
/**
|
|
537
|
-
*
|
|
520
|
+
* REMOVED: parseNaturalLanguage() - regex-based NL parsing violates LLM-driven architecture
|
|
538
521
|
*
|
|
539
|
-
* This function
|
|
540
|
-
*
|
|
541
|
-
* and provide structured data directly.
|
|
522
|
+
* This function was removed because it violates the core principle:
|
|
523
|
+
* "THE AGENT (LLM) DOES THE THINKING. THE MCP PROVIDES CONTEXT AND EXECUTES."
|
|
542
524
|
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
* -
|
|
525
|
+
* Natural language input should be handled by:
|
|
526
|
+
* 1. Intent Architect (runIntentArchitect) - analyzes complexity and returns LLM prompts
|
|
527
|
+
* 2. generateWorkflow() - takes WorkflowIntent and returns LLM prompts for complex cases
|
|
528
|
+
* 3. Direct structured input (WorkflowSpec) from the Agent
|
|
546
529
|
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
530
|
+
* For natural language input, create a minimal WorkflowIntent and let Intent Architect
|
|
531
|
+
* or generateWorkflow() handle the complexity analysis.
|
|
549
532
|
*
|
|
550
533
|
* @see src/mcp/AGENTS.md for the LLM-driven architecture guidelines
|
|
551
534
|
*/
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
// Detect persona type (default: chat)
|
|
556
|
-
let personaType = "chat";
|
|
557
|
-
for (const { pattern, type } of PERSONA_TYPE_PATTERNS) {
|
|
558
|
-
if (pattern.test(text)) {
|
|
559
|
-
personaType = type;
|
|
560
|
-
break;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
// Detect intents
|
|
564
|
-
const intents = [];
|
|
565
|
-
const seenIntents = new Set();
|
|
566
|
-
for (const { pattern, name, handler } of INTENT_PATTERNS) {
|
|
567
|
-
if (pattern.test(text) && !seenIntents.has(name)) {
|
|
568
|
-
seenIntents.add(name);
|
|
569
|
-
intents.push({
|
|
570
|
-
name,
|
|
571
|
-
description: `User requests ${name.toLowerCase()}`,
|
|
572
|
-
handler,
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
// Detect tools from text
|
|
577
|
-
const tools = [];
|
|
578
|
-
for (const { pattern, namespace, action } of TOOL_PATTERNS) {
|
|
579
|
-
if (pattern.test(text)) {
|
|
580
|
-
tools.push({ namespace, action });
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
// Detect data sources
|
|
584
|
-
const dataSources = [];
|
|
585
|
-
if (/search|knowledge|faq|document|lookup|kb/i.test(text)) {
|
|
586
|
-
dataSources.push({ type: "knowledge_base" });
|
|
587
|
-
}
|
|
588
|
-
if (/web|internet|online|live/i.test(text)) {
|
|
589
|
-
dataSources.push({ type: "web_search" });
|
|
590
|
-
}
|
|
591
|
-
if (dataSources.length === 0) {
|
|
592
|
-
// Default to KB search for most use cases
|
|
593
|
-
dataSources.push({ type: "knowledge_base" });
|
|
594
|
-
}
|
|
595
|
-
// Detect constraints
|
|
596
|
-
const constraints = {};
|
|
597
|
-
if (/approv|human|review|escalat|hitl/i.test(text)) {
|
|
598
|
-
constraints.require_hitl = true;
|
|
599
|
-
}
|
|
600
|
-
if (/validat|check|verify/i.test(text)) {
|
|
601
|
-
constraints.require_validation = true;
|
|
602
|
-
}
|
|
603
|
-
// Detect action chains - end-to-end semantic flows
|
|
604
|
-
const actionChains = detectActionChains(text);
|
|
605
|
-
// Detect entities to extract
|
|
606
|
-
const entities = detectEntities(text);
|
|
607
|
-
// Detect delivery configuration
|
|
608
|
-
const deliveryConfig = detectDeliveryConfig(text);
|
|
535
|
+
function createMinimalIntentFromText(text, personaType = "chat") {
|
|
536
|
+
// Create minimal intent - no regex parsing
|
|
537
|
+
// Intent Architect and generateWorkflow() will handle complexity analysis
|
|
609
538
|
// Extract a name from the text (first sentence or first N words)
|
|
610
539
|
const sentences = text.split(/[.!?]/);
|
|
611
540
|
const name = sentences[0].trim().slice(0, 50) || "AI Employee";
|
|
@@ -613,13 +542,6 @@ export function parseNaturalLanguage(text) {
|
|
|
613
542
|
name,
|
|
614
543
|
description: text,
|
|
615
544
|
persona_type: personaType,
|
|
616
|
-
intents: intents.length > 0 ? intents : undefined,
|
|
617
|
-
tools: tools.length > 0 ? tools : undefined,
|
|
618
|
-
data_sources: dataSources,
|
|
619
|
-
constraints: Object.keys(constraints).length > 0 ? constraints : undefined,
|
|
620
|
-
action_chains: actionChains.length > 0 ? actionChains : undefined,
|
|
621
|
-
entities: entities.length > 0 ? entities : undefined,
|
|
622
|
-
delivery_config: deliveryConfig,
|
|
623
545
|
};
|
|
624
546
|
}
|
|
625
547
|
/**
|
|
@@ -1527,9 +1449,19 @@ export function parseInput(input) {
|
|
|
1527
1449
|
let intent;
|
|
1528
1450
|
switch (inputType) {
|
|
1529
1451
|
case "natural_language":
|
|
1530
|
-
//
|
|
1531
|
-
//
|
|
1532
|
-
|
|
1452
|
+
// Create minimal intent - no regex parsing
|
|
1453
|
+
// Intent Architect and generateWorkflow() will handle complexity
|
|
1454
|
+
const text = String(input);
|
|
1455
|
+
// Detect persona type from args if available, otherwise default to chat
|
|
1456
|
+
let personaType = "chat";
|
|
1457
|
+
// Simple detection - can be overridden by args.type in handler
|
|
1458
|
+
if (/voice|call|phone/i.test(text)) {
|
|
1459
|
+
personaType = "voice";
|
|
1460
|
+
}
|
|
1461
|
+
else if (/document|batch|upload/i.test(text)) {
|
|
1462
|
+
personaType = "dashboard";
|
|
1463
|
+
}
|
|
1464
|
+
intent = createMinimalIntentFromText(text, personaType);
|
|
1533
1465
|
break;
|
|
1534
1466
|
case "partial_spec":
|
|
1535
1467
|
intent = parsePartialSpec(input);
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Path Enumeration
|
|
3
|
+
*
|
|
4
|
+
* Implements the Go validator's path enumeration algorithm for static validation.
|
|
5
|
+
* Enumerates all possible execution paths in a workflow by:
|
|
6
|
+
* 1. Traversing workflow graph topologically
|
|
7
|
+
* 2. Forking sessions on branching actions (categorizers, enums, booleans)
|
|
8
|
+
* 3. Tracking completed paths with their outputs
|
|
9
|
+
*
|
|
10
|
+
* Based on: workflow-engine/pkg/engine/validator.go
|
|
11
|
+
*/
|
|
12
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
// Path Enumeration
|
|
14
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* Enumerate all possible execution paths in a workflow.
|
|
17
|
+
*
|
|
18
|
+
* Algorithm:
|
|
19
|
+
* 1. Create initial session
|
|
20
|
+
* 2. Queue initial session
|
|
21
|
+
* 3. While queue not empty:
|
|
22
|
+
* a. Dequeue session
|
|
23
|
+
* b. Run validation on session
|
|
24
|
+
* c. If branching: fork for each possible value, queue forked sessions
|
|
25
|
+
* d. If completed: validate named results, store path info
|
|
26
|
+
* 4. Return completed paths
|
|
27
|
+
*/
|
|
28
|
+
export function enumeratePaths(workflow, options) {
|
|
29
|
+
const maxPaths = options?.max_paths ?? 1000;
|
|
30
|
+
const timeout = options?.timeout_ms ?? 100;
|
|
31
|
+
const startTime = Date.now();
|
|
32
|
+
const completedPaths = [];
|
|
33
|
+
const sessionsToProcess = [createInitialSession(workflow)];
|
|
34
|
+
while (sessionsToProcess.length > 0) {
|
|
35
|
+
// Check timeout
|
|
36
|
+
if (Date.now() - startTime > timeout) {
|
|
37
|
+
throw new Error(`Path enumeration timeout after ${timeout}ms`);
|
|
38
|
+
}
|
|
39
|
+
// Check max paths
|
|
40
|
+
if (completedPaths.length >= maxPaths) {
|
|
41
|
+
throw new Error(`Path enumeration exceeded max paths limit: ${maxPaths}`);
|
|
42
|
+
}
|
|
43
|
+
// Dequeue session
|
|
44
|
+
const session = sessionsToProcess.shift();
|
|
45
|
+
// Run validation on session
|
|
46
|
+
const result = runValidationOnSession(session, workflow);
|
|
47
|
+
if (result.outcome === "branch") {
|
|
48
|
+
// Fork session for each possible value
|
|
49
|
+
if (!result.branch_point) {
|
|
50
|
+
throw new Error("Branch outcome without branch_point");
|
|
51
|
+
}
|
|
52
|
+
for (const value of result.branch_point.possible_values) {
|
|
53
|
+
const forkedSession = forkSessionForBranching(session, result.branch_point, value);
|
|
54
|
+
sessionsToProcess.push(forkedSession);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (result.outcome === "completed") {
|
|
58
|
+
// Store completed path
|
|
59
|
+
const pathInfo = {
|
|
60
|
+
completed_actions: result.completed_actions || [],
|
|
61
|
+
final_outputs: result.final_outputs || {},
|
|
62
|
+
action_name: session.last_branching_action?.action_that_branched ||
|
|
63
|
+
(result.completed_actions?.[result.completed_actions.length - 1] || ""),
|
|
64
|
+
enumerable_output: session.last_branching_action?.branching_output_name,
|
|
65
|
+
chosen_enumerable_value: session.last_branching_action?.chosen_value,
|
|
66
|
+
validation_failed: result.validation_failed || false,
|
|
67
|
+
};
|
|
68
|
+
completedPaths.push(pathInfo);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return completedPaths;
|
|
72
|
+
}
|
|
73
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
74
|
+
// Session Management
|
|
75
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
76
|
+
function createInitialSession(workflow) {
|
|
77
|
+
return {
|
|
78
|
+
completed_actions: [],
|
|
79
|
+
available_outputs: new Map(),
|
|
80
|
+
action_states: new Map(),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function forkSessionForBranching(session, branchInfo, chosenValue) {
|
|
84
|
+
// Deep copy session
|
|
85
|
+
const forked = {
|
|
86
|
+
completed_actions: [...session.completed_actions],
|
|
87
|
+
available_outputs: new Map(session.available_outputs),
|
|
88
|
+
action_states: new Map(session.action_states),
|
|
89
|
+
last_branching_action: {
|
|
90
|
+
action_that_branched: branchInfo.action_that_branched,
|
|
91
|
+
branching_output_name: branchInfo.branching_output_name,
|
|
92
|
+
possible_values: branchInfo.possible_values,
|
|
93
|
+
chosen_value: chosenValue,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
// Mark branching action as completed
|
|
97
|
+
forked.action_states.set(branchInfo.action_that_branched, "completed");
|
|
98
|
+
forked.completed_actions.push(branchInfo.action_that_branched);
|
|
99
|
+
// Add branching output to available outputs
|
|
100
|
+
const outputKey = `${branchInfo.action_that_branched}.${branchInfo.branching_output_name}`;
|
|
101
|
+
forked.available_outputs.set(outputKey, {
|
|
102
|
+
node_id: branchInfo.action_that_branched,
|
|
103
|
+
output_name: branchInfo.branching_output_name,
|
|
104
|
+
});
|
|
105
|
+
// TODO: Generate dummy outputs for other outputs of branching action
|
|
106
|
+
// TODO: Generate dummy outputs for non-branching actions
|
|
107
|
+
return forked;
|
|
108
|
+
}
|
|
109
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
110
|
+
// Validation Execution
|
|
111
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
112
|
+
function runValidationOnSession(session, workflow) {
|
|
113
|
+
// Find next actions to execute (topological order)
|
|
114
|
+
const nextActions = findExecutableActions(session, workflow);
|
|
115
|
+
if (nextActions.length === 0) {
|
|
116
|
+
// No more actions to execute - path is complete
|
|
117
|
+
return {
|
|
118
|
+
outcome: "completed",
|
|
119
|
+
completed_actions: session.completed_actions,
|
|
120
|
+
final_outputs: extractFinalOutputs(session),
|
|
121
|
+
validation_failed: false,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
// Check for branching actions
|
|
125
|
+
for (const action of nextActions) {
|
|
126
|
+
const branchInfo = detectBranchingAction(action, workflow);
|
|
127
|
+
if (branchInfo) {
|
|
128
|
+
return {
|
|
129
|
+
outcome: "branch",
|
|
130
|
+
branch_point: branchInfo,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Execute non-branching actions and track outputs
|
|
135
|
+
for (const action of nextActions) {
|
|
136
|
+
session.action_states.set(action.id, "completed");
|
|
137
|
+
session.completed_actions.push(action.id);
|
|
138
|
+
// Track outputs from this action
|
|
139
|
+
// For validation, we mark that this node produces outputs
|
|
140
|
+
// The actual output name depends on action type - use common patterns
|
|
141
|
+
addActionOutputs(session, action);
|
|
142
|
+
}
|
|
143
|
+
// Continue traversal
|
|
144
|
+
return runValidationOnSession(session, workflow);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Add outputs from an action to the session's available_outputs.
|
|
148
|
+
* Maps action types to their known outputs.
|
|
149
|
+
*/
|
|
150
|
+
function addActionOutputs(session, action) {
|
|
151
|
+
// Common outputs by action type (simplified - LLM knows the full schema)
|
|
152
|
+
const outputsByType = {
|
|
153
|
+
chat_trigger: ["user_query", "conversation_id"],
|
|
154
|
+
document_trigger: ["document_content"],
|
|
155
|
+
search: ["search_results", "sources"],
|
|
156
|
+
call_llm: ["llm_response", "response"],
|
|
157
|
+
respond_with_sources: ["response_with_sources"],
|
|
158
|
+
fixed_response: ["response"],
|
|
159
|
+
external_action_caller: ["action_result"],
|
|
160
|
+
chat_categorizer: ["category", "confidence"],
|
|
161
|
+
text_categorizer: ["category", "confidence"],
|
|
162
|
+
};
|
|
163
|
+
const outputs = outputsByType[action.actionType] || ["output"];
|
|
164
|
+
for (const outputName of outputs) {
|
|
165
|
+
const key = `${action.id}.${outputName}`;
|
|
166
|
+
session.available_outputs.set(key, {
|
|
167
|
+
node_id: action.id,
|
|
168
|
+
output_name: outputName,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
173
|
+
// Helper Functions
|
|
174
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
175
|
+
function findExecutableActions(session, workflow) {
|
|
176
|
+
// Find actions whose inputs are all satisfied
|
|
177
|
+
const executable = [];
|
|
178
|
+
for (const node of workflow.nodes || []) {
|
|
179
|
+
if (session.action_states.get(node.id) === "completed") {
|
|
180
|
+
continue; // Already executed
|
|
181
|
+
}
|
|
182
|
+
// Check if all inputs are available
|
|
183
|
+
const allInputsSatisfied = checkInputsSatisfied(node, session, workflow);
|
|
184
|
+
if (allInputsSatisfied) {
|
|
185
|
+
executable.push(node);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return executable;
|
|
189
|
+
}
|
|
190
|
+
function checkInputsSatisfied(node, session, _workflow) {
|
|
191
|
+
// Triggers have no dependencies - always executable first
|
|
192
|
+
if (node.actionType.includes("trigger")) {
|
|
193
|
+
return session.completed_actions.length === 0; // Only at start
|
|
194
|
+
}
|
|
195
|
+
// Check if all input sources are completed
|
|
196
|
+
if (!node.inputs) {
|
|
197
|
+
return true; // No inputs required
|
|
198
|
+
}
|
|
199
|
+
for (const [_inputName, binding] of Object.entries(node.inputs)) {
|
|
200
|
+
// Literal/inline inputs are always available
|
|
201
|
+
if (binding.type === "literal" ||
|
|
202
|
+
binding.type === "inline_string" ||
|
|
203
|
+
binding.type === "inline_number" ||
|
|
204
|
+
binding.type === "inline_bool" ||
|
|
205
|
+
binding.type === "llm_inferred") {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
// Action output - check if source action is completed
|
|
209
|
+
if (binding.type === "action_output" && binding.actionName) {
|
|
210
|
+
if (session.action_states.get(binding.actionName) !== "completed") {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
function detectBranchingAction(node, _workflow) {
|
|
218
|
+
// Categorizers branch based on category selection
|
|
219
|
+
// LLM already knows this from rules - this is just the automated check
|
|
220
|
+
if (node.actionType === "chat_categorizer" ||
|
|
221
|
+
node.actionType === "text_categorizer") {
|
|
222
|
+
const categories = node.categories || [];
|
|
223
|
+
if (categories.length > 0) {
|
|
224
|
+
return {
|
|
225
|
+
action_that_branched: node.id,
|
|
226
|
+
branching_output_name: "category",
|
|
227
|
+
possible_values: categories.map((c) => c.name),
|
|
228
|
+
chosen_value: undefined,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Note: Boolean/enum outputs could also branch, but categorizers are the
|
|
233
|
+
// primary branching mechanism in workflows. LLM handles other cases via rules.
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
function extractFinalOutputs(session) {
|
|
237
|
+
const outputs = {};
|
|
238
|
+
// Keys are already in "nodeId.outputName" format from addActionOutputs
|
|
239
|
+
for (const [key, value] of session.available_outputs.entries()) {
|
|
240
|
+
outputs[key] = { node_id: value.node_id, output_name: value.output_name };
|
|
241
|
+
}
|
|
242
|
+
return outputs;
|
|
243
|
+
}
|
|
244
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
245
|
+
// Branching Detection
|
|
246
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
247
|
+
/**
|
|
248
|
+
* Check if an output type is enumerable (can be used for branching).
|
|
249
|
+
* Enumerable types: enum, boolean
|
|
250
|
+
*/
|
|
251
|
+
export function isOutputTypeEnumerable(outputType) {
|
|
252
|
+
// Enum types
|
|
253
|
+
if (outputType.includes("enum") || outputType.includes("Enum")) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
// Boolean types
|
|
257
|
+
if (outputType === "WELL_KNOWN_TYPE_BOOL" || outputType === "bool" || outputType === "boolean") {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Get all possible values for an enumerable output.
|
|
264
|
+
* For enums: all enum options
|
|
265
|
+
* For booleans: [true, false]
|
|
266
|
+
*/
|
|
267
|
+
export function getAllPossibleValuesForEnumerableOutput(outputType, enumType) {
|
|
268
|
+
if (outputType.includes("enum") || outputType.includes("Enum")) {
|
|
269
|
+
if (enumType?.options) {
|
|
270
|
+
return enumType.options.map(opt => opt.name);
|
|
271
|
+
}
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
if (outputType === "WELL_KNOWN_TYPE_BOOL" || outputType === "bool" || outputType === "boolean") {
|
|
275
|
+
return [true, false];
|
|
276
|
+
}
|
|
277
|
+
return [];
|
|
278
|
+
}
|