feat(plugins): release Copilot SDK Pipe v0.8.0 and Files Filter v0.1.3 (#50)

* feat(plugins): release copilot sdk pipe v0.8.0 and files filter v0.1.3

- Add P1~P4 conditional tool filtering and admin/server gating behavior

- Fix artifact publishing reliability, strict /api file URLs, and HTML preview/download delivery

- Update bilingual README/docs, release notes, and filter matching/debug improvements

* fix(docs): remove duplicate code block in tool-filtering zh doc

- Remove incorrectly placed duplicate 'if not is_enabled: continue' block
  outside code fence on line 161-163 of copilot-sdk-tool-filtering.zh.md
- Addresses review comment from gemini-code-assist (#50)
This commit is contained in:
Fu-Jie
2026-02-26 01:05:31 +08:00
committed by GitHub
parent 5b6dddd517
commit db33f44cbc
20 changed files with 1175 additions and 247 deletions

View File

@@ -0,0 +1,126 @@
# GitHub Copilot SDK Tool Filtering Logic Documentation
## Overview
The tool filtering logic ensures that changes made in the **OpenWebUI admin panel take effect on the very next chat message** — no restart or cache flush required. The design balances three goals: administrator control, user autonomy, and built-in feature availability.
## Priority Hierarchy
Filtering is applied top-to-bottom. A higher layer can fully block a lower one:
| Priority | Layer | Controls |
|---|---|---|
| 1 (Highest) | **Plugin Valve toggles** | `ENABLE_OPENWEBUI_TOOLS`, `ENABLE_MCP_SERVER`, `ENABLE_OPENAPI_SERVER` — category master switches |
| 2 | **Admin backend server toggle** | Per-server `config.enable` in OpenWebUI Connections panel — blocks specific servers |
| 3 (Lowest) | **User Chat menu selection** | `tool_ids` from the chat UI — selects which enabled items to use |
---
## Core Decision Logic (Flowchart)
```mermaid
graph TD
A[New message arrives] --> V{Plugin Valve enabled\nfor this category?}
V -- No --> VX[Drop all tools in this category]
V -- Yes --> B{Admin backend:\nconfig.enable = True?}
B -- No --> C[Skip this server]
B -- Yes --> F{Built-in or Custom/Server tool?}
F -- Built-in --> G{Any builtin: IDs\nselected in Chat?}
G -- Yes --> H[Enable ONLY the mapped categories\nunselected categories set to False]
G -- No --> I[Enable default 4 categories:\nweb_search, image_generation,\ncode_interpreter, memory]
F -- Custom / Server --> J{Any custom IDs\nselected in Chat?}
J -- Yes --> K[Load ONLY the selected IDs]
J -- No --> L[Load ALL admin-enabled custom tools]
H & I & K & L --> M[Always inject: publish_file_from_workspace]
M --> N[Start / Resume Copilot SDK Session]
```
---
## Scenario Reference Table
| User selects in Chat | Custom tools loaded | Built-in tools loaded |
|---|---|---|
| Nothing | All admin-enabled | Default 4 (search, image, code, memory) |
| Only `builtin:xxx` | All admin-enabled (unaffected) | Only selected categories |
| Only custom/server IDs | Only selected IDs | Default 4 |
| Both builtin and custom | Only selected custom IDs | Only selected builtin categories |
---
## Technical Implementation Details
### 1. Real-time Admin Sync (No Caching)
Every request re-reads `TOOL_SERVER_CONNECTIONS.value` live. There is **no in-memory cache** for server state. As a result:
- Enable a server in the admin panel → it appears on the **next message**.
- Disable a server → it is dropped on the **next message**.
```python
# Read live on every request — no cache
if hasattr(TOOL_SERVER_CONNECTIONS, "value"):
raw_connections = TOOL_SERVER_CONNECTIONS.value
for server in connections:
is_enabled = config.get("enable", False) # checked per-server, per-request
if not is_enabled:
continue # skipped immediately — hard block
```
### 2. Built-in Tool Category Mapping
The plugin maps individual `builtin:func_name` IDs to one of 9 categories understood by `get_builtin_tools`. When the user selects specific builtins, **only those categories are enabled; unselected categories are explicitly set to `False`** (not omitted) to prevent OpenWebUI's default-`True` fallback:
```python
if builtin_selected:
# Strict mode: set every category explicitly
for cat in all_builtin_categories: # all 9
is_enabled = cat in enabled_categories # only selected ones are True
builtin_tools_meta[cat] = is_enabled # unselected are explicitly False
else:
# Default mode: only the 4 core categories
default_builtin_categories = [
"web_search", "image_generation", "code_interpreter", "memory"
]
for cat in all_builtin_categories:
builtin_tools_meta[cat] = cat in default_builtin_categories
features.update(req_features) # merge backend feature flags
```
### 3. Custom Tool "Select-All" Fallback
The whitelist is activated **only when the user explicitly selects custom/server IDs**. Selecting only `builtin:` IDs does not trigger the custom whitelist, so all admin-enabled servers remain accessible:
```python
# custom_selected contains only non-builtin: IDs
if custom_selected:
# Whitelist active: keep only what the user picked
tool_ids = [tid for tid in available_ids if tid in custom_selected]
else:
# No custom selection: load everything enabled in backend
tool_ids = available_ids
```
The same rule applies to MCP servers in `_parse_mcp_servers`.
### 4. Admin Backend Strict Validation
Applied uniformly to both OpenAPI and MCP servers, handling both dict and Pydantic object shapes:
```python
is_enabled = False
config = server.get("config", {}) if isinstance(server, dict) else getattr(server, "config", {})
is_enabled = config.get("enable", False) if isinstance(config, dict) else getattr(config, "enable", False)
if not is_enabled:
continue # hard skip — no user or valve setting can override this
```
## Important Notes
- **SDK Internal Tools**: `available_tools = None` is passed to the session so SDK-native capabilities (`read_file`, `shell`, etc.) are never accidentally blocked by the custom tool list.
- **Persistent Tool**: `publish_file_from_workspace` is always injected after all filtering — it is required for the file delivery workflow regardless of any toggle.

View File

@@ -0,0 +1,206 @@
# GitHub Copilot SDK 工具过滤逻辑开发文档
## 核心需求
**管理员在后台修改工具服务的启用状态后,用户发送下一条消息时立即生效,无需重启服务或刷新缓存。**
过滤逻辑同时兼顾两个目标:管理员管控权、用户自主选择权。内置工具则完全独立,仅由模型配置决定。
---
## 工具分类说明
本文档涉及两类完全独立的工具,权限控制机制不同:
| 工具类型 | 说明 | 权限控制来源 |
|---|---|---|
| **内置工具Builtin Tools** | OpenWebUI 原生能力:时间、知识库、记忆、联网搜索、图像生成、代码解释器等 | 仅由模型配置 `meta.builtinTools` 决定,**与 Chat 前端选择无关** |
| **OpenWebUI Tools** | 用户安装的 Python 工具插件 | 插件 Valve + Chat 工具选择tool_ids |
| **工具服务器OpenAPI / MCP** | 外部 OpenAPI Server、MCP Server | 插件 Valve + 管理员 `config.enable` + `function_name_filter_list` + Chat 工具选择tool_ids |
---
## 内置工具权限控制(模型配置驱动,与前端无关)
内置工具**完全由模型配置决定**Chat 界面的工具选择对其没有任何影响。
### 模型 `meta.builtinTools` 字段
在模型(自定义模型或基础模型)的 `meta` 字段中有一个可选的 `builtinTools` 对象:
```json
{
"meta": {
"capabilities": { "builtin_tools": true },
"builtinTools": {
"time": false,
"memory": true,
"chats": true,
"notes": true,
"knowledge": true,
"channels": true,
"web_search": true,
"image_generation": true,
"code_interpreter": true
}
}
}
```
**判定规则(源码 `utils/tools.py`**
```python
def is_builtin_tool_enabled(category: str) -> bool:
builtin_tools = model.get("info", {}).get("meta", {}).get("builtinTools", {})
return builtin_tools.get(category, True) # 缺省时默认 True
```
- `builtinTools` 字段**不存在** → 所有内置工具类别默认全部开启
- `builtinTools` 字段**存在** → 仅值为 `true` 的类别开启,其余关闭
---
## OpenWebUI Tools 和工具服务器的优先级层级
这两类工具的过滤从上到下依次执行,**受 Chat 前端选择影响**
| 优先级 | 层级 | 控制范围 |
|---|---|---|
| 1最高 | **插件 Valve 开关** | `ENABLE_OPENWEBUI_TOOLS` / `ENABLE_MCP_SERVER` / `ENABLE_OPENAPI_SERVER` — 类别总开关 |
| 2 | **管理员后端服务器开关** | OpenWebUI 连接面板中每个服务器的 `config.enable` — 控制具体服务器是否启用 |
| 3 | **管理员函数名过滤列表** | 工具服务器 `config.function_name_filter_list` — 限制该服务器对外暴露的函数列表(逗号分隔) |
| 4最低 | **用户 Chat 工具选择** | Chat 界面的 `tool_ids` — 在已启用范围内进一步筛选:未选则全选,有选则仅选中的 |
### 管理员函数名过滤列表说明
OpenWebUI 后台的工具服务器连接配置中支持设置 `function_name_filter_list` 字段(逗号分隔的函数名),用于限制该服务器对外暴露的函数。源码逻辑:
```python
# utils/tools.py
function_name_filter_list = tool_server_connection.get("config", {}).get("function_name_filter_list", "")
if isinstance(function_name_filter_list, str):
function_name_filter_list = function_name_filter_list.split(",")
for spec in specs:
function_name = spec["name"]
if function_name_filter_list:
if not is_string_allowed(function_name, function_name_filter_list):
continue # 不在列表中的函数被跳过
```
- 列表**为空** → 该服务器所有函数均可用
- 列表**有值** → 只有名称匹配的函数会被暴露给用户
---
## 核心判定流程
```mermaid
graph TD
A[新消息到来] --> BT[内置工具:读模型 meta.builtinTools]
BT --> BT2{builtinTools 字段存在?}
BT2 -- 否 --> BT3[开启全部内置工具]
BT2 -- 是 --> BT4[仅开启值为 true 的类别]
A --> CT[OpenWebUI Tools / 工具服务器]
CT --> V{插件 Valve 开启了该类别?}
V -- 否 --> VX[丢弃该类别]
V -- 是 --> B{后端 config.enable = True?}
B -- 否 --> C[跳过该服务器]
B -- 是 --> FL{function_name_filter_list 有值?}
FL -- 是 --> FL2[过滤掉不在列表中的函数]
FL -- 否 --> J
FL2 --> J{Chat tool_ids 有勾选?}
J -- 有 --> K[仅加载勾选的 ID]
J -- 无 --> L[加载所有后台已启用工具]
BT3 & BT4 & K & L --> M[始终注入: publish_file_from_workspace]
M --> N[启动/恢复 Copilot SDK 会话]
```
---
## 场景速查表
### 内置工具(与前端选择无关)
| 模型配置 | 结果 |
|---|---|
| `meta.builtinTools` 字段不存在 | 全部内置工具类别开启 |
| `meta.builtinTools` 字段存在 | 仅 `true` 的类别开启 |
### OpenWebUI Tools / 工具服务器(受 Chat 前端选择影响)
| Chat 工具选择情况 | 加载逻辑 |
|---|---|
| 什么都没选 | 加载所有 Valve 开启且后台已启用的工具Python Tools + OpenAPI + MCP |
| 选了部分 tool_ids | 仅加载勾选的 ID必须同时通过 Valve 和 config.enable 校验) |
---
## 代码实现详述
### 1. 管理员后台变更即时同步
OpenWebUI 通过 `PersistentConfig` + Redis 保证多 worker 之间的配置同步,插件直接读取 `request.app.state.config.TOOL_SERVER_CONNECTIONS` 即可获取最新值:
- 后台**启用**一个服务器 → **下一条消息**就出现。
- 后台**禁用**一个服务器 → **下一条消息**就消失。
```python
# 直接读取 OpenWebUI 的配置对象,有 Redis 时每次读取都会同步最新值
connections = request.app.state.config.TOOL_SERVER_CONNECTIONS # list
for server in connections:
config = server.get("config", {})
is_enabled = config.get("enable", False) # 每条服务器、每次请求都检查
if not is_enabled:
continue # 立即跳过,硬性拦截
```
### 2. 内置工具直接透传给 OpenWebUI 处理
插件调用 OpenWebUI 的 `get_builtin_tools(request, extra_params, model)` 时,将 `model` 原样传入即可。OpenWebUI 内部会自动读取 `model.info.meta.builtinTools` 来决定哪些内置工具生效:
```python
# OpenWebUI 源码 utils/tools.py 中的判定逻辑(开发参考,非插件代码)
def is_builtin_tool_enabled(category: str) -> bool:
builtin_tools = model.get("info", {}).get("meta", {}).get("builtinTools", {})
return builtin_tools.get(category, True) # 缺省值 True未配置时全部开启
```
插件无需自行维护内置工具分类映射,也不需要向 `builtinTools` 注入任何值。
### 3. 自定义工具"默认全选"(白名单仅在显式勾选时激活)
白名单**只有在用户明确勾选了 tool_ids 时才启用**。未勾选任何工具时,加载所有后台已启用工具:
```python
# tool_ids 来自 Chat 请求体
if tool_ids:
# 白名单模式:严格只保留勾选项
available_ids = [tid for tid in available_ids if tid in tool_ids]
else:
# 无勾选:加载所有后台已启用工具(免配置可用)
pass # available_ids 保持不变
```
MCP 服务器的 `_parse_mcp_servers` 遵循同样规则。
### 4. 后端状态硬校验OpenAPI 和 MCP 统一处理)
```python
is_enabled = False
config = server.get("config", {}) if isinstance(server, dict) else getattr(server, "config", {})
is_enabled = config.get("enable", False) if isinstance(config, dict) else getattr(config, "enable", False)
if not is_enabled:
continue # 硬性跳过,任何用户或 Valve 设置都无法绕过
```
---
## 注意事项
- **SDK 内部工具**:向会话传 `available_tools = None`,保证 `read_file``shell` 等 SDK 原生能力不被自定义工具列表意外屏蔽。
- **始终注入的工具**`publish_file_from_workspace` 在所有过滤完成后硬性追加,是文件交付工作流的必要依赖,不受任何开关影响。