digitalsee-ai-flow-cli 0.9.24-beta → 0.9.29-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 +146 -2
- package/dist/api/client.js +9 -0
- package/dist/api/flow.js +4 -1
- package/dist/api/node.js +78 -7
- package/dist/api/trigger.js +242 -0
- package/dist/commands/flow/account.js +439 -1
- package/dist/commands/flow/analyze.js +10 -2
- package/dist/commands/flow/export.js +29 -4
- package/dist/commands/flow/helpers.js +439 -53
- package/dist/commands/flow/node.js +290 -32
- package/dist/commands/flow/offline.js +3 -2
- package/dist/commands/flow/trigger.js +190 -0
- package/dist/commands/flow/validate.js +57 -0
- package/dist/commands/flow/variables.js +1 -0
- package/dist/commands/flow.js +2 -0
- package/dist/constants/node-type.js +23 -0
- package/dist/services/classicNodePorts.js +167 -368
- package/dist/services/fieldTypeRegistry.js +496 -0
- package/dist/services/flowAnalyzer.js +8 -1
- package/dist/services/flowValidator.js +145 -0
- package/dist/services/portValidator.js +76 -70
- package/dist/services/skillGenerator.js +236 -194
- package/dist/services/triggerConfig.js +252 -0
- package/dist/utils/conditionRules.js +128 -0
- package/dist/utils/format.js +268 -168
- package/dist/utils/nodeEnvelope.js +128 -0
- package/dist/utils/switchBranch.js +83 -0
- package/dist/utils/updateCheck.js +50 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -396,6 +396,8 @@ ai-flow flow export <flowId> --json
|
|
|
396
396
|
| `--output` | 否 | `./{name}.yaml` | 输出路径,若为目录则自动追加文件名 |
|
|
397
397
|
| `--json` | 否 | — | 以 JSON 格式输出 `{ ok, id, file, message }` |
|
|
398
398
|
|
|
399
|
+
导出文件保持服务端 YAML 原样(保证 `flow import` 回导兼容)。当导出内容包含 NodePackage 提交信封字段(`proxy_id` / `mcp_tool_description`)时:人类可读模式输出提示,`--json` 模式额外返回 `envelopeKeys` 数组;可用 `flow node show` 查看解包后的可读结构。
|
|
400
|
+
|
|
399
401
|
### flow import
|
|
400
402
|
|
|
401
403
|
导入 YAML 连接流文件。支持 `.yaml` / `.yml` 格式,文件大小不超过 5MB。
|
|
@@ -569,8 +571,14 @@ ai-flow flow node create \
|
|
|
569
571
|
3. POST 创建节点
|
|
570
572
|
|
|
571
573
|
> 不传 `--data`/`--data-file` 时使用默认值填充。`--data`/`--data-file` 内容必须为 `{"properties": {"fieldName": "value"}}` 格式,配置字段包裹在 `properties` 中,键名对应配置字段的 `name`。对于特殊节点类型(ConditionNode/LoopNode/SwitchNode),额外字段(`or_list`/`branches`/`type` 等)与 `properties` 同级传入,会自动转发。详见 [特殊节点类型配置指南](docs/special-node-types.md)。`--trigger` 标志会筛选触发器类型的动作。
|
|
574
|
+
>
|
|
575
|
+
> **NodePackage 提交信封**:NodePackage 节点(`action_key + action_ver` 身份)的 `--data` 顶层是提交信封,合法键:`properties`(业务配置)、`position`、`proxy_id`(代理网关 ID,字符串,`"0"`=不使用)、`mcp_tool_description`(MCP 工具描述)、`account_id`(建议用 `--account-id`)。PROXY 类型字段(`node_schema.input_fields` 中 `type=PROXY`)由宿主处理,**写在 `properties` 中不会生效**,会被校验拦截并提示改用顶层 `proxy_id`:
|
|
576
|
+
> ```bash
|
|
577
|
+
> # 正确:代理选择走信封顶层 proxy_id
|
|
578
|
+
> ai-flow flow node create ... --data '{"properties": {"query": "hi"}, "proxy_id": "6880053123997337600"}'
|
|
579
|
+
> ```
|
|
572
580
|
|
|
573
|
-
> **账号配置**:当动作的 `config_schema` 包含 `account_schema_id` 时,需要通过 `--account-id` 指定账号实例 ID。若未指定且仅 1 个可用账号,CLI
|
|
581
|
+
> **账号配置**:当动作的 `config_schema` 包含 `account_schema_id` 时,需要通过 `--account-id` 指定账号实例 ID。若未指定且仅 1 个可用账号,CLI 自动选择;多个账号时通过结构化输出返回可用列表;0 个账号时提示通过 `flow account create` 创建。
|
|
574
582
|
|
|
575
583
|
> **错误处理配置**:通过 `--error-handler-file` 指定 JSON 文件配置节点的错误处理策略,格式如下。`process_type`: `1`=中断流程, `2`=忽略错误;`warn_type`: `1`=启用告警, `0`=禁用。
|
|
576
584
|
> ```json
|
|
@@ -609,6 +617,7 @@ ai-flow flow node show <nodeId> --json
|
|
|
609
617
|
描述 (无)
|
|
610
618
|
账号 ID (无)
|
|
611
619
|
账号 Schema ID (无)
|
|
620
|
+
代理 ID (无)
|
|
612
621
|
|
|
613
622
|
--- 配置参数 (3) ---
|
|
614
623
|
字段名 显示名 类型 必填 当前值 可选值
|
|
@@ -620,6 +629,11 @@ timeout 超时时间(ms) NUMBER 否 5000 -
|
|
|
620
629
|
{ "method": "POST", "content_type": "application/json", "timeout": 5000 }
|
|
621
630
|
```
|
|
622
631
|
|
|
632
|
+
NodePackage 节点的 properties 若被包裹为提交信封(`{properties: {...}, account_id, proxy_id, mcp_tool_description}`),会自动解包为业务字段;信封顶层的宿主字段单独展示:
|
|
633
|
+
|
|
634
|
+
- 人类可读模式:`代理 ID` 显示 proxy_id(`0` 时显示「不使用」)、`MCP 工具描述` 单独分节展示
|
|
635
|
+
- `--json` 模式:`data.properties` 为解包后的业务字段,`data.proxy_id` / `data.mcp_tool_description` 为识别出的信封字段
|
|
636
|
+
|
|
623
637
|
### flow node update
|
|
624
638
|
|
|
625
639
|
更新连接流中的节点配置。使用 `--patch`/`--patch-file` 做深度合并更新(`--data` 已废弃)。支持通过 `--sample-output` 设置节点样本输出,供下游节点测试时引用变量。
|
|
@@ -653,6 +667,12 @@ ai-flow flow node update <nodeId> --flow-id <flowId> --connector-id <id> --actio
|
|
|
653
667
|
ai-flow flow node update <nodeId> --flow-id <flowId> --account-id <accountId>
|
|
654
668
|
ai-flow flow node update <nodeId> --flow-id <flowId> --error-handler-file ./error-handler.json
|
|
655
669
|
|
|
670
|
+
# 更新 NodePackage 信封字段(代理网关 / MCP 工具描述,与 properties 同级)
|
|
671
|
+
ai-flow flow node update <nodeId> --flow-id <flowId> \
|
|
672
|
+
--patch '{"properties": {"query": "hi"}, "proxy_id": "6880053123997337600"}'
|
|
673
|
+
ai-flow flow node update <nodeId> --flow-id <flowId> \
|
|
674
|
+
--patch '{"properties": {}, "mcp_tool_description": "encode tool"}'
|
|
675
|
+
|
|
656
676
|
# 设置样本输出(下游节点可通过 {{N<nodeId>.field}} 引用)
|
|
657
677
|
ai-flow flow node update <nodeId> --flow-id <flowId> --sample-output '{"key":"value"}'
|
|
658
678
|
ai-flow flow node update <nodeId> --flow-id <flowId> --sample-output-file ./sample.json
|
|
@@ -689,6 +709,8 @@ ai-flow flow node update <nodeId> --flow-id <flowId> --patch-file ./config.json
|
|
|
689
709
|
|
|
690
710
|
> `--patch` 与 `--data` 的区别:`--patch` 与现有配置做深度合并,语义明确为 patch 操作;`--data` 为旧参数,语义不统一,已废弃。
|
|
691
711
|
|
|
712
|
+
> **NodePackage 信封字段**:NodePackage 节点(`action_key + action_ver` 身份)的 `--patch` 顶层可携带 `proxy_id`(代理网关 ID,字符串,`"0"`=不使用)与 `mcp_tool_description`(MCP 工具描述),与 `properties` 同级。PROXY 类型字段写在 `properties` 中会被校验拦截,须改用顶层 `proxy_id`。节点已有信封字段在更新时自动回传,详见 [特殊节点类型配置指南](docs/special-node-types.md)。
|
|
713
|
+
|
|
692
714
|
### flow node delete
|
|
693
715
|
|
|
694
716
|
删除连接流中的节点。默认需 `--yes` 确认(面向 Agent 的非交互模式)。
|
|
@@ -739,6 +761,128 @@ ai-flow flow account list <connectorId> --json
|
|
|
739
761
|
| `--account-schema-id` | 否 | 按账号模型过滤 |
|
|
740
762
|
| `--json` | 否 | 以 JSON 格式输出 |
|
|
741
763
|
|
|
764
|
+
### flow account create
|
|
765
|
+
|
|
766
|
+
为连接器创建账号实例。凭据字段校验复用字段类型契约注册表(credential schema 字段的 required/类型/pattern 等规则),非法数据在提交前被拦截;PASSWORD 类字段在输出中脱敏展示。
|
|
767
|
+
|
|
768
|
+
```bash
|
|
769
|
+
# 基本用法:凭据 JSON 内联提供
|
|
770
|
+
ai-flow flow account create <connectorId> --account-schema-id <schemaId> --data '{"host":"db.local","port":3306,"username":"root","password":"***"}'
|
|
771
|
+
|
|
772
|
+
# 从文件读取凭据(与 --data 互斥)
|
|
773
|
+
ai-flow flow account create <connectorId> --account-schema-id <schemaId> --data-file credentials.json
|
|
774
|
+
|
|
775
|
+
# 指定实例名称与代理网关
|
|
776
|
+
ai-flow flow account create <connectorId> --account-schema-id <schemaId> --data-file credentials.json --name "生产库" --proxy-id <proxyId>
|
|
777
|
+
|
|
778
|
+
# JSON 输出(含脱敏后的凭据回显)
|
|
779
|
+
ai-flow flow account create <connectorId> --account-schema-id <schemaId> --data '...' --json
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
| 选项 | 必填 | 说明 |
|
|
783
|
+
|------|------|------|
|
|
784
|
+
| `--account-schema-id` | 是 | 账号模型 ID(数字 schema id 或 account_key) |
|
|
785
|
+
| `--data` | 是* | 凭据字段 JSON(key → 值),与 `--data-file` 互斥 |
|
|
786
|
+
| `--data-file` | 是* | 凭据字段 JSON 文件路径 |
|
|
787
|
+
| `--name` | 否 | 实例显示名称(缺省使用账号模型名称) |
|
|
788
|
+
| `--proxy-id` | 否 | 代理网关 ID |
|
|
789
|
+
| `--json` | 否 | 以 JSON 格式输出 `{ ok, id, account, message }` |
|
|
790
|
+
|
|
791
|
+
创建成功后可直接用于 `flow node create`(单实例时自动选中)。当目标账号不存在时,`flow node create` 的错误信息会提示本命令。
|
|
792
|
+
|
|
793
|
+
### flow account test
|
|
794
|
+
|
|
795
|
+
测试账号实例连通性(服务端校验凭据,RDBMS 类账号会真实建连)。失败时输出服务端返回的字段级错误(如凭据 JSON 消息中的 `errors` 数组)。
|
|
796
|
+
|
|
797
|
+
```bash
|
|
798
|
+
ai-flow flow account test <accountId>
|
|
799
|
+
ai-flow flow account test <accountId> --json
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
| 选项 | 必填 | 说明 |
|
|
803
|
+
|------|------|------|
|
|
804
|
+
| `--json` | 否 | 以 JSON 格式输出 `{ ok, data: { account_id, result }, message }`,失败时附 `fieldErrors` |
|
|
805
|
+
|
|
806
|
+
> 典型链路:`flow account create` → `flow node create`(自动选号)→ `flow account test`,全程无需回 UI。
|
|
807
|
+
|
|
808
|
+
### flow trigger — 触发器配置(cron / webhook)
|
|
809
|
+
|
|
810
|
+
读取与配置连接流的触发器(对齐 rcs/DIP `DipTriggerConfig` 契约)。配置存储在触发器节点(`StartEventNode`)的 `properties` 上,要求流中已存在触发器节点(通过 `flow node create --trigger` 创建,见上文)。三种触发模式:`manual`(手动)、`cron`(定时)、`webhook`(Web 回调)。
|
|
811
|
+
|
|
812
|
+
```bash
|
|
813
|
+
# 查看当前触发器配置(未配置时显示 manual)
|
|
814
|
+
ai-flow flow trigger show <flowId> [--json]
|
|
815
|
+
|
|
816
|
+
# 配置 cron 定时触发器(Quartz 风格表达式:秒 分 时 日 月 周 [年])
|
|
817
|
+
ai-flow flow trigger set-cron <flowId> --expression "0 0/5 * * * ?" --timezone Asia/Shanghai
|
|
818
|
+
|
|
819
|
+
# 配置 webhook 触发器
|
|
820
|
+
ai-flow flow trigger set-webhook <flowId> --path /hooks/flow1 \
|
|
821
|
+
--method POST --response-mode IMMEDIATE
|
|
822
|
+
|
|
823
|
+
# webhook + 自定义响应体(responseMode=CUSTOM 时 --response-data 必填)
|
|
824
|
+
ai-flow flow trigger set-webhook <flowId> --path /hooks/flow1 \
|
|
825
|
+
--response-mode CUSTOM --response-data '{"ok": true}'
|
|
826
|
+
|
|
827
|
+
# webhook + Header 鉴权(--auth-header-name 与 --auth-header-value 必须成对)
|
|
828
|
+
ai-flow flow trigger set-webhook <flowId> --path /hooks/flow1 \
|
|
829
|
+
--auth-required --auth-header-name X-Api-Key --auth-header-value secret123
|
|
830
|
+
|
|
831
|
+
# 切回手动触发(清除 cron/webhook 配置)
|
|
832
|
+
ai-flow flow trigger set-manual <flowId>
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
#### flow trigger show
|
|
836
|
+
|
|
837
|
+
| 选项 | 必填 | 说明 |
|
|
838
|
+
|------|------|------|
|
|
839
|
+
| `--json` | 否 | 以 JSON 输出 `{ ok, data: { flowId, nodeId, connectorId, connectorName, actionId, actionName, trigger } }` |
|
|
840
|
+
|
|
841
|
+
`trigger` 字段结构(未配置时为 `null`,人类可读模式显示为 manual):
|
|
842
|
+
|
|
843
|
+
```json
|
|
844
|
+
// cron
|
|
845
|
+
{ "mode": "cron", "expression": "0 0/5 * * * ?", "timezone": "Asia/Shanghai" }
|
|
846
|
+
// webhook
|
|
847
|
+
{ "mode": "webhook", "path": "/hooks/flow1", "httpMethod": "POST",
|
|
848
|
+
"responseMode": "IMMEDIATE", "authRequired": false }
|
|
849
|
+
// manual
|
|
850
|
+
{ "mode": "manual" }
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
#### flow trigger set-cron
|
|
854
|
+
|
|
855
|
+
| 选项 | 必填 | 默认值 | 说明 |
|
|
856
|
+
|------|------|--------|------|
|
|
857
|
+
| `--expression` | 是 | — | Quartz cron 表达式,6-7 段(秒 分 时 日 月 周 [年]),支持 `*` `?` 数字、区间 `a-b`、步长 `n/m`、列表 `a,b,c`,周字段支持 `MON-FRI` / `SUN` 名称(1-7) |
|
|
858
|
+
| `--timezone` | 否 | 服务器时区 | IANA 时区标识(如 `Asia/Shanghai`) |
|
|
859
|
+
| `--json` | 否 | — | 以 JSON 输出 `{ ok, id, message, data: { trigger } }` |
|
|
860
|
+
|
|
861
|
+
示例表达式:`0 0/5 * * * ?`(每 5 分钟)、`0 0 12 * * ?`(每天 12:00)、`30 15 * * ? MON-FRI`(工作日 15:30)、`0 0 0 1 1 ? 2027`(2027 年元旦)。
|
|
862
|
+
|
|
863
|
+
#### flow trigger set-webhook
|
|
864
|
+
|
|
865
|
+
| 选项 | 必填 | 默认值 | 说明 |
|
|
866
|
+
|------|------|--------|------|
|
|
867
|
+
| `--path` | 是 | — | webhook 路径,以 `/` 开头,最大 256 字符 |
|
|
868
|
+
| `--method` | 否 | `POST` | HTTP 方法:`GET` \| `POST` |
|
|
869
|
+
| `--response-mode` | 否 | `IMMEDIATE` | 响应模式:`IMMEDIATE`(立即返回)\| `LAST_NODE`(末节点输出)\| `CUSTOM`(自定义响应体) |
|
|
870
|
+
| `--response-data` | CUSTOM 时必填 | — | 自定义响应体字符串 |
|
|
871
|
+
| `--auth-required` | 否 | `false` | 启用 Header 鉴权(提供 auth 选项对时自动视为启用) |
|
|
872
|
+
| `--auth-header-name` | 否 | — | 鉴权 Header 名称(与 `--auth-header-value` 成对,字母开头) |
|
|
873
|
+
| `--auth-header-value` | 否 | — | 鉴权 Header 值(与 `--auth-header-name` 成对) |
|
|
874
|
+
| `--json` | 否 | — | 以 JSON 输出 |
|
|
875
|
+
|
|
876
|
+
#### flow trigger set-manual
|
|
877
|
+
|
|
878
|
+
| 选项 | 必填 | 说明 |
|
|
879
|
+
|------|------|------|
|
|
880
|
+
| `--json` | 否 | 以 JSON 输出 |
|
|
881
|
+
|
|
882
|
+
> 校验规则:非法 cron 表达式(段数、取值范围、区间端点、步长)、不以 `/` 开头或含非法字符的 path、非 `GET/POST` 方法、非枚举 responseMode、`CUSTOM` 缺少 `responseData`、`authHeaderName/Value` 不成对均会被拦截并以退出码 1 失败。
|
|
883
|
+
>
|
|
884
|
+
> 与 `flow node create --trigger` 的衔接:先创建触发器节点(选择 cron/webhook 触发器动作),再用 `flow trigger set-*` 写入具体配置;`flow trigger show` 回读校验配置一致。
|
|
885
|
+
|
|
742
886
|
## flow edge connect
|
|
743
887
|
|
|
744
888
|
连接节点(支持单条和批量)。通过 `--source-handle` / `--target-handle` 连接到多端点节点的特定出口/入口。
|
|
@@ -1587,7 +1731,7 @@ src/
|
|
|
1587
1731
|
│ │ ├── test.ts # flow test
|
|
1588
1732
|
│ │ ├── node.ts # flow node (show/create/update/delete/list)
|
|
1589
1733
|
│ │ ├── edge.ts # flow edge (connect/disconnect)
|
|
1590
|
-
│ │ ├── account.ts # flow account list
|
|
1734
|
+
│ │ ├── account.ts # flow account list/create/test
|
|
1591
1735
|
│ │ ├── variables.ts # flow variables (show/inject)
|
|
1592
1736
|
│ │ ├── log.ts # flow log (list/show)
|
|
1593
1737
|
│ │ ├── draft.ts # flow draft (create/commit/discard)
|
package/dist/api/client.js
CHANGED
|
@@ -24,6 +24,15 @@ function isEnvelopeBusinessError(body) {
|
|
|
24
24
|
return code !== '' && !ENVELOPE_SUCCESS_CODES.has(code.toUpperCase());
|
|
25
25
|
}
|
|
26
26
|
function envelopeErrorMessage(body) {
|
|
27
|
+
// error 为对象形态({code, message}):拼成 "[code] message" 优先透出
|
|
28
|
+
if (body.error && typeof body.error === 'object') {
|
|
29
|
+
const record = body.error;
|
|
30
|
+
const msg = [record.message, record.msg, record.error].find((m) => typeof m === 'string' && m !== '');
|
|
31
|
+
const code = record.code ?? body.code;
|
|
32
|
+
if (msg)
|
|
33
|
+
return code !== undefined ? `[${String(code)}] ${msg}` : msg;
|
|
34
|
+
return JSON.stringify(body.error);
|
|
35
|
+
}
|
|
27
36
|
const code = body.error ?? body.code;
|
|
28
37
|
return (body.error_description ||
|
|
29
38
|
body.message ||
|
package/dist/api/flow.js
CHANGED
|
@@ -125,12 +125,15 @@ async function copyFlow(flowId) {
|
|
|
125
125
|
async function deleteFlow(flowId) {
|
|
126
126
|
return (0, client_1.apiDelete)(`/acm/flows/${flowId}`);
|
|
127
127
|
}
|
|
128
|
+
/** 导出连接流为 YAML 并写入文件,返回服务端返回的原始 YAML 文本 */
|
|
128
129
|
async function exportFlow(flowId, outputPath) {
|
|
129
130
|
const client = (0, client_1.getClient)();
|
|
130
131
|
const response = await client.get(`/acm/export/flowInstance/${flowId}`, {
|
|
131
132
|
responseType: 'text',
|
|
132
133
|
});
|
|
133
|
-
|
|
134
|
+
const yamlText = typeof response.data === 'string' ? response.data : String(response.data);
|
|
135
|
+
fs.writeFileSync(outputPath, yamlText, 'utf-8');
|
|
136
|
+
return yamlText;
|
|
134
137
|
}
|
|
135
138
|
async function importFlow(filePath) {
|
|
136
139
|
const client = (0, client_1.getClient)();
|
package/dist/api/node.js
CHANGED
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getActionDetail = getActionDetail;
|
|
4
4
|
exports.getActionNodeSchema = getActionNodeSchema;
|
|
5
|
+
exports.getActions = getActions;
|
|
6
|
+
exports.searchActions = searchActions;
|
|
5
7
|
exports.isNodePackageSupported = isNodePackageSupported;
|
|
6
8
|
exports.getNodeDetail = getNodeDetail;
|
|
7
9
|
exports.createNode = createNode;
|
|
8
10
|
exports.updateNode = updateNode;
|
|
9
11
|
exports.getAccountInstances = getAccountInstances;
|
|
12
|
+
exports.getAccountInstanceDetail = getAccountInstanceDetail;
|
|
13
|
+
exports.getAccountModel = getAccountModel;
|
|
14
|
+
exports.createAccountInstance = createAccountInstance;
|
|
15
|
+
exports.testAccountInstance = testAccountInstance;
|
|
10
16
|
exports.deleteNode = deleteNode;
|
|
11
17
|
exports.testRunNode = testRunNode;
|
|
12
18
|
exports.cancelDebugNode = cancelDebugNode;
|
|
@@ -20,9 +26,20 @@ const encoding_1 = require("@/utils/encoding");
|
|
|
20
26
|
* whose persistent identity is `action_key + action_ver` — the path params
|
|
21
27
|
* there are the string action key and the numeric action version, not a
|
|
22
28
|
* backend id.
|
|
29
|
+
*
|
|
30
|
+
* id 未命中时服务端返回 result:true 的空 data 信封(无 data 键,拦截器不解包):
|
|
31
|
+
* 此处识别信封形态返回 undefined,避免空信封被调用方误当动作对象
|
|
32
|
+
* (曾致创建链路带 action_id='0' 且无 action_key,服务端报
|
|
33
|
+
* "The node key cannot be empty")。
|
|
23
34
|
*/
|
|
24
35
|
async function getActionDetail(id, version) {
|
|
25
|
-
|
|
36
|
+
const detail = await (0, client_1.apiGet)(`/acm/actions/${id}${version ? '/' + version : ''}`);
|
|
37
|
+
// 与 client 拦截器的信封判定对齐(data 键存在时已被解包,此处只见未解包的信封)
|
|
38
|
+
const looksLikeEnvelope = detail instanceof Object && ('result' in detail || 'error' in detail || 'code' in detail);
|
|
39
|
+
if (looksLikeEnvelope) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
return detail;
|
|
26
43
|
}
|
|
27
44
|
function parseMaybeJson(value) {
|
|
28
45
|
if (typeof value !== 'string') {
|
|
@@ -78,19 +95,47 @@ async function getActionNodeSchema(nodeKey, version) {
|
|
|
78
95
|
const response = await (0, client_1.apiGet)(`/acm/actions/${encodeURIComponent(nodeKey)}/${version}`);
|
|
79
96
|
return normalizeActionNodeSchemaResponse(response, nodeKey, version);
|
|
80
97
|
}
|
|
98
|
+
async function getActions(params) {
|
|
99
|
+
const data = await (0, client_1.apiGet)('/acm/actions/v2', params);
|
|
100
|
+
if (Array.isArray(data))
|
|
101
|
+
return data;
|
|
102
|
+
if (data && Array.isArray(data.items))
|
|
103
|
+
return data.items;
|
|
104
|
+
if (data && Array.isArray(data.actions))
|
|
105
|
+
return data.actions;
|
|
106
|
+
if (data && Array.isArray(data.data))
|
|
107
|
+
return data.data;
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
async function searchActions(params) {
|
|
111
|
+
const data = await (0, client_1.apiPost)('/acm/links/search', params);
|
|
112
|
+
const actions = [];
|
|
113
|
+
if (data && data.items && Array.isArray(data.items)) {
|
|
114
|
+
for (const item of data.items) {
|
|
115
|
+
if (item.matched_actions && Array.isArray(item.matched_actions)) {
|
|
116
|
+
actions.push(...item.matched_actions);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return actions;
|
|
121
|
+
}
|
|
81
122
|
/**
|
|
82
123
|
* Single-request probe for NodePackage support.
|
|
83
124
|
*
|
|
84
|
-
* Calls `getNodeCategories()` once:
|
|
85
|
-
* empty array) means the connectable API
|
|
86
|
-
* non-array payload) means unsupported → false.
|
|
87
|
-
* auth, 5xx) is re-thrown so callers can distinguish
|
|
88
|
-
* a definitive "unsupported" conclusion.
|
|
125
|
+
* Calls `getNodeCategories({mode:'independent', scene:'normal', trigger:false})` once:
|
|
126
|
+
* a successful array response (including an empty array) means the connectable API
|
|
127
|
+
* exists → true. A 404/NOT_FOUND (or a non-array payload) means unsupported → false.
|
|
128
|
+
* Any other error (network, auth, 5xx) is re-thrown so callers can distinguish
|
|
129
|
+
* transient failures from a definitive "unsupported" conclusion.
|
|
89
130
|
*/
|
|
90
131
|
async function isNodePackageSupported() {
|
|
91
132
|
let categories;
|
|
92
133
|
try {
|
|
93
|
-
categories = await (0, knowledge_1.getNodeCategories)({
|
|
134
|
+
categories = await (0, knowledge_1.getNodeCategories)({
|
|
135
|
+
mode: 'independent',
|
|
136
|
+
scene: 'normal',
|
|
137
|
+
trigger: false,
|
|
138
|
+
});
|
|
94
139
|
}
|
|
95
140
|
catch (error) {
|
|
96
141
|
if (error instanceof Error && error.code === 'NOT_FOUND') {
|
|
@@ -129,6 +174,32 @@ async function getAccountInstances(linkId, accountSchemaId) {
|
|
|
129
174
|
return data.items;
|
|
130
175
|
return [];
|
|
131
176
|
}
|
|
177
|
+
async function getAccountInstanceDetail(id) {
|
|
178
|
+
return (0, client_1.apiGet)(`/acm/accountInstance/${id}`);
|
|
179
|
+
}
|
|
180
|
+
/** 账号模型(credential schema),GET /acm/accounts/:key[/:version] */
|
|
181
|
+
async function getAccountModel(accountKey, version) {
|
|
182
|
+
const url = `/acm/accounts/${encodeURIComponent(accountKey)}${version ? `/${encodeURIComponent(version)}` : ''}`;
|
|
183
|
+
return (0, client_1.apiGet)(url);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 创建账号实例,POST /acm/accountInstance。
|
|
187
|
+
* 对齐 rcs/IAM-Console:body 为 Base64(JSON) 字符串,Content-Type: text/plain。
|
|
188
|
+
*/
|
|
189
|
+
async function createAccountInstance(payload) {
|
|
190
|
+
return (0, client_1.apiPost)('/acm/accountInstance', (0, encoding_1.encodeBase64)(payload), {
|
|
191
|
+
headers: {
|
|
192
|
+
'Content-Type': 'text/plain',
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* 测试账号实例连通性,POST /acm/accountInstance/test(普通 JSON body)。
|
|
198
|
+
* 仅 RDBMS_ACCOUNT 类凭据在 rcs 中展示"测试连接",此处不限制类型,由服务端判定。
|
|
199
|
+
*/
|
|
200
|
+
async function testAccountInstance(payload) {
|
|
201
|
+
return (0, client_1.apiPost)('/acm/accountInstance/test', payload);
|
|
202
|
+
}
|
|
132
203
|
async function deleteNode(nodeId) {
|
|
133
204
|
return (0, client_1.apiDelete)(`/acm/flows/nodes/v2/${nodeId}`);
|
|
134
205
|
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isTriggerNode = isTriggerNode;
|
|
4
|
+
exports.parseTriggerConfig = parseTriggerConfig;
|
|
5
|
+
exports.findTriggerNode = findTriggerNode;
|
|
6
|
+
exports.setTriggerConfig = setTriggerConfig;
|
|
7
|
+
const flow_1 = require("@/api/flow");
|
|
8
|
+
const node_1 = require("@/api/node");
|
|
9
|
+
const node_type_1 = require("@/constants/node-type");
|
|
10
|
+
const nodeEnvelope_1 = require("@/utils/nodeEnvelope");
|
|
11
|
+
/**
|
|
12
|
+
* 触发器配置 API 封装。
|
|
13
|
+
*
|
|
14
|
+
* ACM/DIP 侧未暴露独立的 trigger 读写端点,触发器配置存储在
|
|
15
|
+
* 触发器节点(StartEventNode)的 properties 上(DipTriggerConfig 契约)。
|
|
16
|
+
* 读:flow detail → 定位触发器节点 → node detail → 解包 properties;
|
|
17
|
+
* 写:读节点详情 → 合并新配置 → PATCH /acm/flows/nodes/v2/:nodeId。
|
|
18
|
+
*/
|
|
19
|
+
/** 判断节点是否为触发器节点 */
|
|
20
|
+
function isTriggerNode(node) {
|
|
21
|
+
const type = node.node_type || '';
|
|
22
|
+
return ((0, node_type_1.isTriggerNodeType)(type) ||
|
|
23
|
+
type === 'START' ||
|
|
24
|
+
(node.connector_name?.toLowerCase().includes('webhook') ?? false));
|
|
25
|
+
}
|
|
26
|
+
/** DipTriggerConfig 的 wire 键名(snake_case) */
|
|
27
|
+
const TRIGGER_WIRE_KEYS = new Set([
|
|
28
|
+
'mode',
|
|
29
|
+
'expression',
|
|
30
|
+
'timezone',
|
|
31
|
+
'path',
|
|
32
|
+
'http_method',
|
|
33
|
+
'httpmethod',
|
|
34
|
+
'response_mode',
|
|
35
|
+
'responsemode',
|
|
36
|
+
'response_data',
|
|
37
|
+
'responsedata',
|
|
38
|
+
'auth_required',
|
|
39
|
+
'authrequired',
|
|
40
|
+
'auth_header_name',
|
|
41
|
+
'authheadername',
|
|
42
|
+
'auth_header_value',
|
|
43
|
+
'authheadervalue',
|
|
44
|
+
]);
|
|
45
|
+
function pickString(props, keys) {
|
|
46
|
+
for (const key of keys) {
|
|
47
|
+
const v = props[key];
|
|
48
|
+
if (typeof v === 'string' && v !== '')
|
|
49
|
+
return v;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
function pickBoolean(props, keys) {
|
|
54
|
+
for (const key of keys) {
|
|
55
|
+
const v = props[key];
|
|
56
|
+
if (typeof v === 'boolean')
|
|
57
|
+
return v;
|
|
58
|
+
if (v === 'true')
|
|
59
|
+
return true;
|
|
60
|
+
if (v === 'false')
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 从节点 properties 中识别 DipTriggerConfig。
|
|
67
|
+
*
|
|
68
|
+
* 兼容字段命名风格:snake_case(http_method)与 camelCase(httpMethod),
|
|
69
|
+
* 字符串值自动大写归一(httpMethod/responseMode)。
|
|
70
|
+
*/
|
|
71
|
+
function parseTriggerConfig(properties) {
|
|
72
|
+
const rawMode = properties.mode ?? properties.trigger_mode ?? properties.triggerMode;
|
|
73
|
+
if (typeof rawMode !== 'string' || !rawMode) {
|
|
74
|
+
// 无 mode 字段时,按特征键推断
|
|
75
|
+
const hasCronKeys = pickString(properties, ['expression', 'cron', 'cron_expression', 'cronExpression']) !==
|
|
76
|
+
undefined;
|
|
77
|
+
const hasWebhookKeys = pickString(properties, ['path', 'hook_url', 'hookUrl']) !== undefined;
|
|
78
|
+
if (hasWebhookKeys)
|
|
79
|
+
return parseWebhookConfig(properties);
|
|
80
|
+
if (hasCronKeys)
|
|
81
|
+
return parseCronConfig(properties);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const mode = rawMode.toLowerCase();
|
|
85
|
+
if (mode === 'manual')
|
|
86
|
+
return { mode: 'manual' };
|
|
87
|
+
if (mode === 'cron')
|
|
88
|
+
return parseCronConfig(properties);
|
|
89
|
+
if (mode === 'webhook')
|
|
90
|
+
return parseWebhookConfig(properties);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
function parseCronConfig(properties) {
|
|
94
|
+
const expression = pickString(properties, [
|
|
95
|
+
'expression',
|
|
96
|
+
'cron',
|
|
97
|
+
'cron_expression',
|
|
98
|
+
'cronExpression',
|
|
99
|
+
]);
|
|
100
|
+
if (!expression)
|
|
101
|
+
return null;
|
|
102
|
+
const config = { mode: 'cron', expression };
|
|
103
|
+
const timezone = pickString(properties, ['timezone', 'time_zone', 'timeZone']);
|
|
104
|
+
if (timezone)
|
|
105
|
+
config.timezone = timezone;
|
|
106
|
+
return config;
|
|
107
|
+
}
|
|
108
|
+
function parseWebhookConfig(properties) {
|
|
109
|
+
const path = pickString(properties, ['path', 'hook_url', 'hookUrl']);
|
|
110
|
+
if (!path)
|
|
111
|
+
return null;
|
|
112
|
+
const rawMethod = (pickString(properties, ['http_method', 'httpMethod', 'method']) || 'POST').toUpperCase();
|
|
113
|
+
const rawResponseMode = (pickString(properties, ['response_mode', 'responseMode']) || 'IMMEDIATE').toUpperCase();
|
|
114
|
+
const config = {
|
|
115
|
+
mode: 'webhook',
|
|
116
|
+
path,
|
|
117
|
+
httpMethod: rawMethod === 'GET' ? 'GET' : 'POST',
|
|
118
|
+
responseMode: rawResponseMode === 'LAST_NODE' || rawResponseMode === 'CUSTOM'
|
|
119
|
+
? rawResponseMode
|
|
120
|
+
: 'IMMEDIATE',
|
|
121
|
+
};
|
|
122
|
+
const responseData = pickString(properties, ['response_data', 'responseData']);
|
|
123
|
+
if (responseData !== undefined)
|
|
124
|
+
config.responseData = responseData;
|
|
125
|
+
const authRequired = pickBoolean(properties, ['auth_required', 'authRequired']);
|
|
126
|
+
if (authRequired !== undefined)
|
|
127
|
+
config.authRequired = authRequired;
|
|
128
|
+
const authHeaderName = pickString(properties, ['auth_header_name', 'authHeaderName']);
|
|
129
|
+
if (authHeaderName)
|
|
130
|
+
config.authHeaderName = authHeaderName;
|
|
131
|
+
const authHeaderValue = pickString(properties, ['auth_header_value', 'authHeaderValue']);
|
|
132
|
+
if (authHeaderValue)
|
|
133
|
+
config.authHeaderValue = authHeaderValue;
|
|
134
|
+
return config;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 查找连接流的触发器节点信息。
|
|
138
|
+
* flow 中无触发器节点时抛错。
|
|
139
|
+
*/
|
|
140
|
+
async function findTriggerNode(flowId) {
|
|
141
|
+
const flow = await (0, flow_1.getFlowDetail)(flowId);
|
|
142
|
+
const nodes = flow.nodes || [];
|
|
143
|
+
const triggerNode = nodes.find((n) => isTriggerNode(n));
|
|
144
|
+
if (!triggerNode) {
|
|
145
|
+
throw new Error(`连接流 ${flowId} 中未找到触发器节点(StartEventNode),请先通过 flow node create --trigger 创建`);
|
|
146
|
+
}
|
|
147
|
+
const nodeId = triggerNode.n_id || triggerNode.id;
|
|
148
|
+
const nodeDetail = await (0, node_1.getNodeDetail)(nodeId);
|
|
149
|
+
const envelope = (0, nodeEnvelope_1.unwrapNodeDetailEnvelope)(nodeDetail);
|
|
150
|
+
const config = parseTriggerConfig(envelope.properties);
|
|
151
|
+
return {
|
|
152
|
+
nodeId,
|
|
153
|
+
connectorId: triggerNode.connector_id,
|
|
154
|
+
connectorName: triggerNode.connector_name,
|
|
155
|
+
actionId: triggerNode.action_id,
|
|
156
|
+
actionName: triggerNode.action_name,
|
|
157
|
+
config,
|
|
158
|
+
properties: envelope.properties,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** 将 TriggerConfig 写入节点 properties(清除旧触发器键,写入规范 snake_case 键) */
|
|
162
|
+
function triggerConfigToProperties(config) {
|
|
163
|
+
const props = { mode: config.mode };
|
|
164
|
+
if (config.mode === 'cron') {
|
|
165
|
+
props.expression = config.expression;
|
|
166
|
+
if (config.timezone)
|
|
167
|
+
props.timezone = config.timezone;
|
|
168
|
+
}
|
|
169
|
+
else if (config.mode === 'webhook') {
|
|
170
|
+
props.path = config.path;
|
|
171
|
+
props.http_method = config.httpMethod;
|
|
172
|
+
props.response_mode = config.responseMode;
|
|
173
|
+
if (config.responseData !== undefined)
|
|
174
|
+
props.response_data = config.responseData;
|
|
175
|
+
if (config.authRequired !== undefined)
|
|
176
|
+
props.auth_required = config.authRequired;
|
|
177
|
+
if (config.authHeaderName !== undefined)
|
|
178
|
+
props.auth_header_name = config.authHeaderName;
|
|
179
|
+
if (config.authHeaderValue !== undefined)
|
|
180
|
+
props.auth_header_value = config.authHeaderValue;
|
|
181
|
+
}
|
|
182
|
+
return props;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* 写入触发器配置:读取触发器节点详情 → 清除旧触发器键 →
|
|
186
|
+
* 合并新配置 → PATCH 提交(保留 position/action 等既有信封字段)。
|
|
187
|
+
*/
|
|
188
|
+
async function setTriggerConfig(flowId, config) {
|
|
189
|
+
const flow = await (0, flow_1.getFlowDetail)(flowId);
|
|
190
|
+
const nodes = flow.nodes || [];
|
|
191
|
+
const triggerNode = nodes.find((n) => isTriggerNode(n));
|
|
192
|
+
if (!triggerNode) {
|
|
193
|
+
throw new Error(`连接流 ${flowId} 中未找到触发器节点(StartEventNode),请先通过 flow node create --trigger 创建`);
|
|
194
|
+
}
|
|
195
|
+
const nodeId = triggerNode.n_id || triggerNode.id;
|
|
196
|
+
const nodeDetail = await (0, node_1.getNodeDetail)(nodeId);
|
|
197
|
+
if (!nodeDetail) {
|
|
198
|
+
throw new Error(`未找到节点 ${nodeId}`);
|
|
199
|
+
}
|
|
200
|
+
const envelope = (0, nodeEnvelope_1.unwrapNodeDetailEnvelope)(nodeDetail);
|
|
201
|
+
// 清除旧触发器配置键(兼容 snake/camel 及 classic 键),再写入新配置
|
|
202
|
+
const nextProperties = {};
|
|
203
|
+
for (const [key, value] of Object.entries(envelope.properties)) {
|
|
204
|
+
const lower = key.toLowerCase();
|
|
205
|
+
if (TRIGGER_WIRE_KEYS.has(key) || TRIGGER_WIRE_KEYS.has(lower))
|
|
206
|
+
continue;
|
|
207
|
+
if ([
|
|
208
|
+
'cron',
|
|
209
|
+
'cron_expression',
|
|
210
|
+
'cronexpression',
|
|
211
|
+
'hook_url',
|
|
212
|
+
'hookurl',
|
|
213
|
+
'trigger_mode',
|
|
214
|
+
'triggermode',
|
|
215
|
+
].includes(lower)) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
nextProperties[key] = value;
|
|
219
|
+
}
|
|
220
|
+
Object.assign(nextProperties, triggerConfigToProperties(config));
|
|
221
|
+
const updatePayload = {
|
|
222
|
+
...nodeDetail,
|
|
223
|
+
flow_id: flowId,
|
|
224
|
+
properties: nextProperties,
|
|
225
|
+
};
|
|
226
|
+
if (envelope.proxyId !== undefined)
|
|
227
|
+
updatePayload.proxy_id = envelope.proxyId;
|
|
228
|
+
if (envelope.mcpToolDescription !== undefined) {
|
|
229
|
+
updatePayload.mcp_tool_description = envelope.mcpToolDescription;
|
|
230
|
+
}
|
|
231
|
+
await (0, node_1.updateNode)(nodeId, updatePayload);
|
|
232
|
+
return {
|
|
233
|
+
nodeId,
|
|
234
|
+
connectorId: triggerNode.connector_id,
|
|
235
|
+
connectorName: triggerNode.connector_name,
|
|
236
|
+
actionId: triggerNode.action_id,
|
|
237
|
+
actionName: triggerNode.action_name,
|
|
238
|
+
config,
|
|
239
|
+
properties: nextProperties,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=trigger.js.map
|