@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.
Files changed (101) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +545 -0
  3. package/dist/ai/ai.d.ts +205 -0
  4. package/dist/ai/ai.js +1200 -0
  5. package/dist/ai/cloud-intent-engine.d.ts +270 -0
  6. package/dist/ai/cloud-intent-engine.js +956 -0
  7. package/dist/ai/command.d.ts +59 -0
  8. package/dist/ai/command.js +285 -0
  9. package/dist/ai/config.d.ts +66 -0
  10. package/dist/ai/config.js +211 -0
  11. package/dist/ai/enhanced-intent.d.ts +17 -0
  12. package/dist/ai/enhanced-intent.js +32 -0
  13. package/dist/ai/index.d.ts +29 -0
  14. package/dist/ai/index.js +44 -0
  15. package/dist/ai/intent.d.ts +16 -0
  16. package/dist/ai/intent.js +30 -0
  17. package/dist/core/ai-config.d.ts +25 -0
  18. package/dist/core/ai-config.js +326 -0
  19. package/dist/core/config-manager.d.ts +36 -0
  20. package/dist/core/config-manager.js +400 -0
  21. package/dist/core/config-validator.d.ts +9 -0
  22. package/dist/core/config-validator.js +184 -0
  23. package/dist/core/constants.d.ts +34 -0
  24. package/dist/core/constants.js +37 -0
  25. package/dist/core/error-ai.d.ts +23 -0
  26. package/dist/core/error-ai.js +217 -0
  27. package/dist/core/error-handler.d.ts +197 -0
  28. package/dist/core/error-handler.js +467 -0
  29. package/dist/core/index.d.ts +13 -0
  30. package/dist/core/index.js +17 -0
  31. package/dist/core/logger.d.ts +27 -0
  32. package/dist/core/logger.js +108 -0
  33. package/dist/core/performance-monitor.d.ts +74 -0
  34. package/dist/core/performance-monitor.js +260 -0
  35. package/dist/core/providers.d.ts +36 -0
  36. package/dist/core/providers.js +304 -0
  37. package/dist/core/retry-manager.d.ts +41 -0
  38. package/dist/core/retry-manager.js +204 -0
  39. package/dist/core/types.d.ts +155 -0
  40. package/dist/core/types.js +2 -0
  41. package/dist/daemon/index.d.ts +10 -0
  42. package/dist/daemon/index.js +15 -0
  43. package/dist/daemon/intent-engine.d.ts +22 -0
  44. package/dist/daemon/intent-engine.js +50 -0
  45. package/dist/daemon/orchestrator.d.ts +24 -0
  46. package/dist/daemon/orchestrator.js +100 -0
  47. package/dist/daemon/pm.d.ts +33 -0
  48. package/dist/daemon/pm.js +127 -0
  49. package/dist/daemon/process.d.ts +11 -0
  50. package/dist/daemon/process.js +49 -0
  51. package/dist/daemon/server.d.ts +17 -0
  52. package/dist/daemon/server.js +435 -0
  53. package/dist/daemon/service.d.ts +36 -0
  54. package/dist/daemon/service.js +278 -0
  55. package/dist/index.d.ts +30 -0
  56. package/dist/index.js +36 -0
  57. package/dist/mcp/client.d.ts +51 -0
  58. package/dist/mcp/client.js +276 -0
  59. package/dist/mcp/index.d.ts +162 -0
  60. package/dist/mcp/index.js +199 -0
  61. package/dist/mcp/tool-registry.d.ts +71 -0
  62. package/dist/mcp/tool-registry.js +308 -0
  63. package/dist/mcp/transport.d.ts +83 -0
  64. package/dist/mcp/transport.js +515 -0
  65. package/dist/mcp/types.d.ts +136 -0
  66. package/dist/mcp/types.js +31 -0
  67. package/dist/runtime/adapter-advanced.d.ts +184 -0
  68. package/dist/runtime/adapter-advanced.js +160 -0
  69. package/dist/runtime/adapter.d.ts +9 -0
  70. package/dist/runtime/adapter.js +2 -0
  71. package/dist/runtime/detector-advanced.d.ts +59 -0
  72. package/dist/runtime/detector-advanced.js +487 -0
  73. package/dist/runtime/detector.d.ts +5 -0
  74. package/dist/runtime/detector.js +56 -0
  75. package/dist/runtime/docker-adapter.d.ts +18 -0
  76. package/dist/runtime/docker-adapter.js +170 -0
  77. package/dist/runtime/docker.d.ts +17 -0
  78. package/dist/runtime/docker.js +71 -0
  79. package/dist/runtime/executable-analyzer.d.ts +56 -0
  80. package/dist/runtime/executable-analyzer.js +391 -0
  81. package/dist/runtime/go-adapter.d.ts +19 -0
  82. package/dist/runtime/go-adapter.js +190 -0
  83. package/dist/runtime/index.d.ts +9 -0
  84. package/dist/runtime/index.js +10 -0
  85. package/dist/runtime/node-adapter.d.ts +10 -0
  86. package/dist/runtime/node-adapter.js +23 -0
  87. package/dist/runtime/node.d.ts +20 -0
  88. package/dist/runtime/node.js +86 -0
  89. package/dist/runtime/python-adapter.d.ts +11 -0
  90. package/dist/runtime/python-adapter.js +102 -0
  91. package/dist/runtime/python.d.ts +17 -0
  92. package/dist/runtime/python.js +72 -0
  93. package/dist/runtime/rust-adapter.d.ts +21 -0
  94. package/dist/runtime/rust-adapter.js +267 -0
  95. package/dist/sdk.d.ts +500 -0
  96. package/dist/sdk.js +904 -0
  97. package/docs/README.ZH_CN.md +545 -0
  98. package/docs/api.md +888 -0
  99. package/docs/architecture.md +731 -0
  100. package/docs/development.md +744 -0
  101. 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