digitalsee-ai-flow-cli 0.9.15 → 0.9.24-beta
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/README.md +3 -0
- package/dist/api/client.js +36 -10
- package/dist/api/flowVersion.js +45 -0
- package/dist/api/knowledge.js +58 -17
- package/dist/api/node.js +76 -38
- package/dist/commands/flow/edge.js +71 -12
- package/dist/commands/flow/list.js +2 -1
- package/dist/commands/flow/node.js +147 -45
- package/dist/commands/flow/offline.js +37 -10
- package/dist/commands/flow/online.js +4 -4
- package/dist/commands/flow/publish.js +122 -17
- package/dist/commands/flow/validate.js +12 -0
- package/dist/commands/flow/version.js +303 -0
- package/dist/commands/flow.js +2 -0
- package/dist/commands/knowledge.js +101 -27
- package/dist/services/classicNodePorts.js +413 -0
- package/dist/services/portValidator.js +304 -0
- package/dist/services/skillGenerator.js +215 -29
- package/dist/utils/connectable.js +221 -0
- package/dist/utils/nodePackage.js +23 -1
- package/dist/utils/nodeSchemaCache.js +87 -0
- package/package.json +1 -1
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeConnectablePluginPage = exports.normalizeConnectableCategories = exports.unwrapResponseData = void 0;
|
|
4
|
+
const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
5
|
+
const hasOwn = (record, key) => Object.prototype.hasOwnProperty.call(record, key);
|
|
6
|
+
const firstValue = (record, keys) => {
|
|
7
|
+
for (const key of keys) {
|
|
8
|
+
if (hasOwn(record, key) && record[key] !== undefined && record[key] !== null) {
|
|
9
|
+
return record[key];
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
};
|
|
14
|
+
const firstString = (record, keys) => {
|
|
15
|
+
for (const key of keys) {
|
|
16
|
+
const value = record[key];
|
|
17
|
+
if (typeof value === 'string' && value.trim()) {
|
|
18
|
+
return value.trim();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
};
|
|
23
|
+
const toFiniteNumber = (value) => {
|
|
24
|
+
if (typeof value === 'number') {
|
|
25
|
+
return Number.isFinite(value) ? value : undefined;
|
|
26
|
+
}
|
|
27
|
+
if (typeof value === 'string' && value.trim()) {
|
|
28
|
+
const parsed = Number(value);
|
|
29
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
};
|
|
33
|
+
const toBoolean = (value, fallback = false) => {
|
|
34
|
+
if (typeof value === 'boolean')
|
|
35
|
+
return value;
|
|
36
|
+
if (value === 1 || value === '1' || value === 'true')
|
|
37
|
+
return true;
|
|
38
|
+
if (value === 0 || value === '0' || value === 'false')
|
|
39
|
+
return false;
|
|
40
|
+
return fallback;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* 循环解包 `data` 信封,深度上限 4。
|
|
44
|
+
*
|
|
45
|
+
* 兼容两类输入:
|
|
46
|
+
* - axios interceptor 已解一层 `{error, result, data}` 后的直接业务数据;
|
|
47
|
+
* - 后端多级 `data` 嵌套(ReturnResultVO 包裹)。
|
|
48
|
+
*
|
|
49
|
+
* 响应含 `items`(分页结构)时停止解包,避免吞掉分页字段。
|
|
50
|
+
*/
|
|
51
|
+
const unwrapResponseData = (response) => {
|
|
52
|
+
let current = response;
|
|
53
|
+
let depth = 0;
|
|
54
|
+
while (depth < 4 && isRecord(current) && hasOwn(current, 'data') && !hasOwn(current, 'items')) {
|
|
55
|
+
current = current.data;
|
|
56
|
+
depth += 1;
|
|
57
|
+
}
|
|
58
|
+
return current;
|
|
59
|
+
};
|
|
60
|
+
exports.unwrapResponseData = unwrapResponseData;
|
|
61
|
+
/**
|
|
62
|
+
* 归一化 connectable 分类响应为 `NodeCategory[]`。
|
|
63
|
+
*
|
|
64
|
+
* wire 字段兼容:`name/categoryName/category_name`、`label/name`、`desc/description`、
|
|
65
|
+
* `icon`、`searchAction/search_action`。
|
|
66
|
+
*/
|
|
67
|
+
const normalizeConnectableCategories = (response) => {
|
|
68
|
+
const rawCategories = (0, exports.unwrapResponseData)(response);
|
|
69
|
+
if (!Array.isArray(rawCategories)) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
return rawCategories.filter(isRecord).map((category) => ({
|
|
73
|
+
name: firstString(category, ['name', 'categoryName', 'category_name']) ?? '',
|
|
74
|
+
label: firstString(category, ['label', 'name']) ?? '',
|
|
75
|
+
desc: firstString(category, ['desc', 'description']),
|
|
76
|
+
icon: firstString(category, ['icon']),
|
|
77
|
+
search_action: toBoolean(firstValue(category, ['searchAction', 'search_action'])),
|
|
78
|
+
}));
|
|
79
|
+
};
|
|
80
|
+
exports.normalizeConnectableCategories = normalizeConnectableCategories;
|
|
81
|
+
const normalizeStableIdentifier = (value) => {
|
|
82
|
+
if (typeof value === 'number') {
|
|
83
|
+
return Number.isSafeInteger(value) && value > 0 ? String(value) : undefined;
|
|
84
|
+
}
|
|
85
|
+
if (typeof value !== 'string') {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const normalized = value.trim();
|
|
89
|
+
if (!normalized)
|
|
90
|
+
return undefined;
|
|
91
|
+
if (/^-?\d+$/.test(normalized)) {
|
|
92
|
+
const digits = normalized.startsWith('-') ? normalized.slice(1) : normalized;
|
|
93
|
+
if (normalized.startsWith('-') || /^0+$/.test(digits))
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
return normalized;
|
|
97
|
+
};
|
|
98
|
+
const normalizeBackendLongIdentifier = (value) => {
|
|
99
|
+
if (typeof value === 'number') {
|
|
100
|
+
return Number.isSafeInteger(value) ? String(value) : undefined;
|
|
101
|
+
}
|
|
102
|
+
if (typeof value !== 'string')
|
|
103
|
+
return undefined;
|
|
104
|
+
const normalized = value.trim();
|
|
105
|
+
return /^-?\d+$/.test(normalized) ? normalized : undefined;
|
|
106
|
+
};
|
|
107
|
+
const findStableIdentifier = (record, candidates) => {
|
|
108
|
+
for (const candidate of candidates) {
|
|
109
|
+
const value = normalizeStableIdentifier(record[candidate]);
|
|
110
|
+
if (value !== undefined) {
|
|
111
|
+
return { value, source: candidate };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
};
|
|
116
|
+
const toStringArray = (value) => Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
|
|
117
|
+
const normalizeConnector = (value) => {
|
|
118
|
+
const connector = isRecord(value) ? value : {};
|
|
119
|
+
// 正式 NodePackage 包标识是 connector.id;linkId/appPackage 等仅兼容未升级部署。
|
|
120
|
+
// 展示 name 不参与兜底,避免把可变文案作为身份标识。
|
|
121
|
+
const identifier = findStableIdentifier(connector, [
|
|
122
|
+
'id',
|
|
123
|
+
'linkId',
|
|
124
|
+
'link_id',
|
|
125
|
+
'appPackage',
|
|
126
|
+
'app_package',
|
|
127
|
+
'connectorId',
|
|
128
|
+
'connector_id',
|
|
129
|
+
]);
|
|
130
|
+
return {
|
|
131
|
+
id: identifier?.value ?? '',
|
|
132
|
+
name: firstString(connector, ['name', 'displayName', 'display_name']) ?? '',
|
|
133
|
+
description: firstString(connector, ['description', 'desc']),
|
|
134
|
+
build_type: firstString(connector, ['buildType', 'build_type']),
|
|
135
|
+
app_domain_category: toStringArray(firstValue(connector, ['appDomainCategory', 'app_domain_category'])),
|
|
136
|
+
app_action_category: toStringArray(firstValue(connector, ['appActionCategory', 'app_action_category'])),
|
|
137
|
+
deployment_type: firstString(connector, ['deploymentType', 'deployment_type']),
|
|
138
|
+
icon: firstString(connector, ['icon']),
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
const normalizeAction = (value) => {
|
|
142
|
+
const action = isRecord(value) ? value : {};
|
|
143
|
+
const rawNodeSchema = firstValue(action, ['nodeSchema', 'node_schema']);
|
|
144
|
+
const nodeSchema = isRecord(rawNodeSchema) ? rawNodeSchema : null;
|
|
145
|
+
const schemaNodeKey = nodeSchema ? firstString(nodeSchema, ['key']) : undefined;
|
|
146
|
+
const schemaNodeVersion = nodeSchema
|
|
147
|
+
? toFiniteNumber(firstValue(nodeSchema, ['version']))
|
|
148
|
+
: undefined;
|
|
149
|
+
// 后端数字 id:19 位雪花 Long ID 保持字符串;<=0 是 legacy 哨兵,仅保留给创建接口。
|
|
150
|
+
const backendActionId = normalizeBackendLongIdentifier(firstValue(action, ['id', 'actionId', 'action_id'])) ??
|
|
151
|
+
(schemaNodeKey ? '0' : undefined);
|
|
152
|
+
// NodePackage 的持久化身份是 action_key + action_ver,数字 id 不能作为 UI 身份。
|
|
153
|
+
const actionKey = firstString(action, ['actionKey', 'action_key', 'nodeKey', 'node_key']) ?? schemaNodeKey;
|
|
154
|
+
return {
|
|
155
|
+
...action,
|
|
156
|
+
id: actionKey ?? '',
|
|
157
|
+
backend_action_id: backendActionId,
|
|
158
|
+
action_key: actionKey ?? '',
|
|
159
|
+
action_ver: toFiniteNumber(firstValue(action, ['actionVer', 'action_ver', 'nodeVersion', 'node_version', 'version'])) ?? schemaNodeVersion,
|
|
160
|
+
is_trigger: toBoolean(firstValue(action, ['isTrigger', 'is_trigger'])),
|
|
161
|
+
name: firstString(action, ['name', 'actionName', 'action_name']) ?? '',
|
|
162
|
+
description: firstString(action, ['description', 'desc']) ?? null,
|
|
163
|
+
// connectable 只返回已加载且当前租户可见的 NodePackage,统一置为可用。
|
|
164
|
+
status: 1,
|
|
165
|
+
protocol: firstString(action, ['protocol']) ?? '',
|
|
166
|
+
config_schema: firstValue(action, ['configSchema', 'config_schema']) ?? {},
|
|
167
|
+
sample_output: firstValue(action, ['sampleOutput', 'sample_output']),
|
|
168
|
+
node_struct: firstString(action, ['nodeStruct', 'node_struct']) ?? '',
|
|
169
|
+
action_type: firstString(action, ['actionType', 'action_type']) ?? null,
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
const normalizeMatchType = (value) => {
|
|
173
|
+
if (typeof value !== 'string' || !value.trim())
|
|
174
|
+
return undefined;
|
|
175
|
+
const normalized = value.trim().toUpperCase();
|
|
176
|
+
switch (normalized) {
|
|
177
|
+
case 'CONNECTOR_ONL':
|
|
178
|
+
return 'CONNECTOR_ONLY';
|
|
179
|
+
case 'ACTION_MATCHED':
|
|
180
|
+
return 'ACTION_ONLY';
|
|
181
|
+
case 'BOTH':
|
|
182
|
+
return 'BOTH_MATCHED';
|
|
183
|
+
default:
|
|
184
|
+
return normalized;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const readPageNumber = (page, keys, fallback) => toFiniteNumber(firstValue(page, keys)) ?? fallback;
|
|
188
|
+
/**
|
|
189
|
+
* 归一化 connectable 插件分页响应为 `SearchLinksResponse`。
|
|
190
|
+
*
|
|
191
|
+
* wire 字段兼容:`items/records`、`page/current`、`pages/total/size`、
|
|
192
|
+
* `matchedActions/matched_actions`、`defaultExpanded/default_expanded`、
|
|
193
|
+
* `matchType/match_type`(旧枚举转换)。
|
|
194
|
+
*/
|
|
195
|
+
const normalizeConnectablePluginPage = (response) => {
|
|
196
|
+
const unwrapped = (0, exports.unwrapResponseData)(response);
|
|
197
|
+
const page = isRecord(unwrapped) ? unwrapped : {};
|
|
198
|
+
const rawItems = firstValue(page, ['items', 'records']);
|
|
199
|
+
const items = Array.isArray(rawItems)
|
|
200
|
+
? rawItems.filter(isRecord).map((item) => {
|
|
201
|
+
const connector = normalizeConnector(item.connector);
|
|
202
|
+
const rawActions = firstValue(item, ['matchedActions', 'matched_actions']);
|
|
203
|
+
const matchedActions = Array.isArray(rawActions) ? rawActions.map(normalizeAction) : [];
|
|
204
|
+
return {
|
|
205
|
+
connector,
|
|
206
|
+
matched_actions: matchedActions,
|
|
207
|
+
default_expanded: toBoolean(firstValue(item, ['defaultExpanded', 'default_expanded'])),
|
|
208
|
+
match_type: normalizeMatchType(firstValue(item, ['matchType', 'match_type'])),
|
|
209
|
+
};
|
|
210
|
+
})
|
|
211
|
+
: [];
|
|
212
|
+
return {
|
|
213
|
+
items,
|
|
214
|
+
total: readPageNumber(page, ['total'], items.length),
|
|
215
|
+
size: readPageNumber(page, ['size'], items.length),
|
|
216
|
+
page: readPageNumber(page, ['page', 'current'], 1),
|
|
217
|
+
pages: readPageNumber(page, ['pages'], 0),
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
exports.normalizeConnectablePluginPage = normalizeConnectablePluginPage;
|
|
221
|
+
//# sourceMappingURL=connectable.js.map
|
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.NODE_PACKAGE_DOWNGRADE_HINT = void 0;
|
|
36
37
|
exports.getCachedNodePackageSupport = getCachedNodePackageSupport;
|
|
37
38
|
exports.getNodePackageSupportFor = getNodePackageSupportFor;
|
|
38
39
|
exports.checkAndCacheNodePackageSupport = checkAndCacheNodePackageSupport;
|
|
@@ -45,6 +46,11 @@ const STATE_DIR = path.join(os.homedir(), '.ai-flow-cli');
|
|
|
45
46
|
function getStateFile() {
|
|
46
47
|
return path.join(STATE_DIR, `node-package-${(0, config_1.getActiveProfile)()}.json`);
|
|
47
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* 本版本统一使用 connectable API,不再回退旧版接口;
|
|
51
|
+
* 目标 baseUrl 不支持 NodePackage 时给出的降级建议。
|
|
52
|
+
*/
|
|
53
|
+
exports.NODE_PACKAGE_DOWNGRADE_HINT = '本版本已统一使用 NodePackage connectable API,目标服务不支持;如需连接旧版服务,请降级 CLI:npm install -g digitalsee-ai-flow-cli@0.9.15';
|
|
48
54
|
function ensureStateDir() {
|
|
49
55
|
if (!fs.existsSync(STATE_DIR)) {
|
|
50
56
|
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
@@ -78,8 +84,24 @@ function getNodePackageSupportFor(profileName) {
|
|
|
78
84
|
}
|
|
79
85
|
return undefined;
|
|
80
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Probe NodePackage support and cache the conclusion per profile.
|
|
89
|
+
*
|
|
90
|
+
* Only a definitive probe result (supported or unsupported) is written to the
|
|
91
|
+
* cache file. Transient errors (network, auth, 5xx) leave the existing cache
|
|
92
|
+
* untouched — a previous conclusion stays valid and the caller gets the stale
|
|
93
|
+
* value (or `false` when nothing is cached) instead of locking in a wrong
|
|
94
|
+
* "unsupported" answer.
|
|
95
|
+
*/
|
|
81
96
|
async function checkAndCacheNodePackageSupport() {
|
|
82
|
-
|
|
97
|
+
let supported;
|
|
98
|
+
try {
|
|
99
|
+
supported = await (0, node_1.isNodePackageSupported)();
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
const cached = getCachedNodePackageSupport();
|
|
103
|
+
return cached ?? false;
|
|
104
|
+
}
|
|
83
105
|
ensureStateDir();
|
|
84
106
|
fs.writeFileSync(getStateFile(), JSON.stringify({ supported, checkedAt: Date.now() }), 'utf-8');
|
|
85
107
|
return supported;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clearNodePackageSchemaCache = clearNodePackageSchemaCache;
|
|
4
|
+
exports.setNodePackageSchema = setNodePackageSchema;
|
|
5
|
+
exports.peekNodePackageSchema = peekNodePackageSchema;
|
|
6
|
+
exports.getNodePackageSchema = getNodePackageSchema;
|
|
7
|
+
const node_1 = require("@/api/node");
|
|
8
|
+
/**
|
|
9
|
+
* NodePackage node schema cache.
|
|
10
|
+
*
|
|
11
|
+
* Cache dimension: (nodeKey, version) → NodeSchema.
|
|
12
|
+
* - Invalid versions (undefined/null/negative/non-finite) use an `@latest`
|
|
13
|
+
* placeholder key: only pre-warmed cache entries are served and no remote
|
|
14
|
+
* request is fired, avoiding ambiguous lookups.
|
|
15
|
+
* - Valid numeric versions (including 0) are cached precisely as
|
|
16
|
+
* `${nodeKey}@${version}`.
|
|
17
|
+
* - Concurrent calls with the same key share one pending Promise.
|
|
18
|
+
*/
|
|
19
|
+
const schemaCacheByKey = new Map();
|
|
20
|
+
const pendingRequestsByKey = new Map();
|
|
21
|
+
function buildSchemaCacheKey(nodeKey, version) {
|
|
22
|
+
if (!nodeKey) {
|
|
23
|
+
throw new Error('nodeSchemaCache: nodeKey is required');
|
|
24
|
+
}
|
|
25
|
+
if (version === undefined || version === null) {
|
|
26
|
+
return `${nodeKey}@latest`;
|
|
27
|
+
}
|
|
28
|
+
if (typeof version !== 'number' || !Number.isFinite(version) || version < 0) {
|
|
29
|
+
return `${nodeKey}@latest`;
|
|
30
|
+
}
|
|
31
|
+
return `${nodeKey}@${version}`;
|
|
32
|
+
}
|
|
33
|
+
function isResolvableVersion(version) {
|
|
34
|
+
return (version !== undefined &&
|
|
35
|
+
version !== null &&
|
|
36
|
+
typeof version === 'number' &&
|
|
37
|
+
Number.isFinite(version) &&
|
|
38
|
+
version >= 0);
|
|
39
|
+
}
|
|
40
|
+
function clearNodePackageSchemaCache() {
|
|
41
|
+
schemaCacheByKey.clear();
|
|
42
|
+
pendingRequestsByKey.clear();
|
|
43
|
+
}
|
|
44
|
+
function setNodePackageSchema(nodeKey, version, schema) {
|
|
45
|
+
if (!nodeKey || !schema) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
schemaCacheByKey.set(buildSchemaCacheKey(nodeKey, version), schema);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Synchronously read a pre-warmed schema without triggering a network request.
|
|
52
|
+
*/
|
|
53
|
+
function peekNodePackageSchema(nodeKey, version) {
|
|
54
|
+
if (!nodeKey)
|
|
55
|
+
return null;
|
|
56
|
+
return schemaCacheByKey.get(buildSchemaCacheKey(nodeKey, version)) ?? null;
|
|
57
|
+
}
|
|
58
|
+
async function getNodePackageSchema(nodeKey, version) {
|
|
59
|
+
if (!nodeKey) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const cacheKey = buildSchemaCacheKey(nodeKey, version);
|
|
63
|
+
if (schemaCacheByKey.has(cacheKey)) {
|
|
64
|
+
return schemaCacheByKey.get(cacheKey);
|
|
65
|
+
}
|
|
66
|
+
if (pendingRequestsByKey.has(cacheKey)) {
|
|
67
|
+
return pendingRequestsByKey.get(cacheKey);
|
|
68
|
+
}
|
|
69
|
+
// Invalid/missing version: @latest placeholder, serve pre-warmed cache only
|
|
70
|
+
if (!isResolvableVersion(version)) {
|
|
71
|
+
return schemaCacheByKey.get(cacheKey) ?? null;
|
|
72
|
+
}
|
|
73
|
+
const request = (0, node_1.getActionNodeSchema)(nodeKey, version)
|
|
74
|
+
.then((schema) => {
|
|
75
|
+
schemaCacheByKey.set(cacheKey, schema);
|
|
76
|
+
return schema;
|
|
77
|
+
})
|
|
78
|
+
.catch(() => {
|
|
79
|
+
return null;
|
|
80
|
+
})
|
|
81
|
+
.finally(() => {
|
|
82
|
+
pendingRequestsByKey.delete(cacheKey);
|
|
83
|
+
});
|
|
84
|
+
pendingRequestsByKey.set(cacheKey, request);
|
|
85
|
+
return request;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=nodeSchemaCache.js.map
|