feat(plugins): add AI tools and consent-gated data access

This commit is contained in:
t8y2 committed 2026-09-25 01:56:26 +08:00
1 parent 1bc6f70cd8
commit 26a015cd86
71 files changed
+4613 -129

No files matched your search

+64 -1
View File
@@ -120,6 +120,7 @@ Manifest 中的路径相对于包根目录,不能以 `/` 开头、不能包含
"host.schema:read",
"host.storage",
"host.clipboard:read",
"host.data:read",
"host.network:https://s3.example.com:443"
],
"entrypoints": {
@@ -140,6 +141,7 @@ Manifest 中的路径相对于包根目录,不能以 `/` 开头、不能包含
- `host.storage` 为工作台 UI 提供本插件独享的持久化键值存储(见 [持久化 UI 状态](#持久化-ui-状态));数据只落在插件自己的插件数据目录里。
- `host.ai` 允许插件在 DBX 内置 AI 面板中新建带数据快照的对话,沿用宿主的模型选择、历史、追问、停止和导出功能。旧版 DBX 会在安装时因未知权限而失败,需要兼容旧宿主的插件应把 `host.ai` 作为可选能力。
- `host.clipboard:read` 允许工作台 UI 读取系统剪贴板(见 [系统剪贴板](#系统剪贴板))。写剪贴板不需要权限;读取会把用户数据(密码、令牌)交给插件代码,因此需要门控。
- `host.data:read` 允许 `window.dbxPlugin.queryData` 在用户授权给插件的连接上执行一条只读 SQL(见 [只读数据查询](#只读数据查询))。每个连接都需要用户同意;它不会授予写入、DDL 或重连。
- `host.network:https://host[:port]` 声明浏览器可访问的 HTTPS origin,DBX 将它加入 CSP 的 `connect-src`;最多 8 个,不允许路径、通配符或 Token。仍受目标服务 CORS 规则约束。该权限不是原生 Sidecar 的网络防火墙。
- `backend.transport` 默认是 `stdio-jsonl`;需要二进制帧时使用 `stdio-framed`,并同时声明 `host.binary`。
- `backend.executable`、`ui.root` 和 `ui.entry` 必须指向包内文件;带原生后端的包必须为每个目标平台提供对应可执行文件。
@@ -348,7 +350,8 @@ const assetUrl = await window.dbxPlugin.readAssetUrl("assets/empty-state.svg");
| `getPlanCapabilities(connectionId)` | 读取宿主与该连接能提供什么计划能力;需要 `host.plans:read`。 |
| `explainPlan({ connectionId, database?, schema?, sql, mode, timeoutMs? })` | 返回 `sql` 的估算执行计划;需要 `host.plans:read`。 |
| `getTableMetadata({ connectionId, database?, schema?, table })` | 读取已打开连接的窄化 table schema metadata;需要 `host.schema:read`。 |
| `capabilities` | 来自 init 消息的 `{ downloadFile, planApi, schemaMetadataApi, storage, ai, clipboardWrite, clipboardRead }`。字段缺失或为 `false` 表示当前宿主不支持该组 Host API,应据此关闭功能而不是用真实请求试探。 |
| `queryData({ connectionId, database?, schema?, sql, maxRows?, timeoutMs? })` | 在用户授权给插件的连接上执行一条只读 SQL;需要 `host.data:read`。 |
| `capabilities` | 来自 init 消息的 `{ downloadFile, planApi, schemaMetadataApi, dataApi, storage, ai, clipboardWrite, clipboardRead }`。字段缺失或为 `false` 表示当前宿主不支持该组 Host API,应据此关闭功能而不是用真实请求试探。 |
| `ai.openConversation({ title, prompt, context, send? })` | 在内置 AI 面板新建插件对话;`send` 默认 `false`,为 `true` 时立即分析;需要 `host.ai`,并以 `capabilities.ai` 广播——旧宿主不带该字段,请按能力位关闭而不是用真实请求试探。 |
| `onInit(fn)` / `onEvent(fn)` | 监听初始化、环境变化或后端事件;转发后端事件需要 `host.events`。 |
| `storage` | 本插件独享的持久化键值状态;需要 `host.storage`;见下文。 |
@@ -456,6 +459,49 @@ if (window.dbxPlugin.capabilities.schemaMetadataApi) {
该 API 属于 Host API 1.3。无法离开它工作的插件应声明:`"engines": { "host_api": "^1.3" }`;同时仍应读取 `capabilities.schemaMetadataApi`,因为旧宿主不会提供该字段。没有 `host.schema:read` 时,宿主会在请求到达 backend 前拒绝调用。
### 只读数据查询
插件可以在用户授权给它的 DBX 连接上执行一条只读 SQL,而不需要驱动、连接池、凭据或连接串。声明权限,并按能力位决定是否调用:
```json
{
"engines": { "host_api": "^1.4" },
"permissions": ["host.data:read"]
}
```
```js
if (window.dbxPlugin.capabilities.dataApi) {
const result = await window.dbxPlugin.queryData({
connectionId,
database,
sql: "SELECT status, count(*) AS total FROM orders GROUP BY status",
maxRows: 200
});
renderChart(result.columns, result.rows);
}
```
`queryData({ connectionId, database?, schema?, sql, maxRows?, timeoutMs? })` 返回:
```json
{
"dbType": "postgres",
"columns": [{ "name": "status", "dataType": "text" }, { "name": "total", "dataType": "int8" }],
"rows": [["paid", 1204], ["refunded", 37]],
"truncated": false,
"elapsedMs": 12
}
```
- **按连接征求同意。** 插件第一次查询某个连接时,宿主会弹窗写明插件名和连接名。用户允许后授权会持久保存,并显示在「插件中心 → 已安装」该插件下,用户可随时撤销;拒绝会在本次工作台会话内被记住。插件无法自行授权。
- **只读且单条语句。** 宿主只接受一条被 DBX SQL 风险分类器判定为只读的语句——与 MCP 只读模式和 AI Agent 使用同一道闸门。写入、DDL、`SELECT … FOR UPDATE`、多条语句以及 `USE` 这类切换会话数据库的语句都会被拒绝;请改用 `database` 参数。
- **只用已打开的连接。** 与执行计划和表结构元数据 API 一样,DBX 不会替插件发起连接:已保存但未打开的连接会以 `Connection is not open` 拒绝。Redis、MongoDB、搜索引擎等非 SQL 连接不提供该能力。
- **有上限。** `maxRows` 默认 500、最多 5000;序列化后的行数据上限 8 MiB;`timeoutMs` 会被限制在连接自身超时和 60 秒上限之内。只要有行被截断,`truncated` 就为 `true`。
- **不含凭据。** 响应只包含列和行。授权被撤销后,调用会返回以 `PLUGIN_DATA_ACCESS_NOT_GRANTED` 开头的错误,下一次查询会重新征求用户同意。
该 API 属于 Host API 1.4。请同时保留 `engines.host_api` 下限和 `capabilities.dataApi` 运行时检查;无法弹出确认框的 Web 宿主会拒绝请求,而不会静默授权。
### 系统剪贴板
插件沙箱是 opaque origin 且没有剪贴板权限,`navigator.clipboard` 在里面不可用。宿主代为桥接系统剪贴板:
@@ -647,6 +693,23 @@ Sidecar 约束:stdout 只能输出协议消息,日志写 stderr;请求 ID
上例可替换 Rust 模板的 `backend/src/main.rs`,同时让 Cargo 包版本与 Manifest 一致。Go 使用模板生成的 `dbxpluginsdk.NewServer(metadata, handler).Serve()`;当前 Go SDK 支持 JSONL,Rust SDK 还支持 framed transport。不要仅修改 Manifest transport 就认为 Go 已具备二进制帧处理能力。完整接口见 [Rust SDK](https://github.com/t8y2/dbx/tree/main/plugins/sdk/rust/dbx-plugin-sdk) 和 [Go SDK](https://github.com/t8y2/dbx/tree/main/plugins/sdk/go/dbx-plugin-sdk)。
## 为 DBX AI 助手提供工具
带原生后端的插件可以暴露工具,供 DBX 内置 AI 助手在 Agent 模式下调用。这样「订单接口为什么变慢了?」这类问题就能把数据库工具和插件提供的指标、日志或 Shell 能力结合起来。Sidecar 需要实现两个方法:
- `mcp/tools` 以 MCP 工具格式返回 `{ "tools": [{ "name", "description", "inputSchema", "annotations"? }] }`。DBX 会传入正在绑定的 `{ "connectionId" }`,插件可以据此在只读连接上隐藏写工具。
- `mcp/call` 接收 `{ "tool", "arguments", "lifecycle" }`,返回 MCP `CallToolResult`(`{ "content": [{ "type": "text", "text": "..." }], "isError": false }`)。`lifecycle` 与已打开连接的 `connection/connect` 载荷相同——凭据由宿主解析,运行端点已经过 SSH 或代理层——因此工具参数里永远不需要密钥。
宿主在此之上负责:
- **需要用户开启。** 用户在「插件中心 → 已安装 → 内置 AI 工具」中开启前,不会暴露任何工具;同一面板会列出工具并标出哪些需要确认。
- **只用已打开的连接。** 只为用户当前已打开的插件连接提供工具。连接由 DBX 绑定:模型看到的 schema 中会去掉 `connectionId` / `connectionName`;如果插件 schema 声明了 `connectionId`,宿主会在转发参数中注入绑定连接的 id。打开了多个连接时,模型通过额外的 `dbx_connection` 参数选择其一。
- **默认需要确认。** 只有声明了 `"annotations": { "readOnlyHint": true }` 的工具才会直接执行。其他调用都会暂停运行,并向用户展示将要转发的完整参数,由用户选择允许一次或拒绝;五分钟未响应视为拒绝。请为真正只读的工具加上标注,同时保留插件自身的防护(只读连接标志、两阶段确认等)——宿主确认拦截的是模型的误操作,不能替代插件自己的安全规则。
- **可移植的 schema。** 工具以 `<前缀>__<工具名>` 暴露(例如 `ssh__ssh_exec`),schema 会被收敛到所有支持的模型供应商都接受的子集:`type`、`description`、`properties`、`required`、`items`、字符串 `enum` 以及数值、长度和元素数量边界。参数名请只使用字母、数字和下划线。
- **结果有上限。** 单次调用 120 秒超时,结果在交给模型前会被压缩。长任务请作为后台任务启动并返回句柄,不要阻塞。
内置 Agent 会把工具输出当作不可信数据处理。结果里请返回事实而不是指令,也不要回显任何密钥。
## 创建插件
直接安装预编译 CLI,不需要 Rust,也不需要克隆 DBX 源码: