flowmind 1.5.2 → 1.5.4
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/CHANGELOG.md +14 -0
- package/bin/flowmind.js +85 -8
- package/core/adapters/mcp-adapter.js +26 -0
- package/core/adapters/workflow-adapter.js +26 -0
- package/core/ai/providers/mimo.js +1 -1
- package/core/component-registry.js +19 -1
- package/core/config-manager.js +7 -2
- package/core/index.js +153 -2
- package/core/mcp-http-client.js +63 -0
- package/core/providers/aliyun/dms-adapter.js +7 -35
- package/core/providers/aliyun/rds-query-adapter.js +70 -0
- package/core/providers/aliyun/redis-adapter.js +4 -20
- package/core/providers/aliyun/sls-adapter.js +3 -10
- package/core/providers/friday/flow-adapter.js +19 -30
- package/core/providers/friday/report-adapter.js +5 -25
- package/core/providers/yapi/yapi-adapter.js +6 -30
- package/core/providers/yuque/yuque-adapter.js +7 -35
- package/core/sdd-agent-sync.js +240 -16
- package/core/source-inference.js +324 -0
- package/package.json +1 -1
- package/skills/auto-flow/index.js +903 -74
- package/skills/data-logic-validation/index.js +133 -12
- package/skills/log-audit/index.js +94 -1
- package/skills/resource-bind/index.js +61 -18
- package/skills/yapi-sync-interface/index.js +146 -13
- package/skills/yuque-sync-design/index.js +130 -11
- package/tui/app.jsx +15 -5
- package/tui/components/ChatPanel.jsx +1 -1
- package/tui/format-result.js +43 -4
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Verify SQL queries, Redis operations, and data processing logic against real data via MCP
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
const { inferSourceContext } = require('../../core/source-inference');
|
|
7
|
+
|
|
6
8
|
module.exports = {
|
|
7
9
|
canHandle(input, context) {
|
|
8
10
|
if (!input) return false;
|
|
@@ -13,6 +15,7 @@ module.exports = {
|
|
|
13
15
|
const registry = context.componentRegistry;
|
|
14
16
|
const params = parseValidationParams(input);
|
|
15
17
|
const learnedBinding = context.resourceBinding;
|
|
18
|
+
const sourceInference = context.sourceInference || await safeInferSourceContext(input);
|
|
16
19
|
|
|
17
20
|
// Check MCP component availability
|
|
18
21
|
const dbManager = registry?.getAdapter('databaseManager');
|
|
@@ -26,7 +29,7 @@ module.exports = {
|
|
|
26
29
|
};
|
|
27
30
|
|
|
28
31
|
if (params.action === 'sql') {
|
|
29
|
-
if (!dbQuery
|
|
32
|
+
if (!dbQuery) {
|
|
30
33
|
return {
|
|
31
34
|
type: 'result',
|
|
32
35
|
skill: 'data-logic-validation',
|
|
@@ -37,16 +40,22 @@ module.exports = {
|
|
|
37
40
|
};
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
const target = resolveDatabaseTarget(params, learnedBinding, sourceInference);
|
|
44
|
+
const sql = params.query || 'SELECT 1';
|
|
45
|
+
const execution = await dbQuery.queryExec(buildSqlExecutionParams(target, sql));
|
|
46
|
+
|
|
40
47
|
return {
|
|
41
48
|
type: 'result',
|
|
42
49
|
skill: 'data-logic-validation',
|
|
43
|
-
message: `SQL validation
|
|
50
|
+
message: `SQL validation executed for: ${target.project || target.database || 'resolved target'}`,
|
|
44
51
|
data: {
|
|
45
52
|
action: 'validate_sql',
|
|
46
|
-
query:
|
|
47
|
-
|
|
53
|
+
query: sql,
|
|
54
|
+
target,
|
|
55
|
+
execution,
|
|
48
56
|
availableComponents,
|
|
49
|
-
binding: learnedBinding
|
|
57
|
+
binding: learnedBinding,
|
|
58
|
+
inferredSource: summarizeSourceInference(sourceInference)
|
|
50
59
|
},
|
|
51
60
|
input,
|
|
52
61
|
timestamp: new Date().toISOString()
|
|
@@ -54,27 +63,33 @@ module.exports = {
|
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
if (params.action === 'redis') {
|
|
57
|
-
if (!
|
|
66
|
+
if (!dbQuery) {
|
|
58
67
|
return {
|
|
59
68
|
type: 'result',
|
|
60
69
|
skill: 'data-logic-validation',
|
|
61
|
-
message: 'Redis
|
|
62
|
-
data: { availableComponents, hint: 'Configure
|
|
70
|
+
message: 'Redis direct query service not configured (need friday-rds-redis-query MCP)',
|
|
71
|
+
data: { availableComponents, hint: 'Configure database query MCP server first' },
|
|
63
72
|
input,
|
|
64
73
|
timestamp: new Date().toISOString()
|
|
65
74
|
};
|
|
66
75
|
}
|
|
67
76
|
|
|
77
|
+
const target = resolveRedisTarget(params, learnedBinding, sourceInference);
|
|
78
|
+
const execution = await executeRedisAction(dbQuery, params, target);
|
|
79
|
+
|
|
68
80
|
return {
|
|
69
81
|
type: 'result',
|
|
70
82
|
skill: 'data-logic-validation',
|
|
71
|
-
message: `Redis validation
|
|
83
|
+
message: `Redis validation executed for: ${target.project || target.host || 'resolved target'}`,
|
|
72
84
|
data: {
|
|
73
85
|
action: 'validate_redis',
|
|
74
86
|
key: params.key,
|
|
75
|
-
|
|
87
|
+
target,
|
|
88
|
+
execution,
|
|
76
89
|
availableComponents,
|
|
77
|
-
binding: learnedBinding
|
|
90
|
+
binding: learnedBinding,
|
|
91
|
+
inferredSource: summarizeSourceInference(sourceInference),
|
|
92
|
+
monitorProvider: redisMonitor?.providerName || null
|
|
78
93
|
},
|
|
79
94
|
input,
|
|
80
95
|
timestamp: new Date().toISOString()
|
|
@@ -89,7 +104,8 @@ module.exports = {
|
|
|
89
104
|
actions: ['sql - Validate SQL queries', 'redis - Validate Redis operations'],
|
|
90
105
|
availableComponents,
|
|
91
106
|
requiredMCP: ['databaseManager', 'databaseQuery', 'redisMonitor'],
|
|
92
|
-
binding: learnedBinding
|
|
107
|
+
binding: learnedBinding,
|
|
108
|
+
inferredSource: summarizeSourceInference(sourceInference)
|
|
93
109
|
},
|
|
94
110
|
input,
|
|
95
111
|
timestamp: new Date().toISOString()
|
|
@@ -108,5 +124,110 @@ function parseValidationParams(input) {
|
|
|
108
124
|
const keyMatch = input.match(/(?:key|键)\s*[:=]?\s*(\S+)/i);
|
|
109
125
|
if (keyMatch) params.key = keyMatch[1];
|
|
110
126
|
|
|
127
|
+
const projectMatch = input.match(/(?:project|项目)\s*[:=]?\s*([\u4e00-\u9fa5A-Za-z0-9._-]+)/i);
|
|
128
|
+
if (projectMatch) params.project = projectMatch[1];
|
|
129
|
+
|
|
130
|
+
const envMatch = input.match(/(?:env|环境)\s*[:=]?\s*([A-Za-z_-]+)/i);
|
|
131
|
+
if (envMatch) params.env = envMatch[1].toLowerCase();
|
|
132
|
+
|
|
111
133
|
return params;
|
|
112
134
|
}
|
|
135
|
+
|
|
136
|
+
async function safeInferSourceContext(input) {
|
|
137
|
+
try {
|
|
138
|
+
return await inferSourceContext(input);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function resolveDatabaseTarget(params, learnedBinding, sourceInference) {
|
|
145
|
+
const bindingConnection = learnedBinding?.connection || {};
|
|
146
|
+
const inferredDatabase = sourceInference?.database || {};
|
|
147
|
+
|
|
148
|
+
return compactObject({
|
|
149
|
+
project: params.project || sourceInference?.project || bindingConnection.project || null,
|
|
150
|
+
environment: params.env || sourceInference?.environment || bindingConnection.env || inferredDatabase.envType || null,
|
|
151
|
+
sourceId: bindingConnection.sourceId || inferredDatabase.sourceId || null,
|
|
152
|
+
databaseId: bindingConnection.databaseId || inferredDatabase.databaseId || null,
|
|
153
|
+
database: inferredDatabase.database || bindingConnection.database || null,
|
|
154
|
+
host: bindingConnection.host || inferredDatabase.host || null,
|
|
155
|
+
port: bindingConnection.port || inferredDatabase.port || null,
|
|
156
|
+
username: bindingConnection.username || bindingConnection.dbUser || inferredDatabase.username || inferredDatabase.dbUser || null,
|
|
157
|
+
password: bindingConnection.password || bindingConnection.dbPassword || inferredDatabase.password || inferredDatabase.dbPassword || null
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function resolveRedisTarget(params, learnedBinding, sourceInference) {
|
|
162
|
+
const bindingConnection = learnedBinding?.connection || {};
|
|
163
|
+
const inferredRedis = sourceInference?.redis || {};
|
|
164
|
+
|
|
165
|
+
return compactObject({
|
|
166
|
+
project: params.project || sourceInference?.project || bindingConnection.project || null,
|
|
167
|
+
environment: params.env || sourceInference?.environment || bindingConnection.env || inferredRedis.envType || null,
|
|
168
|
+
sourceId: bindingConnection.sourceId || inferredRedis.sourceId || null,
|
|
169
|
+
host: bindingConnection.host || inferredRedis.host || null,
|
|
170
|
+
port: bindingConnection.port || inferredRedis.port || null,
|
|
171
|
+
password: bindingConnection.password || inferredRedis.password || null,
|
|
172
|
+
database: inferredRedis.database ?? bindingConnection.database ?? 1
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function buildSqlExecutionParams(target, sql) {
|
|
177
|
+
return compactObject({
|
|
178
|
+
source_id: target.sourceId || null,
|
|
179
|
+
database_id: target.databaseId || null,
|
|
180
|
+
schema: target.database || null,
|
|
181
|
+
sql,
|
|
182
|
+
env: target.environment || null,
|
|
183
|
+
host: target.host || null,
|
|
184
|
+
port: target.port || null,
|
|
185
|
+
username: target.username || null,
|
|
186
|
+
password: target.password || null
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function executeRedisAction(dbQuery, params, target) {
|
|
191
|
+
const baseParams = compactObject({
|
|
192
|
+
source_id: target.sourceId || null,
|
|
193
|
+
env: target.environment || null,
|
|
194
|
+
host: target.host || null,
|
|
195
|
+
port: target.port || null,
|
|
196
|
+
password: target.password || null,
|
|
197
|
+
database: target.database ?? 1
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (params.key && !/[*?]/.test(params.key)) {
|
|
201
|
+
return dbQuery.redisKeyGet({
|
|
202
|
+
...baseParams,
|
|
203
|
+
key: params.key
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return dbQuery.redisScan({
|
|
208
|
+
...baseParams,
|
|
209
|
+
pattern: params.key || '*',
|
|
210
|
+
count: 50
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function summarizeSourceInference(sourceInference) {
|
|
215
|
+
if (!sourceInference?.matched) {
|
|
216
|
+
return sourceInference?.environment
|
|
217
|
+
? { matched: false, environment: sourceInference.environment }
|
|
218
|
+
: null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
matched: true,
|
|
223
|
+
project: sourceInference.project,
|
|
224
|
+
environment: sourceInference.environment,
|
|
225
|
+
database: sourceInference.database?.database || null,
|
|
226
|
+
redisDatabase: sourceInference.redis?.database ?? null,
|
|
227
|
+
matchedEntries: sourceInference.matchedEntries?.map((entry) => entry.title) || []
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function compactObject(value) {
|
|
232
|
+
return Object.fromEntries(Object.entries(value).filter(([, fieldValue]) => fieldValue !== null && fieldValue !== undefined && fieldValue !== ''));
|
|
233
|
+
}
|
|
@@ -29,6 +29,7 @@ module.exports = {
|
|
|
29
29
|
? logService.buildQueryParams(queryInput)
|
|
30
30
|
: queryInput;
|
|
31
31
|
const execution = await logService.queryLogs(queryParams);
|
|
32
|
+
const summary = summarizeLogExecution(execution);
|
|
32
33
|
|
|
33
34
|
return {
|
|
34
35
|
type: 'result',
|
|
@@ -40,7 +41,7 @@ module.exports = {
|
|
|
40
41
|
action: params.traceId ? 'trace' : 'query',
|
|
41
42
|
provider: logService.providerName,
|
|
42
43
|
queryParams,
|
|
43
|
-
execution
|
|
44
|
+
execution: summary
|
|
44
45
|
},
|
|
45
46
|
input,
|
|
46
47
|
timestamp: new Date().toISOString()
|
|
@@ -87,3 +88,95 @@ function buildLogQueryInput(params) {
|
|
|
87
88
|
line: params.limit || 100
|
|
88
89
|
};
|
|
89
90
|
}
|
|
91
|
+
|
|
92
|
+
function summarizeLogExecution(execution) {
|
|
93
|
+
const payload = unwrapPayload(execution);
|
|
94
|
+
const data = payload && typeof payload === 'object' ? payload : {};
|
|
95
|
+
const records = extractRecords(data);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
status: extractStatus(data) || 'ok',
|
|
99
|
+
total: extractTotal(data, records),
|
|
100
|
+
records: records.slice(0, 5).map(summarizeRecord),
|
|
101
|
+
requestId: data.requestId || data.traceId || data.id || null
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function unwrapPayload(payload) {
|
|
106
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
107
|
+
return payload;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const textPayload = extractContentText(payload);
|
|
111
|
+
if (textPayload) {
|
|
112
|
+
return parseMaybeJson(textPayload);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)) {
|
|
116
|
+
return payload.data;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return payload;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function extractRecords(data) {
|
|
123
|
+
const candidates = [data.logs, data.records, data.items, data.result, data.data];
|
|
124
|
+
for (const candidate of candidates) {
|
|
125
|
+
if (Array.isArray(candidate)) {
|
|
126
|
+
return candidate;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function extractStatus(data) {
|
|
134
|
+
if (!data || typeof data !== 'object') {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (typeof data.status === 'string') {
|
|
139
|
+
return data.status;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (data.success === false) {
|
|
143
|
+
return 'error';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function extractTotal(data, records) {
|
|
150
|
+
return data.total || data.count || records.length || null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function summarizeRecord(record) {
|
|
154
|
+
if (!record || typeof record !== 'object') {
|
|
155
|
+
return record;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
time: record.time || record.timestamp || record.__time__ || null,
|
|
160
|
+
level: record.level || record.severity || null,
|
|
161
|
+
message: record.message || record.content || record.msg || null,
|
|
162
|
+
traceId: record.traceId || record.trace_id || null
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function extractContentText(payload) {
|
|
167
|
+
const content = Array.isArray(payload.content) ? payload.content : [];
|
|
168
|
+
const textItem = content.find((item) => item && typeof item.text === 'string');
|
|
169
|
+
return textItem ? textItem.text : null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function parseMaybeJson(value) {
|
|
173
|
+
if (typeof value !== 'string') {
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
return JSON.parse(value);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
return { text: value };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Manage database, Redis, API, and other external resource connections
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
const { inferSourceContext } = require('../../core/source-inference');
|
|
7
|
+
|
|
6
8
|
const PROVIDER_MAP = {
|
|
7
9
|
'yapi-mcp': { componentType: 'apiDoc', provider: 'yapi', mcpServer: 'yapi-mcp' },
|
|
8
10
|
yapi: { componentType: 'apiDoc', provider: 'yapi', mcpServer: 'yapi-mcp' },
|
|
@@ -27,9 +29,10 @@ module.exports = {
|
|
|
27
29
|
async execute(input, context) {
|
|
28
30
|
const registry = context.componentRegistry;
|
|
29
31
|
const params = parseResourceParams(input);
|
|
32
|
+
const sourceInference = context.sourceInference || await safeInferSourceContext(input);
|
|
30
33
|
|
|
31
34
|
if (params.action === 'bind') {
|
|
32
|
-
return bindResource(input, context, params);
|
|
35
|
+
return bindResource(input, context, params, sourceInference);
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
if (params.action === 'list') {
|
|
@@ -48,7 +51,8 @@ module.exports = {
|
|
|
48
51
|
initialized: c.initialized,
|
|
49
52
|
mcpServer: c.mcpServer
|
|
50
53
|
})),
|
|
51
|
-
bindings
|
|
54
|
+
bindings,
|
|
55
|
+
inferredSource: summarizeSourceInference(sourceInference)
|
|
52
56
|
},
|
|
53
57
|
input,
|
|
54
58
|
timestamp: new Date().toISOString()
|
|
@@ -69,7 +73,8 @@ module.exports = {
|
|
|
69
73
|
: 'Resource connection status',
|
|
70
74
|
data: {
|
|
71
75
|
status,
|
|
72
|
-
matchedBinding
|
|
76
|
+
matchedBinding,
|
|
77
|
+
inferredSource: summarizeSourceInference(sourceInference)
|
|
73
78
|
},
|
|
74
79
|
input,
|
|
75
80
|
timestamp: new Date().toISOString()
|
|
@@ -87,7 +92,8 @@ module.exports = {
|
|
|
87
92
|
'status - Show connection status and matched binding',
|
|
88
93
|
'use - Resolve a learned binding for the current business'
|
|
89
94
|
],
|
|
90
|
-
supportedTypes: ['MySQL', 'PostgreSQL', 'Redis', 'REST API', 'YApi', 'Yuque', 'Workflow']
|
|
95
|
+
supportedTypes: ['MySQL', 'PostgreSQL', 'Redis', 'REST API', 'YApi', 'Yuque', 'Workflow'],
|
|
96
|
+
inferredSource: summarizeSourceInference(sourceInference)
|
|
91
97
|
},
|
|
92
98
|
input,
|
|
93
99
|
timestamp: new Date().toISOString()
|
|
@@ -95,7 +101,7 @@ module.exports = {
|
|
|
95
101
|
}
|
|
96
102
|
};
|
|
97
103
|
|
|
98
|
-
async function bindResource(input, context, params) {
|
|
104
|
+
async function bindResource(input, context, params, sourceInference) {
|
|
99
105
|
const learning = context.flowmind?.learning;
|
|
100
106
|
if (!learning?.saveResourceBinding) {
|
|
101
107
|
return {
|
|
@@ -107,7 +113,7 @@ async function bindResource(input, context, params) {
|
|
|
107
113
|
};
|
|
108
114
|
}
|
|
109
115
|
|
|
110
|
-
const binding = buildBinding(input, params, context);
|
|
116
|
+
const binding = buildBinding(input, params, context, sourceInference);
|
|
111
117
|
const saved = await learning.saveResourceBinding(binding, {
|
|
112
118
|
currentSkill: 'resource-bind'
|
|
113
119
|
});
|
|
@@ -126,10 +132,20 @@ async function bindResource(input, context, params) {
|
|
|
126
132
|
};
|
|
127
133
|
}
|
|
128
134
|
|
|
129
|
-
function buildBinding(input, params, context) {
|
|
135
|
+
function buildBinding(input, params, context, sourceInference) {
|
|
130
136
|
const inferred = resolveProviderInfo(params);
|
|
131
|
-
const
|
|
132
|
-
const
|
|
137
|
+
const inferredDatabase = sourceInference?.database || null;
|
|
138
|
+
const inferredRedis = sourceInference?.redis || null;
|
|
139
|
+
const business = inferBusiness(input, params, inferred, sourceInference);
|
|
140
|
+
const aliases = [
|
|
141
|
+
business,
|
|
142
|
+
params.project,
|
|
143
|
+
sourceInference?.project,
|
|
144
|
+
params.namespace,
|
|
145
|
+
params.alias,
|
|
146
|
+
params.env,
|
|
147
|
+
sourceInference?.environment
|
|
148
|
+
].filter(Boolean);
|
|
133
149
|
const keywords = extractKeywords(input, aliases);
|
|
134
150
|
|
|
135
151
|
return {
|
|
@@ -140,18 +156,20 @@ function buildBinding(input, params, context) {
|
|
|
140
156
|
mcpServer: params.mcpServer || inferred.mcpServer || null,
|
|
141
157
|
connection: {
|
|
142
158
|
address: params.address || params.url || params.endpoint || null,
|
|
143
|
-
host: params.host || null,
|
|
144
|
-
port: params.port || null,
|
|
159
|
+
host: params.host || inferredDatabase?.host || inferredRedis?.host || null,
|
|
160
|
+
port: params.port || inferredDatabase?.port || inferredRedis?.port || null,
|
|
145
161
|
token: params.token || null,
|
|
146
162
|
apiKey: params.apiKey || null,
|
|
147
163
|
namespace: params.namespace || null,
|
|
148
|
-
project: params.project || null,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
164
|
+
project: params.project || sourceInference?.project || null,
|
|
165
|
+
database: inferredDatabase?.database || null,
|
|
166
|
+
sourceId: params.sourceId || inferredDatabase?.sourceId || inferredRedis?.sourceId || null,
|
|
167
|
+
databaseId: params.databaseId || inferredDatabase?.databaseId || null,
|
|
168
|
+
env: params.env || sourceInference?.environment || null
|
|
152
169
|
},
|
|
153
170
|
metadata: {
|
|
154
|
-
input
|
|
171
|
+
input,
|
|
172
|
+
sourceInference: summarizeSourceInference(sourceInference)
|
|
155
173
|
},
|
|
156
174
|
keywords
|
|
157
175
|
};
|
|
@@ -231,8 +249,8 @@ function resolveProviderInfo(params) {
|
|
|
231
249
|
return {};
|
|
232
250
|
}
|
|
233
251
|
|
|
234
|
-
function inferBusiness(input, params, inferred = {}) {
|
|
235
|
-
const explicit = params.business || params.project || params.namespace;
|
|
252
|
+
function inferBusiness(input, params, inferred = {}, sourceInference = null) {
|
|
253
|
+
const explicit = params.business || params.project || sourceInference?.project || params.namespace;
|
|
236
254
|
if (explicit) return explicit;
|
|
237
255
|
|
|
238
256
|
const keywordMatch = String(input || '').match(/(?:绑定|bind)\s*([\u4e00-\u9fa5A-Za-z0-9._-]+?)\s*(平台|服务|环境|工程|项目)/i);
|
|
@@ -259,3 +277,28 @@ function extractKeywords(input, aliases = []) {
|
|
|
259
277
|
function escapeRegex(value) {
|
|
260
278
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
261
279
|
}
|
|
280
|
+
|
|
281
|
+
async function safeInferSourceContext(input) {
|
|
282
|
+
try {
|
|
283
|
+
return await inferSourceContext(input);
|
|
284
|
+
} catch (error) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function summarizeSourceInference(sourceInference) {
|
|
290
|
+
if (!sourceInference?.matched) {
|
|
291
|
+
return sourceInference?.environment
|
|
292
|
+
? { matched: false, environment: sourceInference.environment }
|
|
293
|
+
: null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
matched: true,
|
|
298
|
+
project: sourceInference.project,
|
|
299
|
+
environment: sourceInference.environment,
|
|
300
|
+
database: sourceInference.database?.database || null,
|
|
301
|
+
redisDatabase: sourceInference.redis?.database ?? null,
|
|
302
|
+
matchedEntries: sourceInference.matchedEntries?.map((entry) => entry.title) || []
|
|
303
|
+
};
|
|
304
|
+
}
|
|
@@ -10,7 +10,9 @@ module.exports = {
|
|
|
10
10
|
},
|
|
11
11
|
|
|
12
12
|
async execute(input, context) {
|
|
13
|
-
const apiDoc = context.componentRegistry?.getAdapter('apiDoc')
|
|
13
|
+
const apiDoc = context.componentRegistry?.getAdapter('apiDoc')
|
|
14
|
+
|| context.componentRegistry?.getAdapterByProvider?.('apiDoc', 'yapi')
|
|
15
|
+
|| null;
|
|
14
16
|
const learnedBinding = context.resourceBinding?.componentType === 'apiDoc'
|
|
15
17
|
? context.resourceBinding
|
|
16
18
|
: null;
|
|
@@ -26,17 +28,38 @@ module.exports = {
|
|
|
26
28
|
};
|
|
27
29
|
}
|
|
28
30
|
|
|
31
|
+
if (!apiDoc) {
|
|
32
|
+
return {
|
|
33
|
+
type: 'result',
|
|
34
|
+
skill: 'yapi-sync-interface',
|
|
35
|
+
message: 'YApi adapter is not available in the current registry.',
|
|
36
|
+
data: {
|
|
37
|
+
hint: 'Make sure the apiDoc component is configured and loaded',
|
|
38
|
+
binding: learnedBinding
|
|
39
|
+
},
|
|
40
|
+
input,
|
|
41
|
+
timestamp: new Date().toISOString()
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
29
45
|
const params = parseYApiParams(input);
|
|
30
46
|
|
|
31
47
|
if (params.action === 'search') {
|
|
48
|
+
const args = {
|
|
49
|
+
projectKeyword: params.project || learnedBinding?.connection?.project,
|
|
50
|
+
nameKeyword: params.keyword,
|
|
51
|
+
limit: 20
|
|
52
|
+
};
|
|
53
|
+
const execution = await apiDoc.searchApis(args);
|
|
32
54
|
return {
|
|
33
55
|
type: 'result',
|
|
34
56
|
skill: 'yapi-sync-interface',
|
|
35
57
|
message: `Searching YApi for: ${params.keyword || 'all'}`,
|
|
36
58
|
data: {
|
|
37
|
-
|
|
38
|
-
args
|
|
39
|
-
binding: learnedBinding
|
|
59
|
+
action: 'search',
|
|
60
|
+
args,
|
|
61
|
+
binding: learnedBinding,
|
|
62
|
+
execution: summarizeYApiExecution('search', execution)
|
|
40
63
|
},
|
|
41
64
|
input,
|
|
42
65
|
timestamp: new Date().toISOString()
|
|
@@ -44,14 +67,17 @@ module.exports = {
|
|
|
44
67
|
}
|
|
45
68
|
|
|
46
69
|
if (params.action === 'list') {
|
|
70
|
+
const projectId = params.project || learnedBinding?.connection?.project;
|
|
71
|
+
const execution = await apiDoc.getCategories(projectId);
|
|
47
72
|
return {
|
|
48
73
|
type: 'result',
|
|
49
74
|
skill: 'yapi-sync-interface',
|
|
50
|
-
message: `Listing YApi categories for project: ${
|
|
75
|
+
message: `Listing YApi categories for project: ${projectId || 'default'}`,
|
|
51
76
|
data: {
|
|
52
|
-
|
|
53
|
-
args: { projectId
|
|
54
|
-
binding: learnedBinding
|
|
77
|
+
action: 'list',
|
|
78
|
+
args: { projectId },
|
|
79
|
+
binding: learnedBinding,
|
|
80
|
+
execution: summarizeYApiExecution('list', execution)
|
|
55
81
|
},
|
|
56
82
|
input,
|
|
57
83
|
timestamp: new Date().toISOString()
|
|
@@ -59,14 +85,17 @@ module.exports = {
|
|
|
59
85
|
}
|
|
60
86
|
|
|
61
87
|
if (params.action === 'export') {
|
|
88
|
+
const projectId = params.project || learnedBinding?.connection?.project;
|
|
89
|
+
const execution = await apiDoc.exportProject(projectId, params.format || 'swagger');
|
|
62
90
|
return {
|
|
63
91
|
type: 'result',
|
|
64
92
|
skill: 'yapi-sync-interface',
|
|
65
|
-
message: `Exporting YApi project: ${
|
|
93
|
+
message: `Exporting YApi project: ${projectId || 'default'}`,
|
|
66
94
|
data: {
|
|
67
|
-
|
|
68
|
-
args: { projectId
|
|
69
|
-
binding: learnedBinding
|
|
95
|
+
action: 'export',
|
|
96
|
+
args: { projectId, type: params.format || 'swagger' },
|
|
97
|
+
binding: learnedBinding,
|
|
98
|
+
execution: summarizeYApiExecution('export', execution)
|
|
70
99
|
},
|
|
71
100
|
input,
|
|
72
101
|
timestamp: new Date().toISOString()
|
|
@@ -98,7 +127,7 @@ function parseYApiParams(input) {
|
|
|
98
127
|
const projectMatch = input.match(/(?:项目|project)\s*[:=]?\s*(\S+)/i);
|
|
99
128
|
if (projectMatch) params.project = projectMatch[1];
|
|
100
129
|
|
|
101
|
-
const keywordMatch = input.match(/(?:关键词|keyword
|
|
130
|
+
const keywordMatch = input.match(/(?:关键词|keyword|名称|name)\s*[:=]?\s*(.+?)(?=\s+(?:项目|project|格式|format)\s*[:=]|\s*$)/i);
|
|
102
131
|
if (keywordMatch) params.keyword = keywordMatch[1].trim();
|
|
103
132
|
|
|
104
133
|
const formatMatch = input.match(/(?:格式|format)\s*[:=]?\s*(json|markdown|swagger)/i);
|
|
@@ -106,3 +135,107 @@ function parseYApiParams(input) {
|
|
|
106
135
|
|
|
107
136
|
return params;
|
|
108
137
|
}
|
|
138
|
+
|
|
139
|
+
function summarizeYApiExecution(action, execution) {
|
|
140
|
+
const payload = unwrapPayload(execution);
|
|
141
|
+
const data = getPayloadData(payload);
|
|
142
|
+
const items = extractListItems(data);
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
action,
|
|
146
|
+
status: extractStatus(data) || extractStatus(payload) || 'ok',
|
|
147
|
+
total: extractTotal(data, payload, items),
|
|
148
|
+
items: items.slice(0, 5).map(summarizeItem),
|
|
149
|
+
runId: extractRunId(data) || extractRunId(payload) || null
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function unwrapPayload(payload) {
|
|
154
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
155
|
+
return payload;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const textPayload = extractContentText(payload);
|
|
159
|
+
if (textPayload) {
|
|
160
|
+
return parseMaybeJson(textPayload);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if ('data' in payload && payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)) {
|
|
164
|
+
return payload.data;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return payload;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function getPayloadData(payload) {
|
|
171
|
+
return payload && typeof payload === 'object' && !Array.isArray(payload) ? payload : {};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function extractListItems(data) {
|
|
175
|
+
const candidates = [data.list, data.items, data.records, data.result, data.data];
|
|
176
|
+
for (const candidate of candidates) {
|
|
177
|
+
if (Array.isArray(candidate)) {
|
|
178
|
+
return candidate;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function extractStatus(payload) {
|
|
186
|
+
if (!payload || typeof payload !== 'object') {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (typeof payload.status === 'string') {
|
|
191
|
+
return payload.status;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (payload.success === false) {
|
|
195
|
+
return 'error';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return typeof payload.code === 'string' ? payload.code : null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function extractTotal(data, payload, items) {
|
|
202
|
+
return data.total || payload.total || items.length || null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function extractRunId(payload) {
|
|
206
|
+
if (!payload || typeof payload !== 'object') {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return payload.runId || payload.id || payload.requestId || null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function summarizeItem(item) {
|
|
214
|
+
if (!item || typeof item !== 'object') {
|
|
215
|
+
return item;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
id: item._id || item.id || item.api_id || item.catid || null,
|
|
220
|
+
name: item.name || item.title || item.path || item.url || null,
|
|
221
|
+
method: item.method || item.req_method || null
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function extractContentText(payload) {
|
|
226
|
+
const content = Array.isArray(payload.content) ? payload.content : [];
|
|
227
|
+
const textItem = content.find((item) => item && typeof item.text === 'string');
|
|
228
|
+
return textItem ? textItem.text : null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function parseMaybeJson(value) {
|
|
232
|
+
if (typeof value !== 'string') {
|
|
233
|
+
return value;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
return JSON.parse(value);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
return { text: value };
|
|
240
|
+
}
|
|
241
|
+
}
|