dsh-apis-plugin 0.1.4 → 0.1.6
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 +112 -0
- package/client.js +12 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1 +1,113 @@
|
|
|
1
1
|
# dsh-apis-plugin
|
|
2
|
+
|
|
3
|
+
通用接口管理插件(DeepSeek Harness / Cordis 双面插件):把任意一套 HTTP 接口注册成 agent 可用的**文档查询 + 请求调用**双工具。
|
|
4
|
+
|
|
5
|
+
核心约定:**`api.cfg` 是接口的唯一权威范围**——agent 只能请求 api.cfg 中列出的接口,范围外一律拒绝。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
dsh plugin --profile web add dsh-apis-plugin
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
安装后重启 `dsh web` 生效。指定版本:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
dsh plugin --profile web add dsh-apis-plugin@0.1.5
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 配置
|
|
20
|
+
|
|
21
|
+
### 1. api.cfg(接口清单,必需)
|
|
22
|
+
|
|
23
|
+
在插件配置页指定的 API 目录下创建 `api.cfg`(key=value 行,`#` 注释,UTF-8 编码):
|
|
24
|
+
|
|
25
|
+
```ini
|
|
26
|
+
# 接口服务前缀:域名 + nginx 前缀 + 网关路由段
|
|
27
|
+
baseUrl=http://your-host/prod-api
|
|
28
|
+
|
|
29
|
+
# 接口列表:逗号分隔同行书写
|
|
30
|
+
apis=/users/page,/users/{id}
|
|
31
|
+
|
|
32
|
+
# 或逐行列举
|
|
33
|
+
apis=
|
|
34
|
+
/outline/page
|
|
35
|
+
/outline/detailByNos
|
|
36
|
+
/basicTargetChar/item/listByTargetCharId
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- `baseUrl`:请求时拼接在接口路径前(改文件即生效,无需重启)
|
|
40
|
+
- `apis`:接口白名单,支持 `{param}` 模板段(如 `/users/{id}`),agent 请求时填实际值
|
|
41
|
+
- **增删接口只改这个文件**,改完点插件配置页的「加载API」(或重启)生效
|
|
42
|
+
|
|
43
|
+
### 2. api_doc.md(接口文档,建议提供)
|
|
44
|
+
|
|
45
|
+
与 `api.cfg` 同目录。用 `---` 分节,每节描述一个接口,节内用反引号标注接口路径:
|
|
46
|
+
|
|
47
|
+
```markdown
|
|
48
|
+
## 通用说明
|
|
49
|
+
|
|
50
|
+
所有接口返回 JSON,需携带 token。
|
|
51
|
+
|
|
52
|
+
## 分页查询用户
|
|
53
|
+
|
|
54
|
+
- 路径:`/users/page`(GET)
|
|
55
|
+
- 参数:keyword(可选)、page、pageSize
|
|
56
|
+
|
|
57
|
+
## 用户详情
|
|
58
|
+
|
|
59
|
+
- 路径:`/users/{id}`(GET)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
agent 调用 `{prefix}_api_doc` 时按路径命中对应小节返回。
|
|
63
|
+
|
|
64
|
+
### 3. 部署侧 cordis.patch.yml(可选覆盖)
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
- insert:
|
|
68
|
+
- id: dsh-apis-plugin
|
|
69
|
+
name: dsh-apis-plugin
|
|
70
|
+
config:
|
|
71
|
+
domain: "用户中心" # 领域名:拼进工具描述 prompt
|
|
72
|
+
toolPrefix: api # 工具名前缀:生成 api_api_doc / api_api_request
|
|
73
|
+
apiDir: "" # api.cfg / api_doc.md 默认目录(留空则在 UI 里填)
|
|
74
|
+
endpoints: [] # yml 侧额外固定接口
|
|
75
|
+
baseUrl: "" # 服务前缀兜底(api.cfg 优先)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 使用
|
|
79
|
+
|
|
80
|
+
### Web UI(设置 → 插件 → 插件配置)
|
|
81
|
+
|
|
82
|
+
| 操作 | 说明 |
|
|
83
|
+
|---|---|
|
|
84
|
+
| API 配置 | 填 api.cfg / api_doc.md 所在目录,**失焦或回车自动保存** |
|
|
85
|
+
| 加载API | 重扫目录:接口列表严格等于当前 api.cfg 内容 |
|
|
86
|
+
| 接口列表 | 只读展示,增删请在 api.cfg 中操作 |
|
|
87
|
+
|
|
88
|
+
### Agent 工具
|
|
89
|
+
|
|
90
|
+
插件向 agent 注册两个工具(`{prefix}` 默认 `api`):
|
|
91
|
+
|
|
92
|
+
| 工具 | 用途 |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `api_api_doc` | 查接口文档:传 `path` 返回该接口的参数与响应说明;不传返回通用说明和已配置接口目录 |
|
|
95
|
+
| `api_api_request` | 真正调用接口:`path` 必须在白名单内,GET 用 `query`,POST 用 `body` |
|
|
96
|
+
|
|
97
|
+
超出 api.cfg 范围的请求会被直接拒绝,并提示可用接口列表。
|
|
98
|
+
|
|
99
|
+
## 排查
|
|
100
|
+
|
|
101
|
+
dsh 控制台日志:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
[dsh-apis-plugin] loaded; endpoints=N, doc sections=M
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- `endpoints=0`:检查 API 目录路径是否正确、api.cfg 是否与目录同级
|
|
108
|
+
- 加载后列表与 api.cfg 不一致:确认已在配置页点过「加载API」
|
|
109
|
+
- 提示未配置 baseUrl:在 api.cfg 写入 `baseUrl=http://...`
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT
|
package/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// 浏览器半侧:在「插件配置」标签页为 dsh-apis-plugin 命名空间注册一张展示卡片
|
|
2
2
|
// 接口的增删以 api.cfg 为唯一来源:这里只读展示列表,编辑请改 api.cfg 后点「加载API」重扫
|
|
3
|
-
//
|
|
3
|
+
// API 目录失焦或点「加载API」时自动保存,无页脚按钮
|
|
4
|
+
// 样式与 DOM 结构对齐原生 PluginCard(li 卡片 + SVG 箭头)
|
|
4
5
|
window.__ModuleLoader__.load({
|
|
5
6
|
id: "dsh-apis-plugin",
|
|
6
7
|
factory: (require) => {
|
|
@@ -26,13 +27,11 @@ window.__ModuleLoader__.load({
|
|
|
26
27
|
.apis-description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
|
|
27
28
|
.apis-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}
|
|
28
29
|
.apis-body{border-top:.5px solid var(--dsw-alias-border-l2);margin:0 16px;padding:12px 0 8px;display:grid;gap:10px}
|
|
29
|
-
.apis-
|
|
30
|
-
.apis-saved{color:var(--dsw-alias-label-tertiary);margin-right:auto;font-size:12px}
|
|
30
|
+
.apis-saved{color:var(--dsw-alias-label-tertiary);font-size:12px;white-space:nowrap}
|
|
31
31
|
.apis-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid transparent;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}
|
|
32
32
|
.apis-btn:disabled{opacity:.4;cursor:default}
|
|
33
33
|
.apis-discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}
|
|
34
34
|
.apis-discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}
|
|
35
|
-
.apis-save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
|
|
36
35
|
.apis-row{display:flex;align-items:center;gap:8px}
|
|
37
36
|
.apis-rowLabel{width:44px;flex-shrink:0;font-size:13px;color:var(--dsw-alias-label-primary)}
|
|
38
37
|
.apis-rowLabelWide{width:auto;white-space:nowrap}
|
|
@@ -82,9 +81,7 @@ window.__ModuleLoader__.load({
|
|
|
82
81
|
function ApisCard(props) {
|
|
83
82
|
const state = props.useApisCard((s) => s);
|
|
84
83
|
const [dirDraft, setDirDraft] = react.useState(null); // 文档目录,null 表示未编辑
|
|
85
|
-
const [saving, setSaving] = react.useState(false);
|
|
86
84
|
const [loading, setLoading] = react.useState(false); // 加载API 扫描中
|
|
87
|
-
const [saved, setSaved] = react.useState(false); // 保存成功后短暂提示
|
|
88
85
|
const [loaded, setLoaded] = react.useState(false); // 加载API 完成后短暂提示
|
|
89
86
|
const [open, setOpen] = react.useState(false); // 折叠态只显示标题行
|
|
90
87
|
const ready = state.status === "ready" && state.writable;
|
|
@@ -96,14 +93,10 @@ window.__ModuleLoader__.load({
|
|
|
96
93
|
const shownDir = dirDraft ?? savedDir;
|
|
97
94
|
const dirDirty = dirDraft !== null && dirDraft !== savedDir;
|
|
98
95
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
await scope.set("apiDir", dirDraft); // 只存目录,扫描交给「加载API」
|
|
103
|
-
setSaving(false);
|
|
96
|
+
/** 目录失焦时自动保存(点「加载API」也会先写入目录) */
|
|
97
|
+
function commitDir() {
|
|
98
|
+
if (dirDirty && ready) scope.set("apiDir", dirDraft);
|
|
104
99
|
setDirDraft(null);
|
|
105
|
-
setSaved(true); // 显示「已保存」提示,2 秒后消失
|
|
106
|
-
setTimeout(() => setSaved(false), 2000);
|
|
107
100
|
}
|
|
108
101
|
|
|
109
102
|
/** 加载API:把目录写入配置并翻转 scanToken,服务端 watch 重扫 apis.txt 后回写接口列表 */
|
|
@@ -148,19 +141,21 @@ window.__ModuleLoader__.load({
|
|
|
148
141
|
react_jsx_runtime.jsx(Chevron, { open }),
|
|
149
142
|
],
|
|
150
143
|
}),
|
|
151
|
-
// 展开体:API配置目录行 + 分隔线 +
|
|
144
|
+
// 展开体:API配置目录行 + 分隔线 + 接口行列表(只读)
|
|
152
145
|
open && react_jsx_runtime.jsxs("div", { className: "apis-body", children: [
|
|
153
|
-
// API配置:文档目录(
|
|
146
|
+
// API配置:文档目录(api.cfg / api_doc.md 所在目录,失焦自动保存)+ 加载API 重扫按钮
|
|
154
147
|
react_jsx_runtime.jsxs("div", { className: "apis-row", children: [
|
|
155
148
|
react_jsx_runtime.jsx("div", { className: "apis-rowLabel apis-rowLabelWide", children: "API配置" }),
|
|
156
149
|
react_jsx_runtime.jsx("input", {
|
|
157
|
-
value: shownDir, disabled: !ready ||
|
|
150
|
+
value: shownDir, disabled: !ready || loading,
|
|
158
151
|
onChange: (e) => setDirDraft(e.target.value),
|
|
152
|
+
onBlur: commitDir,
|
|
153
|
+
onKeyDown: (e) => { if (e.key === "Enter") e.currentTarget.blur(); },
|
|
159
154
|
placeholder: "api.cfg / api_doc.md 所在目录",
|
|
160
155
|
className: "apis-input",
|
|
161
156
|
}),
|
|
162
157
|
react_jsx_runtime.jsx("button", {
|
|
163
|
-
type: "button", disabled: !ready ||
|
|
158
|
+
type: "button", disabled: !ready || loading || !shownDir.trim(),
|
|
164
159
|
onClick: loadApis,
|
|
165
160
|
className: "apis-btn apis-discard", children: loading ? "扫描中…" : "加载API",
|
|
166
161
|
}),
|
|
@@ -169,20 +164,6 @@ window.__ModuleLoader__.load({
|
|
|
169
164
|
react_jsx_runtime.jsx("hr", { className: "apis-hr" }),
|
|
170
165
|
!state.writable && react_jsx_runtime.jsx("span", { className: "apis-description", children: "只读" }),
|
|
171
166
|
list.length ? list.map(row) : react_jsx_runtime.jsx("span", { className: "apis-empty", children: "暂无接口:请在 API 目录下的 api.cfg 中配置 apis,然后点击「加载API」" }),
|
|
172
|
-
react_jsx_runtime.jsxs("div", { className: "apis-footer", children: [
|
|
173
|
-
// 保存成功后的短暂提示(占按钮行左侧)
|
|
174
|
-
react_jsx_runtime.jsx("span", { className: "apis-saved", children: saved ? "已保存" : "" }),
|
|
175
|
-
react_jsx_runtime.jsx("button", {
|
|
176
|
-
type: "button", disabled: !dirDirty || saving,
|
|
177
|
-
onClick: () => setDirDraft(null),
|
|
178
|
-
className: "apis-btn apis-discard", children: "放弃修改",
|
|
179
|
-
}),
|
|
180
|
-
react_jsx_runtime.jsx("button", {
|
|
181
|
-
type: "button", disabled: !ready || !dirDirty || saving,
|
|
182
|
-
onClick: save, children: saving ? "保存中…" : "保存",
|
|
183
|
-
className: "apis-btn apis-save",
|
|
184
|
-
}),
|
|
185
|
-
] }),
|
|
186
167
|
] }),
|
|
187
168
|
],
|
|
188
169
|
});
|