@mcpilotx/intentorch 0.5.0
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.
- package/LICENSE +201 -0
- package/README.md +545 -0
- package/dist/ai/ai.d.ts +205 -0
- package/dist/ai/ai.js +1200 -0
- package/dist/ai/cloud-intent-engine.d.ts +270 -0
- package/dist/ai/cloud-intent-engine.js +956 -0
- package/dist/ai/command.d.ts +59 -0
- package/dist/ai/command.js +285 -0
- package/dist/ai/config.d.ts +66 -0
- package/dist/ai/config.js +211 -0
- package/dist/ai/enhanced-intent.d.ts +17 -0
- package/dist/ai/enhanced-intent.js +32 -0
- package/dist/ai/index.d.ts +29 -0
- package/dist/ai/index.js +44 -0
- package/dist/ai/intent.d.ts +16 -0
- package/dist/ai/intent.js +30 -0
- package/dist/core/ai-config.d.ts +25 -0
- package/dist/core/ai-config.js +326 -0
- package/dist/core/config-manager.d.ts +36 -0
- package/dist/core/config-manager.js +400 -0
- package/dist/core/config-validator.d.ts +9 -0
- package/dist/core/config-validator.js +184 -0
- package/dist/core/constants.d.ts +34 -0
- package/dist/core/constants.js +37 -0
- package/dist/core/error-ai.d.ts +23 -0
- package/dist/core/error-ai.js +217 -0
- package/dist/core/error-handler.d.ts +197 -0
- package/dist/core/error-handler.js +467 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +17 -0
- package/dist/core/logger.d.ts +27 -0
- package/dist/core/logger.js +108 -0
- package/dist/core/performance-monitor.d.ts +74 -0
- package/dist/core/performance-monitor.js +260 -0
- package/dist/core/providers.d.ts +36 -0
- package/dist/core/providers.js +304 -0
- package/dist/core/retry-manager.d.ts +41 -0
- package/dist/core/retry-manager.js +204 -0
- package/dist/core/types.d.ts +155 -0
- package/dist/core/types.js +2 -0
- package/dist/daemon/index.d.ts +10 -0
- package/dist/daemon/index.js +15 -0
- package/dist/daemon/intent-engine.d.ts +22 -0
- package/dist/daemon/intent-engine.js +50 -0
- package/dist/daemon/orchestrator.d.ts +24 -0
- package/dist/daemon/orchestrator.js +100 -0
- package/dist/daemon/pm.d.ts +33 -0
- package/dist/daemon/pm.js +127 -0
- package/dist/daemon/process.d.ts +11 -0
- package/dist/daemon/process.js +49 -0
- package/dist/daemon/server.d.ts +17 -0
- package/dist/daemon/server.js +435 -0
- package/dist/daemon/service.d.ts +36 -0
- package/dist/daemon/service.js +278 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +36 -0
- package/dist/mcp/client.d.ts +51 -0
- package/dist/mcp/client.js +276 -0
- package/dist/mcp/index.d.ts +162 -0
- package/dist/mcp/index.js +199 -0
- package/dist/mcp/tool-registry.d.ts +71 -0
- package/dist/mcp/tool-registry.js +308 -0
- package/dist/mcp/transport.d.ts +83 -0
- package/dist/mcp/transport.js +515 -0
- package/dist/mcp/types.d.ts +136 -0
- package/dist/mcp/types.js +31 -0
- package/dist/runtime/adapter-advanced.d.ts +184 -0
- package/dist/runtime/adapter-advanced.js +160 -0
- package/dist/runtime/adapter.d.ts +9 -0
- package/dist/runtime/adapter.js +2 -0
- package/dist/runtime/detector-advanced.d.ts +59 -0
- package/dist/runtime/detector-advanced.js +487 -0
- package/dist/runtime/detector.d.ts +5 -0
- package/dist/runtime/detector.js +56 -0
- package/dist/runtime/docker-adapter.d.ts +18 -0
- package/dist/runtime/docker-adapter.js +170 -0
- package/dist/runtime/docker.d.ts +17 -0
- package/dist/runtime/docker.js +71 -0
- package/dist/runtime/executable-analyzer.d.ts +56 -0
- package/dist/runtime/executable-analyzer.js +391 -0
- package/dist/runtime/go-adapter.d.ts +19 -0
- package/dist/runtime/go-adapter.js +190 -0
- package/dist/runtime/index.d.ts +9 -0
- package/dist/runtime/index.js +10 -0
- package/dist/runtime/node-adapter.d.ts +10 -0
- package/dist/runtime/node-adapter.js +23 -0
- package/dist/runtime/node.d.ts +20 -0
- package/dist/runtime/node.js +86 -0
- package/dist/runtime/python-adapter.d.ts +11 -0
- package/dist/runtime/python-adapter.js +102 -0
- package/dist/runtime/python.d.ts +17 -0
- package/dist/runtime/python.js +72 -0
- package/dist/runtime/rust-adapter.d.ts +21 -0
- package/dist/runtime/rust-adapter.js +267 -0
- package/dist/sdk.d.ts +500 -0
- package/dist/sdk.js +904 -0
- package/docs/README.ZH_CN.md +545 -0
- package/docs/api.md +888 -0
- package/docs/architecture.md +731 -0
- package/docs/development.md +744 -0
- package/package.json +112 -0
|
@@ -0,0 +1,956 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud LLM Intent Engine
|
|
3
|
+
* Cloud LLM-based intent parsing and MCP capability auto-mapping engine
|
|
4
|
+
*
|
|
5
|
+
* Core capabilities:
|
|
6
|
+
* 1. Decompose natural language instructions into atomic intents (with parameters)
|
|
7
|
+
* 2. Infer dependencies between atomic intents (generate DAG)
|
|
8
|
+
* 3. Select the most appropriate tool from MCP tools for each atomic intent
|
|
9
|
+
* 4. Map intent parameters to tool input parameters
|
|
10
|
+
* 5. Execute tool calls in dependency order
|
|
11
|
+
*/
|
|
12
|
+
import { logger } from '../core/logger.js';
|
|
13
|
+
import { SimpleAI } from './ai.js';
|
|
14
|
+
// ==================== Main Engine Class ====================
|
|
15
|
+
export class CloudIntentEngine {
|
|
16
|
+
ai;
|
|
17
|
+
config;
|
|
18
|
+
availableTools = [];
|
|
19
|
+
toolCache = new Map();
|
|
20
|
+
constructor(config) {
|
|
21
|
+
this.config = {
|
|
22
|
+
llm: {
|
|
23
|
+
provider: 'openai',
|
|
24
|
+
temperature: 0.1,
|
|
25
|
+
maxTokens: 2048,
|
|
26
|
+
timeout: 30000,
|
|
27
|
+
maxRetries: 3,
|
|
28
|
+
...config.llm,
|
|
29
|
+
},
|
|
30
|
+
execution: {
|
|
31
|
+
maxConcurrentTools: 3,
|
|
32
|
+
timeout: 60000,
|
|
33
|
+
retryAttempts: 2,
|
|
34
|
+
retryDelay: 1000,
|
|
35
|
+
...config.execution,
|
|
36
|
+
},
|
|
37
|
+
fallback: {
|
|
38
|
+
enableKeywordMatching: true,
|
|
39
|
+
askUserOnFailure: false,
|
|
40
|
+
defaultTools: {},
|
|
41
|
+
...config.fallback,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
this.ai = new SimpleAI();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Initialize the engine
|
|
48
|
+
*/
|
|
49
|
+
async initialize() {
|
|
50
|
+
try {
|
|
51
|
+
// Configure AI service
|
|
52
|
+
await this.ai.configure({
|
|
53
|
+
provider: this.config.llm.provider,
|
|
54
|
+
apiKey: this.config.llm.apiKey,
|
|
55
|
+
endpoint: this.config.llm.endpoint,
|
|
56
|
+
model: this.config.llm.model,
|
|
57
|
+
});
|
|
58
|
+
logger.info('[CloudIntentEngine] Engine initialized successfully');
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
logger.error(`[CloudIntentEngine] Failed to initialize: ${error}`);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Set available tools list
|
|
67
|
+
*/
|
|
68
|
+
setAvailableTools(tools) {
|
|
69
|
+
this.availableTools = tools;
|
|
70
|
+
this.toolCache.clear();
|
|
71
|
+
// Build tool cache
|
|
72
|
+
tools.forEach(tool => {
|
|
73
|
+
this.toolCache.set(tool.name, tool);
|
|
74
|
+
});
|
|
75
|
+
logger.info(`[CloudIntentEngine] Set ${tools.length} available tools`);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Parse natural language instruction into atomic intents and dependencies
|
|
79
|
+
*/
|
|
80
|
+
async parseIntent(query) {
|
|
81
|
+
logger.info(`[CloudIntentEngine] Parsing intent: "${query}"`);
|
|
82
|
+
try {
|
|
83
|
+
// Build prompt
|
|
84
|
+
const prompt = this.buildIntentParsePrompt(query);
|
|
85
|
+
// Call LLM to parse intent
|
|
86
|
+
const llmResponse = await this.callLLM(prompt, {
|
|
87
|
+
temperature: 0.1,
|
|
88
|
+
maxTokens: 1024,
|
|
89
|
+
});
|
|
90
|
+
// Parse LLM response
|
|
91
|
+
const parsedResult = this.parseIntentResponse(llmResponse);
|
|
92
|
+
logger.info(`[CloudIntentEngine] Parsed ${parsedResult.intents.length} intents with ${parsedResult.edges.length} dependencies`);
|
|
93
|
+
return parsedResult;
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
logger.error(`[CloudIntentEngine] Intent parsing failed: ${error}`);
|
|
97
|
+
// Fallback: use simple rule-based parsing
|
|
98
|
+
return this.fallbackIntentParse(query);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Select the most appropriate tool for each intent
|
|
103
|
+
*/
|
|
104
|
+
async selectTools(intents) {
|
|
105
|
+
logger.info(`[CloudIntentEngine] Selecting tools for ${intents.length} intents`);
|
|
106
|
+
const results = [];
|
|
107
|
+
// Process intents in parallel (limited by concurrency)
|
|
108
|
+
const batchSize = this.config.execution.maxConcurrentTools || 3;
|
|
109
|
+
for (let i = 0; i < intents.length; i += batchSize) {
|
|
110
|
+
const batch = intents.slice(i, i + batchSize);
|
|
111
|
+
const batchPromises = batch.map(intent => this.selectToolForIntent(intent));
|
|
112
|
+
const batchResults = await Promise.all(batchPromises);
|
|
113
|
+
results.push(...batchResults);
|
|
114
|
+
}
|
|
115
|
+
// Count selection results
|
|
116
|
+
const successfulSelections = results.filter(r => r.confidence > 0.5).length;
|
|
117
|
+
logger.info(`[CloudIntentEngine] Tool selection completed: ${successfulSelections}/${intents.length} successful`);
|
|
118
|
+
return results;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Execute workflow
|
|
122
|
+
*/
|
|
123
|
+
async executeWorkflow(intents, toolSelections, edges, toolExecutor) {
|
|
124
|
+
logger.info(`[CloudIntentEngine] Executing workflow with ${intents.length} steps`);
|
|
125
|
+
const context = {
|
|
126
|
+
results: new Map(),
|
|
127
|
+
variables: new Map(),
|
|
128
|
+
};
|
|
129
|
+
const stepResults = [];
|
|
130
|
+
// Build dependency graph
|
|
131
|
+
const dependencyGraph = this.buildDependencyGraph(intents, edges);
|
|
132
|
+
// Topological sort
|
|
133
|
+
const executionOrder = this.topologicalSort(dependencyGraph);
|
|
134
|
+
if (!executionOrder) {
|
|
135
|
+
return {
|
|
136
|
+
success: false,
|
|
137
|
+
stepResults: [],
|
|
138
|
+
finalResult: 'Circular dependency detected in workflow',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// Execute in order
|
|
142
|
+
for (const intentId of executionOrder) {
|
|
143
|
+
const intent = intents.find(i => i.id === intentId);
|
|
144
|
+
const toolSelection = toolSelections.find(s => s.intentId === intentId);
|
|
145
|
+
if (!intent || !toolSelection) {
|
|
146
|
+
stepResults.push({
|
|
147
|
+
intentId,
|
|
148
|
+
toolName: 'unknown',
|
|
149
|
+
success: false,
|
|
150
|
+
error: 'Intent or tool selection not found',
|
|
151
|
+
});
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
// Prepare parameters (support variable substitution)
|
|
156
|
+
const resolvedParams = this.resolveParameters(toolSelection.mappedParameters, context);
|
|
157
|
+
// Execute tool
|
|
158
|
+
const result = await toolExecutor(toolSelection.toolName, resolvedParams);
|
|
159
|
+
// Save result to context
|
|
160
|
+
context.results.set(intentId, result);
|
|
161
|
+
stepResults.push({
|
|
162
|
+
intentId,
|
|
163
|
+
toolName: toolSelection.toolName,
|
|
164
|
+
success: true,
|
|
165
|
+
result,
|
|
166
|
+
});
|
|
167
|
+
logger.info(`[CloudIntentEngine] Step ${intentId} (${toolSelection.toolName}) completed successfully`);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
171
|
+
stepResults.push({
|
|
172
|
+
intentId,
|
|
173
|
+
toolName: toolSelection.toolName,
|
|
174
|
+
success: false,
|
|
175
|
+
error: errorMessage,
|
|
176
|
+
});
|
|
177
|
+
logger.error(`[CloudIntentEngine] Step ${intentId} (${toolSelection.toolName}) failed: ${errorMessage}`);
|
|
178
|
+
// Decide whether to continue based on configuration
|
|
179
|
+
if (this.config.execution.retryAttempts) {
|
|
180
|
+
// Retry logic can be added here
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Calculate final result
|
|
185
|
+
const success = stepResults.every(step => step.success);
|
|
186
|
+
const finalResult = success ? context.results.get(executionOrder[executionOrder.length - 1]) : undefined;
|
|
187
|
+
return {
|
|
188
|
+
success,
|
|
189
|
+
finalResult,
|
|
190
|
+
stepResults,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Parse and plan workflow (without execution)
|
|
195
|
+
*/
|
|
196
|
+
async parseAndPlan(query) {
|
|
197
|
+
logger.info(`[CloudIntentEngine] Parsing and planning workflow: "${query}"`);
|
|
198
|
+
const startTime = Date.now();
|
|
199
|
+
try {
|
|
200
|
+
// Parse intent
|
|
201
|
+
const intentResult = await this.parseIntent(query);
|
|
202
|
+
// Select tools
|
|
203
|
+
const toolSelections = await this.selectTools(intentResult.intents);
|
|
204
|
+
// Build dependency graph and get execution order
|
|
205
|
+
const dependencyGraph = this.buildDependencyGraph(intentResult.intents, intentResult.edges);
|
|
206
|
+
const executionOrder = this.topologicalSort(dependencyGraph);
|
|
207
|
+
if (!executionOrder) {
|
|
208
|
+
throw new Error('Circular dependency detected in workflow');
|
|
209
|
+
}
|
|
210
|
+
const plan = {
|
|
211
|
+
query,
|
|
212
|
+
parsedIntents: intentResult.intents,
|
|
213
|
+
dependencies: intentResult.edges,
|
|
214
|
+
toolSelections,
|
|
215
|
+
executionOrder,
|
|
216
|
+
estimatedSteps: intentResult.intents.length,
|
|
217
|
+
createdAt: new Date(),
|
|
218
|
+
};
|
|
219
|
+
const duration = Date.now() - startTime;
|
|
220
|
+
logger.info(`[CloudIntentEngine] Workflow plan created in ${duration}ms with ${intentResult.intents.length} steps`);
|
|
221
|
+
return plan;
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
logger.error(`[CloudIntentEngine] Failed to parse and plan workflow: ${error}`);
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Execute workflow with enhanced tracking
|
|
230
|
+
*/
|
|
231
|
+
async executeWorkflowWithTracking(intents, toolSelections, edges, toolExecutor, callbacks) {
|
|
232
|
+
logger.info(`[CloudIntentEngine] Executing workflow with enhanced tracking for ${intents.length} steps`);
|
|
233
|
+
const startTime = Date.now();
|
|
234
|
+
const context = {
|
|
235
|
+
results: new Map(),
|
|
236
|
+
variables: new Map(),
|
|
237
|
+
};
|
|
238
|
+
const executionSteps = [];
|
|
239
|
+
// Build dependency graph
|
|
240
|
+
const dependencyGraph = this.buildDependencyGraph(intents, edges);
|
|
241
|
+
// Topological sort
|
|
242
|
+
const executionOrder = this.topologicalSort(dependencyGraph);
|
|
243
|
+
if (!executionOrder) {
|
|
244
|
+
return {
|
|
245
|
+
success: false,
|
|
246
|
+
parsedIntents: intents,
|
|
247
|
+
dependencies: edges,
|
|
248
|
+
toolSelections,
|
|
249
|
+
executionSteps: [],
|
|
250
|
+
statistics: {
|
|
251
|
+
totalSteps: intents.length,
|
|
252
|
+
successfulSteps: 0,
|
|
253
|
+
failedSteps: intents.length,
|
|
254
|
+
totalDuration: Date.now() - startTime,
|
|
255
|
+
averageStepDuration: 0,
|
|
256
|
+
llmCalls: 0,
|
|
257
|
+
},
|
|
258
|
+
finalResult: 'Circular dependency detected in workflow',
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
let successfulSteps = 0;
|
|
262
|
+
let failedSteps = 0;
|
|
263
|
+
let totalStepDuration = 0;
|
|
264
|
+
// Execute in order
|
|
265
|
+
for (const intentId of executionOrder) {
|
|
266
|
+
const intent = intents.find(i => i.id === intentId);
|
|
267
|
+
const toolSelection = toolSelections.find(s => s.intentId === intentId);
|
|
268
|
+
if (!intent || !toolSelection) {
|
|
269
|
+
const errorStep = {
|
|
270
|
+
intentId,
|
|
271
|
+
intentDescription: 'Unknown intent',
|
|
272
|
+
intentType: 'unknown',
|
|
273
|
+
intentParameters: {},
|
|
274
|
+
toolName: 'unknown',
|
|
275
|
+
toolDescription: 'No tool selected',
|
|
276
|
+
mappedParameters: {},
|
|
277
|
+
confidence: 0,
|
|
278
|
+
success: false,
|
|
279
|
+
error: 'Intent or tool selection not found',
|
|
280
|
+
startedAt: new Date(),
|
|
281
|
+
completedAt: new Date(),
|
|
282
|
+
duration: 0,
|
|
283
|
+
};
|
|
284
|
+
executionSteps.push(errorStep);
|
|
285
|
+
failedSteps++;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const stepStartTime = Date.now();
|
|
289
|
+
const stepStartedAt = new Date();
|
|
290
|
+
// Notify step started
|
|
291
|
+
if (callbacks?.onStepStarted) {
|
|
292
|
+
callbacks.onStepStarted({
|
|
293
|
+
intentId,
|
|
294
|
+
toolName: toolSelection.toolName,
|
|
295
|
+
intentDescription: intent.description,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
// Prepare parameters (support variable substitution)
|
|
300
|
+
const resolvedParams = this.resolveParameters(toolSelection.mappedParameters, context);
|
|
301
|
+
// Execute tool
|
|
302
|
+
const result = await toolExecutor(toolSelection.toolName, resolvedParams);
|
|
303
|
+
// Save result to context
|
|
304
|
+
context.results.set(intentId, result);
|
|
305
|
+
const stepDuration = Date.now() - stepStartTime;
|
|
306
|
+
totalStepDuration += stepDuration;
|
|
307
|
+
const successStep = {
|
|
308
|
+
intentId,
|
|
309
|
+
intentDescription: intent.description,
|
|
310
|
+
intentType: intent.type,
|
|
311
|
+
intentParameters: intent.parameters,
|
|
312
|
+
toolName: toolSelection.toolName,
|
|
313
|
+
toolDescription: toolSelection.toolDescription,
|
|
314
|
+
mappedParameters: toolSelection.mappedParameters,
|
|
315
|
+
confidence: toolSelection.confidence,
|
|
316
|
+
success: true,
|
|
317
|
+
result,
|
|
318
|
+
startedAt: stepStartedAt,
|
|
319
|
+
completedAt: new Date(),
|
|
320
|
+
duration: stepDuration,
|
|
321
|
+
};
|
|
322
|
+
executionSteps.push(successStep);
|
|
323
|
+
successfulSteps++;
|
|
324
|
+
// Notify step completed
|
|
325
|
+
if (callbacks?.onStepCompleted) {
|
|
326
|
+
callbacks.onStepCompleted(successStep);
|
|
327
|
+
}
|
|
328
|
+
logger.info(`[CloudIntentEngine] Step ${intentId} (${toolSelection.toolName}) completed in ${stepDuration}ms`);
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
332
|
+
const stepDuration = Date.now() - stepStartTime;
|
|
333
|
+
totalStepDuration += stepDuration;
|
|
334
|
+
const failedStep = {
|
|
335
|
+
intentId,
|
|
336
|
+
intentDescription: intent.description,
|
|
337
|
+
intentType: intent.type,
|
|
338
|
+
intentParameters: intent.parameters,
|
|
339
|
+
toolName: toolSelection.toolName,
|
|
340
|
+
toolDescription: toolSelection.toolDescription,
|
|
341
|
+
mappedParameters: toolSelection.mappedParameters,
|
|
342
|
+
confidence: toolSelection.confidence,
|
|
343
|
+
success: false,
|
|
344
|
+
error: errorMessage,
|
|
345
|
+
startedAt: stepStartedAt,
|
|
346
|
+
completedAt: new Date(),
|
|
347
|
+
duration: stepDuration,
|
|
348
|
+
};
|
|
349
|
+
executionSteps.push(failedStep);
|
|
350
|
+
failedSteps++;
|
|
351
|
+
// Notify step failed
|
|
352
|
+
if (callbacks?.onStepFailed) {
|
|
353
|
+
callbacks.onStepFailed(failedStep);
|
|
354
|
+
}
|
|
355
|
+
logger.error(`[CloudIntentEngine] Step ${intentId} (${toolSelection.toolName}) failed in ${stepDuration}ms: ${errorMessage}`);
|
|
356
|
+
// Decide whether to continue based on configuration
|
|
357
|
+
if (this.config.execution.retryAttempts) {
|
|
358
|
+
// Retry logic can be added here
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
const totalDuration = Date.now() - startTime;
|
|
363
|
+
const averageStepDuration = executionSteps.length > 0 ? totalStepDuration / executionSteps.length : 0;
|
|
364
|
+
// Calculate final result
|
|
365
|
+
const success = executionSteps.every(step => step.success);
|
|
366
|
+
const finalResult = success ? context.results.get(executionOrder[executionOrder.length - 1]) : undefined;
|
|
367
|
+
return {
|
|
368
|
+
success,
|
|
369
|
+
finalResult,
|
|
370
|
+
parsedIntents: intents,
|
|
371
|
+
dependencies: edges,
|
|
372
|
+
toolSelections,
|
|
373
|
+
executionSteps,
|
|
374
|
+
statistics: {
|
|
375
|
+
totalSteps: intents.length,
|
|
376
|
+
successfulSteps,
|
|
377
|
+
failedSteps,
|
|
378
|
+
totalDuration,
|
|
379
|
+
averageStepDuration,
|
|
380
|
+
llmCalls: 0, // This would need to be tracked during parsing and tool selection
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Preview workflow plan (parse and select tools only)
|
|
386
|
+
*/
|
|
387
|
+
async previewPlan(query) {
|
|
388
|
+
return await this.parseAndPlan(query);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Confirm and execute workflow plan
|
|
392
|
+
*/
|
|
393
|
+
async confirmAndExecute(plan, toolExecutor, callbacks) {
|
|
394
|
+
logger.info(`[CloudIntentEngine] Confirming and executing workflow plan with ${plan.estimatedSteps} steps`);
|
|
395
|
+
return await this.executeWorkflowWithTracking(plan.parsedIntents, plan.toolSelections, plan.dependencies, toolExecutor, callbacks);
|
|
396
|
+
}
|
|
397
|
+
// ==================== Private Methods ====================
|
|
398
|
+
/**
|
|
399
|
+
* Build intent parsing prompt
|
|
400
|
+
*/
|
|
401
|
+
buildIntentParsePrompt(query) {
|
|
402
|
+
return `You are a workflow parser. Please decompose the following natural language instruction into atomic intents and infer their dependencies.
|
|
403
|
+
|
|
404
|
+
User instruction: "${query}"
|
|
405
|
+
|
|
406
|
+
Please output JSON format with the following fields:
|
|
407
|
+
1. "intents": Array of atomic intents, each containing:
|
|
408
|
+
- "id": Unique identifier (e.g., A1, A2, A3...)
|
|
409
|
+
- "type": Short action name (e.g., open_web, search, screenshot, send_email)
|
|
410
|
+
- "description": Human-readable step description
|
|
411
|
+
- "parameters": Parameter dictionary extracted from instruction (e.g., {"url": "https://www.baidu.com", "keyword": "mcp service"})
|
|
412
|
+
|
|
413
|
+
2. "edges": Array of dependency edges, each containing:
|
|
414
|
+
- "from": Source intent ID
|
|
415
|
+
- "to": Target intent ID
|
|
416
|
+
|
|
417
|
+
Dependencies should be inferred based on common sense (e.g., open webpage before searching, get results before taking screenshot).
|
|
418
|
+
|
|
419
|
+
Example output:
|
|
420
|
+
{
|
|
421
|
+
"intents": [
|
|
422
|
+
{"id": "A1", "type": "open_web", "description": "Open Baidu", "parameters": {"url": "https://www.baidu.com"}},
|
|
423
|
+
{"id": "A2", "type": "search", "description": "Search for mcp service", "parameters": {"keyword": "mcp service"}},
|
|
424
|
+
{"id": "A3", "type": "extract_results", "description": "Get top 10 search results", "parameters": {"count": 10}},
|
|
425
|
+
{"id": "A4", "type": "screenshot", "description": "Take screenshot", "parameters": {}},
|
|
426
|
+
{"id": "A5", "type": "send_email", "description": "Send image to xx.com email", "parameters": {"recipient": "xx.com"}}
|
|
427
|
+
],
|
|
428
|
+
"edges": [
|
|
429
|
+
{"from": "A1", "to": "A2"},
|
|
430
|
+
{"from": "A2", "to": "A3"},
|
|
431
|
+
{"from": "A3", "to": "A4"},
|
|
432
|
+
{"from": "A4", "to": "A5"}
|
|
433
|
+
]
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
Output only JSON, no other content.`;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Call LLM
|
|
440
|
+
*/
|
|
441
|
+
async callLLM(prompt, options) {
|
|
442
|
+
try {
|
|
443
|
+
// Use SimpleAI's raw API call
|
|
444
|
+
const response = await this.ai.callRawAPI({
|
|
445
|
+
messages: [
|
|
446
|
+
{
|
|
447
|
+
role: 'system',
|
|
448
|
+
content: 'You are a workflow parser, please output strictly in JSON format as required.',
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
role: 'user',
|
|
452
|
+
content: prompt,
|
|
453
|
+
},
|
|
454
|
+
],
|
|
455
|
+
temperature: options?.temperature || 0.1,
|
|
456
|
+
maxTokens: options?.maxTokens || 1024,
|
|
457
|
+
responseFormat: { type: 'json_object' },
|
|
458
|
+
});
|
|
459
|
+
// Extract text content from response based on provider
|
|
460
|
+
let text = '';
|
|
461
|
+
if (response.choices && response.choices[0]?.message?.content) {
|
|
462
|
+
// OpenAI, Azure, DeepSeek format
|
|
463
|
+
text = response.choices[0].message.content;
|
|
464
|
+
}
|
|
465
|
+
else if (response.content && response.content[0]?.text) {
|
|
466
|
+
// Anthropic format
|
|
467
|
+
text = response.content[0].text;
|
|
468
|
+
}
|
|
469
|
+
else if (response.candidates && response.candidates[0]?.content?.parts?.[0]?.text) {
|
|
470
|
+
// Google format
|
|
471
|
+
text = response.candidates[0].content.parts[0].text;
|
|
472
|
+
}
|
|
473
|
+
else if (response.response) {
|
|
474
|
+
// Ollama format
|
|
475
|
+
text = response.response;
|
|
476
|
+
}
|
|
477
|
+
else if (response.text) {
|
|
478
|
+
// Generic format
|
|
479
|
+
text = response.text;
|
|
480
|
+
}
|
|
481
|
+
if (!text) {
|
|
482
|
+
throw new Error('Empty response from LLM');
|
|
483
|
+
}
|
|
484
|
+
return text;
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
logger.error(`[CloudIntentEngine] LLM call failed: ${error}`);
|
|
488
|
+
throw error;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Parse intent response
|
|
493
|
+
*/
|
|
494
|
+
parseIntentResponse(response) {
|
|
495
|
+
try {
|
|
496
|
+
// Extract JSON part (handle possible extra text)
|
|
497
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
498
|
+
if (!jsonMatch) {
|
|
499
|
+
throw new Error('No JSON found in response');
|
|
500
|
+
}
|
|
501
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
502
|
+
// Validate structure
|
|
503
|
+
if (!Array.isArray(parsed.intents) || !Array.isArray(parsed.edges)) {
|
|
504
|
+
throw new Error('Invalid response structure');
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
intents: parsed.intents,
|
|
508
|
+
edges: parsed.edges,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
logger.error(`[CloudIntentEngine] Failed to parse intent response: ${error}`);
|
|
513
|
+
throw error;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Fallback: use simple rule-based intent parsing
|
|
518
|
+
*/
|
|
519
|
+
fallbackIntentParse(query) {
|
|
520
|
+
const queryLower = query.toLowerCase();
|
|
521
|
+
const intents = [];
|
|
522
|
+
const edges = [];
|
|
523
|
+
// Simple rule matching
|
|
524
|
+
if (queryLower.includes('open') && queryLower.includes('web')) {
|
|
525
|
+
intents.push({
|
|
526
|
+
id: 'A1',
|
|
527
|
+
type: 'open_web',
|
|
528
|
+
description: 'Open webpage',
|
|
529
|
+
parameters: { url: this.extractUrl(query) || 'https://www.example.com' },
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
if (queryLower.includes('search')) {
|
|
533
|
+
intents.push({
|
|
534
|
+
id: 'A2',
|
|
535
|
+
type: 'search',
|
|
536
|
+
description: 'Search content',
|
|
537
|
+
parameters: { keyword: this.extractKeyword(query) || 'default' },
|
|
538
|
+
});
|
|
539
|
+
// Add dependency if there's an open web intent
|
|
540
|
+
if (intents.some(i => i.type === 'open_web')) {
|
|
541
|
+
edges.push({ from: 'A1', to: 'A2' });
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (queryLower.includes('screenshot') || queryLower.includes('capture')) {
|
|
545
|
+
intents.push({
|
|
546
|
+
id: 'A3',
|
|
547
|
+
type: 'screenshot',
|
|
548
|
+
description: 'Take screenshot',
|
|
549
|
+
parameters: {},
|
|
550
|
+
});
|
|
551
|
+
// Add dependency if there's a search intent
|
|
552
|
+
if (intents.some(i => i.type === 'search')) {
|
|
553
|
+
edges.push({ from: 'A2', to: 'A3' });
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
// If no intent matched, create a generic intent
|
|
557
|
+
if (intents.length === 0) {
|
|
558
|
+
intents.push({
|
|
559
|
+
id: 'A1',
|
|
560
|
+
type: 'process',
|
|
561
|
+
description: 'Process query',
|
|
562
|
+
parameters: { query },
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
return { intents, edges };
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Select tool for a single intent
|
|
569
|
+
*/
|
|
570
|
+
async selectToolForIntent(intent) {
|
|
571
|
+
// If tool list is empty, return default result
|
|
572
|
+
if (this.availableTools.length === 0) {
|
|
573
|
+
return {
|
|
574
|
+
intentId: intent.id,
|
|
575
|
+
toolName: 'unknown',
|
|
576
|
+
toolDescription: 'No tools available',
|
|
577
|
+
mappedParameters: intent.parameters,
|
|
578
|
+
confidence: 0,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
try {
|
|
582
|
+
// Build tool selection prompt
|
|
583
|
+
const prompt = this.buildToolSelectionPrompt(intent);
|
|
584
|
+
// Call LLM to select tool
|
|
585
|
+
const llmResponse = await this.callLLM(prompt, {
|
|
586
|
+
temperature: 0.1,
|
|
587
|
+
maxTokens: 512,
|
|
588
|
+
});
|
|
589
|
+
// Parse tool selection result
|
|
590
|
+
const selection = this.parseToolSelectionResponse(llmResponse, intent);
|
|
591
|
+
return selection;
|
|
592
|
+
}
|
|
593
|
+
catch (error) {
|
|
594
|
+
logger.error(`[CloudIntentEngine] Tool selection for intent ${intent.id} failed: ${error}`);
|
|
595
|
+
// Fallback: use keyword matching
|
|
596
|
+
return this.fallbackToolSelection(intent);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Build tool selection prompt
|
|
601
|
+
*/
|
|
602
|
+
buildToolSelectionPrompt(intent) {
|
|
603
|
+
// Format tool list as string
|
|
604
|
+
const toolsDescription = this.availableTools.map(tool => {
|
|
605
|
+
return `- ${tool.name}: ${tool.description}\n Input parameters: ${JSON.stringify(tool.inputSchema)}`;
|
|
606
|
+
}).join('\n\n');
|
|
607
|
+
return `Please select the most appropriate tool for the following intent:
|
|
608
|
+
|
|
609
|
+
Intent description: ${intent.description}
|
|
610
|
+
Intent type: ${intent.type}
|
|
611
|
+
Intent parameters: ${JSON.stringify(intent.parameters, null, 2)}
|
|
612
|
+
|
|
613
|
+
Available tools:
|
|
614
|
+
${toolsDescription}
|
|
615
|
+
|
|
616
|
+
Please select the best matching tool and map intent parameters to tool parameters.
|
|
617
|
+
Output JSON format:
|
|
618
|
+
{
|
|
619
|
+
"tool_name": "selected_tool_name",
|
|
620
|
+
"arguments": {
|
|
621
|
+
// Map intent parameters to tool parameters
|
|
622
|
+
},
|
|
623
|
+
"confidence": 0.9 // Matching confidence (0-1)
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
Output only JSON, no other content.`;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Parse tool selection response
|
|
630
|
+
*/
|
|
631
|
+
parseToolSelectionResponse(response, intent) {
|
|
632
|
+
try {
|
|
633
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
634
|
+
if (!jsonMatch) {
|
|
635
|
+
throw new Error('No JSON found in response');
|
|
636
|
+
}
|
|
637
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
638
|
+
// Validate structure
|
|
639
|
+
if (!parsed.tool_name || !parsed.arguments || typeof parsed.confidence !== 'number') {
|
|
640
|
+
throw new Error('Invalid tool selection response structure');
|
|
641
|
+
}
|
|
642
|
+
// Get tool details from cache
|
|
643
|
+
const tool = this.toolCache.get(parsed.tool_name);
|
|
644
|
+
return {
|
|
645
|
+
intentId: intent.id,
|
|
646
|
+
toolName: parsed.tool_name,
|
|
647
|
+
toolDescription: tool?.description || 'No description available',
|
|
648
|
+
mappedParameters: parsed.arguments,
|
|
649
|
+
confidence: parsed.confidence,
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
catch (error) {
|
|
653
|
+
logger.error(`[CloudIntentEngine] Failed to parse tool selection response: ${error}`);
|
|
654
|
+
throw error;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Fallback tool selection using keyword matching
|
|
659
|
+
*/
|
|
660
|
+
fallbackToolSelection(intent) {
|
|
661
|
+
// Simple keyword matching
|
|
662
|
+
const intentLower = intent.description.toLowerCase();
|
|
663
|
+
let bestMatch = null;
|
|
664
|
+
for (const tool of this.availableTools) {
|
|
665
|
+
let score = 0;
|
|
666
|
+
// Check tool name
|
|
667
|
+
if (tool.name.toLowerCase().includes(intent.type.toLowerCase())) {
|
|
668
|
+
score += 3;
|
|
669
|
+
}
|
|
670
|
+
// Check tool description
|
|
671
|
+
if (tool.description.toLowerCase().includes(intent.type.toLowerCase())) {
|
|
672
|
+
score += 2;
|
|
673
|
+
}
|
|
674
|
+
// Check for keyword matches
|
|
675
|
+
const keywords = ['open', 'search', 'read', 'write', 'create', 'delete', 'list', 'get'];
|
|
676
|
+
for (const keyword of keywords) {
|
|
677
|
+
if (intentLower.includes(keyword) && tool.description.toLowerCase().includes(keyword)) {
|
|
678
|
+
score += 1;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
if (score > 0 && (!bestMatch || score > bestMatch.score)) {
|
|
682
|
+
bestMatch = { tool, score };
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (bestMatch) {
|
|
686
|
+
return {
|
|
687
|
+
intentId: intent.id,
|
|
688
|
+
toolName: bestMatch.tool.name,
|
|
689
|
+
toolDescription: bestMatch.tool.description,
|
|
690
|
+
mappedParameters: this.simpleParameterMapping(intent.parameters, bestMatch.tool),
|
|
691
|
+
confidence: Math.min(bestMatch.score / 5, 0.7), // Normalize to 0-0.7 range
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
// Use default tool if configured
|
|
695
|
+
const defaultToolName = this.config.fallback.defaultTools?.[intent.type];
|
|
696
|
+
if (defaultToolName) {
|
|
697
|
+
const defaultTool = this.availableTools.find(t => t.name === defaultToolName);
|
|
698
|
+
if (defaultTool) {
|
|
699
|
+
return {
|
|
700
|
+
intentId: intent.id,
|
|
701
|
+
toolName: defaultTool.name,
|
|
702
|
+
toolDescription: defaultTool.description,
|
|
703
|
+
mappedParameters: intent.parameters,
|
|
704
|
+
confidence: 0.3,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
// Return unknown tool
|
|
709
|
+
return {
|
|
710
|
+
intentId: intent.id,
|
|
711
|
+
toolName: 'unknown',
|
|
712
|
+
toolDescription: 'No matching tool found',
|
|
713
|
+
mappedParameters: intent.parameters,
|
|
714
|
+
confidence: 0,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Enhanced parameter mapping with better handling of parameter name mismatches
|
|
719
|
+
*/
|
|
720
|
+
simpleParameterMapping(intentParams, tool) {
|
|
721
|
+
const mapped = {};
|
|
722
|
+
const schema = tool.inputSchema;
|
|
723
|
+
// Common parameter name mappings (intent param -> tool param)
|
|
724
|
+
// Also include reverse mappings for better coverage
|
|
725
|
+
const commonMappings = {
|
|
726
|
+
// File operations
|
|
727
|
+
'filename': ['path', 'file', 'filepath', 'filename', 'name'],
|
|
728
|
+
'path': ['path', 'filepath', 'filename', 'directory', 'name'],
|
|
729
|
+
'directory': ['directory', 'path', 'folder', 'dir', 'name'],
|
|
730
|
+
'content': ['content', 'text', 'data', 'body'],
|
|
731
|
+
'file': ['path', 'filename', 'filepath', 'name'],
|
|
732
|
+
'name': ['name', 'path', 'filename', 'filepath', 'directory'],
|
|
733
|
+
// Git operations
|
|
734
|
+
'repo': ['repository', 'repo', 'repo_path', 'path'],
|
|
735
|
+
'repository': ['repo', 'repository', 'path'],
|
|
736
|
+
'branch': ['branch', 'ref', 'target'],
|
|
737
|
+
'target': ['branch', 'ref', 'target'],
|
|
738
|
+
'message': ['message', 'commit_message', 'msg'],
|
|
739
|
+
'commit_message': ['message', 'commit_message', 'msg'],
|
|
740
|
+
// Shell operations
|
|
741
|
+
'command': ['command', 'cmd', 'shell_command'],
|
|
742
|
+
'cmd': ['command', 'cmd', 'shell_command'],
|
|
743
|
+
'args': ['args', 'arguments', 'parameters'],
|
|
744
|
+
'arguments': ['args', 'arguments', 'parameters'],
|
|
745
|
+
'parameters': ['args', 'arguments', 'parameters'],
|
|
746
|
+
// Web operations
|
|
747
|
+
'url': ['url', 'uri', 'link', 'address'],
|
|
748
|
+
'uri': ['url', 'uri', 'link', 'address'],
|
|
749
|
+
'method': ['method', 'http_method', 'verb'],
|
|
750
|
+
'http_method': ['method', 'http_method', 'verb'],
|
|
751
|
+
'headers': ['headers', 'http_headers'],
|
|
752
|
+
'http_headers': ['headers', 'http_headers'],
|
|
753
|
+
'body': ['body', 'data', 'content', 'payload'],
|
|
754
|
+
'data': ['body', 'data', 'content', 'payload'],
|
|
755
|
+
'payload': ['body', 'data', 'content', 'payload'],
|
|
756
|
+
};
|
|
757
|
+
// Also build a reverse lookup for tool parameter names
|
|
758
|
+
const toolParamNames = Object.keys(schema.properties);
|
|
759
|
+
const toolParamLookup = new Set(toolParamNames);
|
|
760
|
+
// Try to map based on parameter names
|
|
761
|
+
for (const [paramName, paramValue] of Object.entries(intentParams)) {
|
|
762
|
+
let mappedParamName;
|
|
763
|
+
// 1. Direct match (case-insensitive)
|
|
764
|
+
const directMatch = toolParamNames.find(toolParam => toolParam.toLowerCase() === paramName.toLowerCase());
|
|
765
|
+
if (directMatch) {
|
|
766
|
+
mappedParamName = directMatch;
|
|
767
|
+
}
|
|
768
|
+
// 2. Check common mappings
|
|
769
|
+
else if (commonMappings[paramName]) {
|
|
770
|
+
for (const possibleName of commonMappings[paramName]) {
|
|
771
|
+
if (toolParamLookup.has(possibleName)) {
|
|
772
|
+
mappedParamName = possibleName;
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
// 3. Try to find similar parameter name (fuzzy match with better logic)
|
|
778
|
+
if (!mappedParamName) {
|
|
779
|
+
// First, check if any tool parameter is in the common mappings for this param
|
|
780
|
+
for (const toolParam of toolParamNames) {
|
|
781
|
+
if (commonMappings[toolParam]?.includes(paramName)) {
|
|
782
|
+
mappedParamName = toolParam;
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
// If still not found, try fuzzy matching
|
|
787
|
+
if (!mappedParamName) {
|
|
788
|
+
const similarParam = toolParamNames.find(toolParam => {
|
|
789
|
+
// Remove underscores and normalize
|
|
790
|
+
const normalizedToolParam = toolParam.toLowerCase().replace(/_/g, '');
|
|
791
|
+
const normalizedIntentParam = paramName.toLowerCase().replace(/_/g, '');
|
|
792
|
+
// Check for various similarity patterns
|
|
793
|
+
return (
|
|
794
|
+
// Exact match after normalization
|
|
795
|
+
normalizedToolParam === normalizedIntentParam ||
|
|
796
|
+
// One contains the other
|
|
797
|
+
normalizedToolParam.includes(normalizedIntentParam) ||
|
|
798
|
+
normalizedIntentParam.includes(normalizedToolParam) ||
|
|
799
|
+
// Common abbreviations
|
|
800
|
+
(paramName === 'cmd' && toolParam === 'command') ||
|
|
801
|
+
(paramName === 'uri' && toolParam === 'url') ||
|
|
802
|
+
(paramName === 'target' && toolParam === 'branch') ||
|
|
803
|
+
// Check for common suffixes
|
|
804
|
+
toolParam.toLowerCase().endsWith(paramName.toLowerCase()) ||
|
|
805
|
+
paramName.toLowerCase().endsWith(toolParam.toLowerCase()) ||
|
|
806
|
+
// Check for common prefixes
|
|
807
|
+
toolParam.toLowerCase().startsWith(paramName.toLowerCase()) ||
|
|
808
|
+
paramName.toLowerCase().startsWith(toolParam.toLowerCase()));
|
|
809
|
+
});
|
|
810
|
+
if (similarParam) {
|
|
811
|
+
mappedParamName = similarParam;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
// 4. If still not found, check if we can use the parameter anyway
|
|
816
|
+
// (if additionalProperties is true or not specified)
|
|
817
|
+
if (!mappedParamName) {
|
|
818
|
+
if (schema.additionalProperties !== false) {
|
|
819
|
+
// Allow unknown parameters if additionalProperties is not explicitly false
|
|
820
|
+
mappedParamName = paramName;
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
// Log warning but don't fail - let tool validation handle it
|
|
824
|
+
console.warn(`Parameter "${paramName}" not found in tool schema for "${tool.name}". Tool may reject this parameter.`);
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
// Map the parameter
|
|
829
|
+
if (mappedParamName) {
|
|
830
|
+
mapped[mappedParamName] = paramValue;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return mapped;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Extract URL from query
|
|
837
|
+
*/
|
|
838
|
+
extractUrl(query) {
|
|
839
|
+
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
|
840
|
+
const match = query.match(urlRegex);
|
|
841
|
+
return match ? match[0] : null;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Extract keyword from query
|
|
845
|
+
*/
|
|
846
|
+
extractKeyword(query) {
|
|
847
|
+
// Simple extraction: look for content after "search for" or similar phrases
|
|
848
|
+
const patterns = [
|
|
849
|
+
/search\s+for\s+["']?([^"'\s]+)["']?/i,
|
|
850
|
+
/search\s+["']?([^"'\s]+)["']?/i,
|
|
851
|
+
/find\s+["']?([^"'\s]+)["']?/i,
|
|
852
|
+
/look\s+for\s+["']?([^"'\s]+)["']?/i,
|
|
853
|
+
];
|
|
854
|
+
for (const pattern of patterns) {
|
|
855
|
+
const match = query.match(pattern);
|
|
856
|
+
if (match && match[1]) {
|
|
857
|
+
return match[1];
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Build dependency graph
|
|
864
|
+
*/
|
|
865
|
+
buildDependencyGraph(intents, edges) {
|
|
866
|
+
const graph = new Map();
|
|
867
|
+
// Initialize graph with all intents
|
|
868
|
+
intents.forEach(intent => {
|
|
869
|
+
graph.set(intent.id, new Set());
|
|
870
|
+
});
|
|
871
|
+
// Add edges
|
|
872
|
+
edges.forEach(edge => {
|
|
873
|
+
const dependencies = graph.get(edge.to);
|
|
874
|
+
if (dependencies) {
|
|
875
|
+
dependencies.add(edge.from);
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
return graph;
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Topological sort (Kahn's algorithm)
|
|
882
|
+
*/
|
|
883
|
+
topologicalSort(graph) {
|
|
884
|
+
const sorted = [];
|
|
885
|
+
const indegree = new Map();
|
|
886
|
+
const queue = [];
|
|
887
|
+
// Calculate indegree for each node
|
|
888
|
+
graph.forEach((dependencies, node) => {
|
|
889
|
+
indegree.set(node, dependencies.size);
|
|
890
|
+
if (dependencies.size === 0) {
|
|
891
|
+
queue.push(node);
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
// Process nodes with zero indegree
|
|
895
|
+
while (queue.length > 0) {
|
|
896
|
+
const node = queue.shift();
|
|
897
|
+
sorted.push(node);
|
|
898
|
+
// Decrease indegree of neighbors
|
|
899
|
+
graph.forEach((dependencies, neighbor) => {
|
|
900
|
+
if (dependencies.has(node)) {
|
|
901
|
+
const newIndegree = (indegree.get(neighbor) || 0) - 1;
|
|
902
|
+
indegree.set(neighbor, newIndegree);
|
|
903
|
+
if (newIndegree === 0) {
|
|
904
|
+
queue.push(neighbor);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
// Check for cycles
|
|
910
|
+
if (sorted.length !== graph.size) {
|
|
911
|
+
return null; // Cycle detected
|
|
912
|
+
}
|
|
913
|
+
return sorted;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Resolve parameters with variable substitution
|
|
917
|
+
*/
|
|
918
|
+
resolveParameters(parameters, context) {
|
|
919
|
+
const resolved = {};
|
|
920
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
921
|
+
if (typeof value === 'string') {
|
|
922
|
+
// Simple variable substitution: {{intentId.result}}
|
|
923
|
+
const varMatch = value.match(/\{\{([A-Za-z0-9_]+)\.([A-Za-z0-9_]+)\}\}/);
|
|
924
|
+
if (varMatch) {
|
|
925
|
+
const [, intentId, field] = varMatch;
|
|
926
|
+
const result = context.results.get(intentId);
|
|
927
|
+
if (result && typeof result === 'object' && field in result) {
|
|
928
|
+
resolved[key] = result[field];
|
|
929
|
+
}
|
|
930
|
+
else {
|
|
931
|
+
resolved[key] = value; // Keep original if substitution fails
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
else {
|
|
935
|
+
resolved[key] = value;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
resolved[key] = value;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
return resolved;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Get engine status
|
|
946
|
+
*/
|
|
947
|
+
getStatus() {
|
|
948
|
+
return {
|
|
949
|
+
initialized: this.ai.getStatus().enabled,
|
|
950
|
+
toolsCount: this.availableTools.length,
|
|
951
|
+
llmProvider: this.config.llm.provider,
|
|
952
|
+
llmConfigured: !!this.config.llm.apiKey,
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
//# sourceMappingURL=cloud-intent-engine.js.map
|