fix(pipes): fix mcp tool filtering and force-enable autonomous web search
- Fix issue where mcp tool filtering logic (function_name_filter_list) in admin backend caused all tools to be hidden due to ID prefix mismatch - Force enable web_search tool for Copilot Agent regardless of UI toggles, providing full autonomy for search-related intents - Updated README and version to v0.9.1
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
# 🧭 Agents Stability & Friendliness Guide
|
||||
|
||||
This guide focuses on how to improve **reliability** and **user experience** of agents in `github_copilot_sdk.py`.
|
||||
|
||||
---
|
||||
|
||||
## 1) Goals
|
||||
|
||||
- Reduce avoidable failures (timeouts, tool-call dead ends, invalid outputs).
|
||||
- Keep responses predictable under stress (large context, unstable upstream, partial tool failures).
|
||||
- Make interaction friendly (clear progress, clarification before risky actions, graceful fallback).
|
||||
- Preserve backwards compatibility while introducing stronger defaults.
|
||||
|
||||
---
|
||||
|
||||
## 2) Stability model (4 layers)
|
||||
|
||||
## Layer A — Input safety
|
||||
|
||||
- Validate essential runtime context early (user/chat/model/tool availability).
|
||||
- Use strict parsing for JSON-like user/task config (never trust raw free text).
|
||||
- Add guardrails for unsupported mode combinations (e.g., no tools + tool-required task).
|
||||
|
||||
**Implementation hints**
|
||||
- Add preflight validator before `create_session`.
|
||||
- Return fast-fail structured errors with recovery actions.
|
||||
|
||||
## Layer B — Session safety
|
||||
|
||||
- Use profile-driven defaults (`model`, `reasoning_effort`, `infinite_sessions` thresholds).
|
||||
- Auto-fallback to safe profile when unknown profile is requested.
|
||||
- Isolate each chat in a deterministic workspace path.
|
||||
|
||||
**Implementation hints**
|
||||
- Add `AGENT_PROFILE` + fallback to `default`.
|
||||
- Keep `infinite_sessions` enabled by default for long tasks.
|
||||
|
||||
## Layer C — Tool-call safety
|
||||
|
||||
- Add `on_pre_tool_use` to validate and sanitize args.
|
||||
- Add denylist/allowlist checks for dangerous operations.
|
||||
- Add timeout budget per tool class (file/network/shell).
|
||||
|
||||
**Implementation hints**
|
||||
- Keep current `on_post_tool_use` behavior.
|
||||
- Extend hooks gradually: `on_pre_tool_use` first, then `on_error_occurred`.
|
||||
|
||||
## Layer D — Recovery safety
|
||||
|
||||
- Retry only idempotent operations with capped attempts.
|
||||
- Distinguish recoverable vs non-recoverable failures.
|
||||
- Add deterministic fallback path (summary answer + explicit limitation).
|
||||
|
||||
**Implementation hints**
|
||||
- Retry policy table by event type.
|
||||
- Emit "what succeeded / what failed / what to do next" blocks.
|
||||
|
||||
---
|
||||
|
||||
## 3) Friendliness model (UX contract)
|
||||
|
||||
## A. Clarification first for ambiguity
|
||||
|
||||
Use `on_user_input_request` for:
|
||||
- Missing constraints (scope, target path, output format)
|
||||
- High-risk actions (delete/migrate/overwrite)
|
||||
- Contradictory instructions
|
||||
|
||||
**Rule**: ask once with concise choices; avoid repeated back-and-forth.
|
||||
|
||||
## B. Progress visibility
|
||||
|
||||
Emit status in major phases:
|
||||
1. Context check
|
||||
2. Planning/analysis
|
||||
3. Tool execution
|
||||
4. Verification
|
||||
5. Final result
|
||||
|
||||
**Rule**: no silent waits > 8 seconds.
|
||||
|
||||
## C. Friendly failure style
|
||||
|
||||
Every failure should include:
|
||||
- what failed
|
||||
- why (short)
|
||||
- what was already done
|
||||
- next recommended action
|
||||
|
||||
## D. Output readability
|
||||
|
||||
Standardize final response blocks:
|
||||
- `Outcome`
|
||||
- `Changes`
|
||||
- `Validation`
|
||||
- `Limitations`
|
||||
- `Next Step`
|
||||
|
||||
---
|
||||
|
||||
## 4) High-value features to add (priority)
|
||||
|
||||
## P0 (immediate)
|
||||
|
||||
1. `on_user_input_request` handler with default answer strategy
|
||||
2. `on_pre_tool_use` for argument checks + risk gates
|
||||
3. Structured progress events (phase-based)
|
||||
|
||||
## P1 (short-term)
|
||||
|
||||
4. Error taxonomy + retry policy (`network`, `provider`, `tool`, `validation`)
|
||||
5. Profile-based session factory with safe fallback
|
||||
6. Auto quality gate for final output sections
|
||||
|
||||
## P2 (mid-term)
|
||||
|
||||
7. Transport flexibility (`cli_url`, `use_stdio`, `port`) for deployment resilience
|
||||
8. Azure provider path completion
|
||||
9. Foreground session lifecycle support for advanced multi-session control
|
||||
|
||||
---
|
||||
|
||||
## 5) Suggested valves for stability/friendliness
|
||||
|
||||
- `AGENT_PROFILE`: `default | builder | analyst | reviewer`
|
||||
- `ENABLE_USER_INPUT_REQUEST`: `bool`
|
||||
- `DEFAULT_USER_INPUT_ANSWER`: `str`
|
||||
- `TOOL_CALL_TIMEOUT_SECONDS`: `int`
|
||||
- `MAX_RETRY_ATTEMPTS`: `int`
|
||||
- `ENABLE_SAFE_TOOL_GUARD`: `bool`
|
||||
- `ENABLE_PHASE_STATUS_EVENTS`: `bool`
|
||||
- `ENABLE_FRIENDLY_FAILURE_TEMPLATE`: `bool`
|
||||
|
||||
---
|
||||
|
||||
## 6) Failure playbooks (practical)
|
||||
|
||||
## Playbook A — Provider timeout
|
||||
|
||||
- Retry once if request is idempotent.
|
||||
- Downgrade reasoning effort if timeout persists.
|
||||
- Return concise fallback and preserve partial result.
|
||||
|
||||
## Playbook B — Tool argument mismatch
|
||||
|
||||
- Block execution in `on_pre_tool_use`.
|
||||
- Ask user one clarification question if recoverable.
|
||||
- Otherwise skip tool and explain impact.
|
||||
|
||||
## Playbook C — Large output overflow
|
||||
|
||||
- Save large output to workspace file.
|
||||
- Return file path + short summary.
|
||||
- Avoid flooding chat with huge payload.
|
||||
|
||||
## Playbook D — Conflicting user instructions
|
||||
|
||||
- Surface conflict explicitly.
|
||||
- Offer 2-3 fixed choices.
|
||||
- Continue only after user selection.
|
||||
|
||||
---
|
||||
|
||||
## 7) Metrics to track
|
||||
|
||||
- Session success rate
|
||||
- Tool-call success rate
|
||||
- Average recovery rate after first failure
|
||||
- Clarification rate vs hallucination rate
|
||||
- Mean time to first useful output
|
||||
- User follow-up dissatisfaction signals (e.g., “not what I asked”)
|
||||
|
||||
---
|
||||
|
||||
## 8) Minimal rollout plan
|
||||
|
||||
1. Add `on_user_input_request` + `on_pre_tool_use` (feature-gated).
|
||||
2. Add phase status events and friendly failure template.
|
||||
3. Add retry policy + error taxonomy.
|
||||
4. Add profile fallback and deployment transport options.
|
||||
5. Observe metrics for 1-2 weeks, then tighten defaults.
|
||||
|
||||
---
|
||||
|
||||
## 9) Quick acceptance checklist
|
||||
|
||||
- Agent asks clarification only when necessary.
|
||||
- No long silent period without status updates.
|
||||
- Failures always include next actionable step.
|
||||
- Unknown profile/provider config does not crash session.
|
||||
- Large outputs are safely redirected to file.
|
||||
- Final response follows a stable structure.
|
||||
@@ -0,0 +1,192 @@
|
||||
# 🧭 Agents 稳定性与友好性指南
|
||||
|
||||
本文聚焦如何提升 `github_copilot_sdk.py` 中 Agent 的**稳定性**与**交互友好性**。
|
||||
|
||||
---
|
||||
|
||||
## 1)目标
|
||||
|
||||
- 降低可避免失败(超时、工具死路、输出不可解析)。
|
||||
- 在高压场景保持可预期(大上下文、上游不稳定、部分工具失败)。
|
||||
- 提升交互体验(进度可见、风险操作先澄清、优雅降级)。
|
||||
- 在不破坏兼容性的前提下逐步增强默认行为。
|
||||
|
||||
---
|
||||
|
||||
## 2)稳定性模型(4 层)
|
||||
|
||||
## A 层:输入安全
|
||||
|
||||
- 会话创建前验证关键上下文(user/chat/model/tool 可用性)。
|
||||
- 对 JSON/配置采用严格解析,不信任自由文本。
|
||||
- 对不支持的模式组合做前置拦截(例如:任务需要工具但工具被禁用)。
|
||||
|
||||
**落地建议**
|
||||
- `create_session` 前增加 preflight validator。
|
||||
- 快速失败并返回结构化恢复建议。
|
||||
|
||||
## B 层:会话安全
|
||||
|
||||
- 使用 profile 驱动默认值(`model`、`reasoning_effort`、`infinite_sessions`)。
|
||||
- 请求未知 profile 时自动回退到安全默认 profile。
|
||||
- 每个 chat 使用确定性 workspace 路径隔离。
|
||||
|
||||
**落地建议**
|
||||
- 增加 `AGENT_PROFILE`,未知值回退 `default`。
|
||||
- 长任务默认开启 `infinite_sessions`。
|
||||
|
||||
## C 层:工具调用安全
|
||||
|
||||
- 增加 `on_pre_tool_use` 做参数校验与净化。
|
||||
- 增加高风险操作 allow/deny 规则。
|
||||
- 按工具类别配置超时预算(文件/网络/命令)。
|
||||
|
||||
**落地建议**
|
||||
- 保留现有 `on_post_tool_use`。
|
||||
- 先补 `on_pre_tool_use`,再补 `on_error_occurred`。
|
||||
|
||||
## D 层:恢复安全
|
||||
|
||||
- 仅对幂等操作重试,且有次数上限。
|
||||
- 区分可恢复/不可恢复错误。
|
||||
- 提供确定性降级输出(摘要 + 限制说明)。
|
||||
|
||||
**落地建议**
|
||||
- 按错误类型配置重试策略。
|
||||
- 统一输出“成功了什么 / 失败了什么 / 下一步”。
|
||||
|
||||
---
|
||||
|
||||
## 3)友好性模型(UX 合约)
|
||||
|
||||
## A. 歧义先澄清
|
||||
|
||||
通过 `on_user_input_request` 处理:
|
||||
- 约束缺失(范围、目标路径、输出格式)
|
||||
- 高风险动作(删除/迁移/覆盖)
|
||||
- 用户指令互相冲突
|
||||
|
||||
**规则**:一次提问给出有限选项,避免反复追问。
|
||||
|
||||
## B. 进度可见
|
||||
|
||||
按阶段发状态:
|
||||
1. 上下文检查
|
||||
2. 规划/分析
|
||||
3. 工具执行
|
||||
4. 验证
|
||||
5. 结果输出
|
||||
|
||||
**规则**:超过 8 秒不能无状态输出。
|
||||
|
||||
## C. 失败友好
|
||||
|
||||
每次失败都要包含:
|
||||
- 失败点
|
||||
- 简短原因
|
||||
- 已完成部分
|
||||
- 下一步可执行建议
|
||||
|
||||
## D. 输出可读
|
||||
|
||||
统一最终输出结构:
|
||||
- `Outcome`
|
||||
- `Changes`
|
||||
- `Validation`
|
||||
- `Limitations`
|
||||
- `Next Step`
|
||||
|
||||
---
|
||||
|
||||
## 4)高价值增强项(优先级)
|
||||
|
||||
## P0(立即)
|
||||
|
||||
1. `on_user_input_request` + 默认答复策略
|
||||
2. `on_pre_tool_use` 参数检查 + 风险闸门
|
||||
3. 阶段化状态事件
|
||||
|
||||
## P1(短期)
|
||||
|
||||
4. 错误分类 + 重试策略(`network/provider/tool/validation`)
|
||||
5. profile 化 session 工厂 + 安全回退
|
||||
6. 最终输出质量门(结构校验)
|
||||
|
||||
## P2(中期)
|
||||
|
||||
7. 传输配置能力(`cli_url/use_stdio/port`)
|
||||
8. Azure provider 支持完善
|
||||
9. foreground session 生命周期能力(高级多会话)
|
||||
|
||||
---
|
||||
|
||||
## 5)建议新增 valves
|
||||
|
||||
- `AGENT_PROFILE`: `default | builder | analyst | reviewer`
|
||||
- `ENABLE_USER_INPUT_REQUEST`: `bool`
|
||||
- `DEFAULT_USER_INPUT_ANSWER`: `str`
|
||||
- `TOOL_CALL_TIMEOUT_SECONDS`: `int`
|
||||
- `MAX_RETRY_ATTEMPTS`: `int`
|
||||
- `ENABLE_SAFE_TOOL_GUARD`: `bool`
|
||||
- `ENABLE_PHASE_STATUS_EVENTS`: `bool`
|
||||
- `ENABLE_FRIENDLY_FAILURE_TEMPLATE`: `bool`
|
||||
|
||||
---
|
||||
|
||||
## 6)故障应对手册(实用)
|
||||
|
||||
## 场景 A:Provider 超时
|
||||
|
||||
- 若请求幂等,重试一次。
|
||||
- 仍超时则降低 reasoning 强度。
|
||||
- 返回简洁降级结果并保留已有中间成果。
|
||||
|
||||
## 场景 B:工具参数不匹配
|
||||
|
||||
- 在 `on_pre_tool_use` 阻断。
|
||||
- 可恢复则提一个澄清问题。
|
||||
- 不可恢复则跳过工具并说明影响。
|
||||
|
||||
## 场景 C:输出过大
|
||||
|
||||
- 大输出落盘到 workspace 文件。
|
||||
- 返回文件路径 + 简要摘要。
|
||||
- 避免把超大内容直接刷屏。
|
||||
|
||||
## 场景 D:用户指令冲突
|
||||
|
||||
- 明确指出冲突点。
|
||||
- 给 2-3 个固定选项。
|
||||
- 用户选定后再继续。
|
||||
|
||||
---
|
||||
|
||||
## 7)建议监控指标
|
||||
|
||||
- 会话成功率
|
||||
- 工具调用成功率
|
||||
- 首次失败后的恢复率
|
||||
- 澄清率 vs 幻觉率
|
||||
- 首次可用输出耗时
|
||||
- 用户不满意信号(如“不是我要的”)
|
||||
|
||||
---
|
||||
|
||||
## 8)最小落地路径
|
||||
|
||||
1. 先加 `on_user_input_request` + `on_pre_tool_use`(功能开关控制)。
|
||||
2. 增加阶段状态事件和失败友好模板。
|
||||
3. 增加错误分类与重试策略。
|
||||
4. 增加 profile 安全回退与传输配置能力。
|
||||
5. 观察 1-2 周指标,再逐步收紧默认策略。
|
||||
|
||||
---
|
||||
|
||||
## 9)验收速查
|
||||
|
||||
- 仅在必要时澄清,不重复追问。
|
||||
- 无长时间无状态“沉默”。
|
||||
- 失败输出包含下一步动作。
|
||||
- profile/provider 配置异常不导致会话崩溃。
|
||||
- 超大输出可安全转文件。
|
||||
- 最终响应结构稳定一致。
|
||||
294
plugins/pipes/github-copilot-sdk/CUSTOM_AGENTS_REFERENCE.md
Normal file
294
plugins/pipes/github-copilot-sdk/CUSTOM_AGENTS_REFERENCE.md
Normal file
@@ -0,0 +1,294 @@
|
||||
# 🤖 Custom Agents Reference (Copilot SDK Python)
|
||||
|
||||
This document explains how to create **custom agent profiles** using the SDK at:
|
||||
|
||||
- `/Users/fujie/app/python/oui/copilot-sdk/python`
|
||||
|
||||
and apply them in this pipe:
|
||||
|
||||
- `plugins/pipes/github-copilot-sdk/github_copilot_sdk.py`
|
||||
|
||||
---
|
||||
|
||||
## 1) What is a “Custom Agent” here?
|
||||
|
||||
In Copilot SDK Python, a custom agent is not a separate runtime class from the SDK itself.
|
||||
It is typically a **session configuration bundle**:
|
||||
|
||||
- model + reasoning level
|
||||
- system message/persona
|
||||
- tools exposure
|
||||
- hooks lifecycle behavior
|
||||
- user input strategy
|
||||
- infinite session compaction strategy
|
||||
- provider (optional BYOK)
|
||||
|
||||
So the practical implementation is:
|
||||
|
||||
1. Define an `AgentProfile` data structure.
|
||||
2. Convert profile -> `session_config`.
|
||||
3. Call `client.create_session(session_config)`.
|
||||
|
||||
---
|
||||
|
||||
## 2) SDK capabilities you can use
|
||||
|
||||
From `copilot-sdk/python/README.md`, the key knobs are:
|
||||
|
||||
- `model`
|
||||
- `reasoning_effort`
|
||||
- `tools`
|
||||
- `system_message`
|
||||
- `streaming`
|
||||
- `provider`
|
||||
- `infinite_sessions`
|
||||
- `on_user_input_request`
|
||||
- `hooks`
|
||||
|
||||
These are enough to create different agent personas without forking core logic.
|
||||
|
||||
---
|
||||
|
||||
## 3) Recommended architecture in pipe
|
||||
|
||||
Use a **profile registry** + a single factory method.
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
@dataclass
|
||||
class AgentProfile:
|
||||
name: str
|
||||
model: str
|
||||
reasoning_effort: str = "medium"
|
||||
system_message: Optional[str] = None
|
||||
enable_tools: bool = True
|
||||
enable_openwebui_tools: bool = True
|
||||
enable_hooks: bool = False
|
||||
enable_user_input: bool = False
|
||||
infinite_sessions_enabled: bool = True
|
||||
compaction_threshold: float = 0.8
|
||||
buffer_exhaustion_threshold: float = 0.95
|
||||
```
|
||||
|
||||
Then map profile -> session config:
|
||||
|
||||
```python
|
||||
def build_session_config(profile: AgentProfile, tools: list, hooks: dict, user_input_handler: Optional[Callable[..., Any]]):
|
||||
config = {
|
||||
"model": profile.model,
|
||||
"reasoning_effort": profile.reasoning_effort,
|
||||
"streaming": True,
|
||||
"infinite_sessions": {
|
||||
"enabled": profile.infinite_sessions_enabled,
|
||||
"background_compaction_threshold": profile.compaction_threshold,
|
||||
"buffer_exhaustion_threshold": profile.buffer_exhaustion_threshold,
|
||||
},
|
||||
}
|
||||
|
||||
if profile.system_message:
|
||||
config["system_message"] = {"content": profile.system_message}
|
||||
|
||||
if profile.enable_tools:
|
||||
config["tools"] = tools
|
||||
|
||||
if profile.enable_hooks and hooks:
|
||||
config["hooks"] = hooks
|
||||
|
||||
if profile.enable_user_input and user_input_handler:
|
||||
config["on_user_input_request"] = user_input_handler
|
||||
|
||||
return config
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4) Example profile presets
|
||||
|
||||
```python
|
||||
AGENT_PROFILES = {
|
||||
"builder": AgentProfile(
|
||||
name="builder",
|
||||
model="claude-sonnet-4.6",
|
||||
reasoning_effort="high",
|
||||
system_message="You are a precise coding agent. Prefer minimal, verifiable changes.",
|
||||
enable_tools=True,
|
||||
enable_hooks=True,
|
||||
),
|
||||
"analyst": AgentProfile(
|
||||
name="analyst",
|
||||
model="gpt-5-mini",
|
||||
reasoning_effort="medium",
|
||||
system_message="You analyze and summarize with clear evidence mapping.",
|
||||
enable_tools=False,
|
||||
enable_hooks=False,
|
||||
),
|
||||
"reviewer": AgentProfile(
|
||||
name="reviewer",
|
||||
model="claude-sonnet-4.6",
|
||||
reasoning_effort="high",
|
||||
system_message="Review diffs, identify risks, and propose minimal fixes.",
|
||||
enable_tools=True,
|
||||
enable_hooks=True,
|
||||
),
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5) Integrating with this pipe
|
||||
|
||||
In `github_copilot_sdk.py`:
|
||||
|
||||
1. Add a Valve like `AGENT_PROFILE` (default: `builder`).
|
||||
2. Resolve profile from registry at runtime.
|
||||
3. Build `session_config` from profile.
|
||||
4. Merge existing valve toggles (`ENABLE_TOOLS`, `ENABLE_OPENWEBUI_TOOLS`) as final override.
|
||||
|
||||
Priority recommendation:
|
||||
|
||||
- explicit runtime override > valve toggle > profile default
|
||||
|
||||
This keeps backward compatibility while enabling profile-based behavior.
|
||||
|
||||
---
|
||||
|
||||
## 6) Hook strategy (safe defaults)
|
||||
|
||||
Use hooks only when needed:
|
||||
|
||||
- `on_pre_tool_use`: allow/deny tools, sanitize args
|
||||
- `on_post_tool_use`: add short execution context
|
||||
- `on_user_prompt_submitted`: normalize unsafe prompt patterns
|
||||
- `on_error_occurred`: retry/skip/abort policy
|
||||
|
||||
Start with no-op hooks, then incrementally enforce policy.
|
||||
|
||||
---
|
||||
|
||||
## 7) Validation checklist
|
||||
|
||||
- Profile can be selected by valve and takes effect.
|
||||
- Session created with expected model/reasoning.
|
||||
- Tool availability matches profile + valve overrides.
|
||||
- Hook handlers run only when enabled.
|
||||
- Infinite-session compaction settings are applied.
|
||||
- Fallback to default profile if unknown profile name is provided.
|
||||
|
||||
---
|
||||
|
||||
## 8) Anti-patterns to avoid
|
||||
|
||||
- Hardcoding profile behavior in multiple places.
|
||||
- Mixing tool registration logic with prompt-format logic.
|
||||
- Enabling expensive hooks for all profiles by default.
|
||||
- Coupling profile name to exact model id with no fallback.
|
||||
|
||||
---
|
||||
|
||||
## 9) Minimal rollout plan
|
||||
|
||||
1. Add profile dataclass + registry.
|
||||
2. Add one valve: `AGENT_PROFILE`.
|
||||
3. Build session config factory.
|
||||
4. Keep existing behavior as default profile.
|
||||
5. Add 2 more profiles (`analyst`, `reviewer`) and test.
|
||||
|
||||
---
|
||||
|
||||
## 10) SDK gap analysis for current pipe (high-value missing features)
|
||||
|
||||
Current pipe already implements many advanced capabilities:
|
||||
|
||||
- `SessionConfig` with `tools`, `system_message`, `infinite_sessions`, `provider`, `mcp_servers`
|
||||
- Session resume/create path
|
||||
- `list_models()` cache path
|
||||
- Attachments in `session.send(...)`
|
||||
- Hook integration (currently `on_post_tool_use`)
|
||||
|
||||
Still missing (or partially implemented) high-value SDK features:
|
||||
|
||||
### A. `on_user_input_request` handler (ask-user loop)
|
||||
|
||||
**Why valuable**
|
||||
- Enables safe clarification for ambiguous tasks instead of hallucinated assumptions.
|
||||
|
||||
**Current state**
|
||||
- Not wired into `create_session(...)`.
|
||||
|
||||
**Implementation idea**
|
||||
- Add valves:
|
||||
- `ENABLE_USER_INPUT_REQUEST: bool`
|
||||
- `DEFAULT_USER_INPUT_ANSWER: str`
|
||||
- Add a handler function and pass:
|
||||
- `session_params["on_user_input_request"] = handler`
|
||||
|
||||
### B. Full lifecycle hooks (beyond `on_post_tool_use`)
|
||||
|
||||
**Why valuable**
|
||||
- Better policy control and observability.
|
||||
|
||||
**Current state**
|
||||
- Only `on_post_tool_use` implemented.
|
||||
|
||||
**Implementation idea**
|
||||
- Add optional handlers for:
|
||||
- `on_pre_tool_use`
|
||||
- `on_user_prompt_submitted`
|
||||
- `on_session_start`
|
||||
- `on_session_end`
|
||||
- `on_error_occurred`
|
||||
|
||||
### C. Provider type coverage gap (`azure`)
|
||||
|
||||
**Why valuable**
|
||||
- Azure OpenAI users cannot configure provider type natively.
|
||||
|
||||
**Current state**
|
||||
- Valve type only allows `openai | anthropic`.
|
||||
|
||||
**Implementation idea**
|
||||
- Extend valve enum to include `azure`.
|
||||
- Add `BYOK_AZURE_API_VERSION` valve.
|
||||
- Build `provider` payload with `azure` block when selected.
|
||||
|
||||
### D. Client transport options exposure (`cli_url`, `use_stdio`, `port`)
|
||||
|
||||
**Why valuable**
|
||||
- Enables remote/shared Copilot server and tuning transport mode.
|
||||
|
||||
**Current state**
|
||||
- `_build_client_config` sets `cli_path/cwd/config_dir/log_level/env`, but not transport options.
|
||||
|
||||
**Implementation idea**
|
||||
- Add valves:
|
||||
- `COPILOT_CLI_URL`
|
||||
- `COPILOT_USE_STDIO`
|
||||
- `COPILOT_PORT`
|
||||
- Conditionally inject into `client_config`.
|
||||
|
||||
### E. Foreground session lifecycle APIs
|
||||
|
||||
**Why valuable**
|
||||
- Better multi-session UX and control in TUI/server mode.
|
||||
|
||||
**Current state**
|
||||
- No explicit usage of:
|
||||
- `get_foreground_session_id()`
|
||||
- `set_foreground_session_id()`
|
||||
- `client.on("session.foreground", ...)`
|
||||
|
||||
**Implementation idea**
|
||||
- Optional debug/admin feature only.
|
||||
- Add event bridge for lifecycle notifications.
|
||||
|
||||
---
|
||||
|
||||
## 11) Recommended implementation priority
|
||||
|
||||
1. `on_user_input_request` (highest value / low risk)
|
||||
2. Full lifecycle hooks (high value / medium risk)
|
||||
3. Azure provider support (high value for enterprise users)
|
||||
4. Client transport valves (`cli_url/use_stdio/port`)
|
||||
5. Foreground session APIs (optional advanced ops)
|
||||
292
plugins/pipes/github-copilot-sdk/CUSTOM_AGENTS_REFERENCE_CN.md
Normal file
292
plugins/pipes/github-copilot-sdk/CUSTOM_AGENTS_REFERENCE_CN.md
Normal file
@@ -0,0 +1,292 @@
|
||||
# 🤖 自定义 Agents 参考文档(Copilot SDK Python)
|
||||
|
||||
本文说明如何基于以下 SDK 创建**可复用的自定义 Agent 配置**:
|
||||
|
||||
- `/Users/fujie/app/python/oui/copilot-sdk/python`
|
||||
|
||||
并接入当前 Pipe:
|
||||
|
||||
- `plugins/pipes/github-copilot-sdk/github_copilot_sdk.py`
|
||||
|
||||
---
|
||||
|
||||
## 1)这里的“自定义 Agent”是什么?
|
||||
|
||||
在 Copilot SDK Python 中,自定义 Agent 通常不是 SDK 里的独立类,而是一个**会话配置组合**:
|
||||
|
||||
- 模型与推理强度
|
||||
- system message / 人设
|
||||
- tools 暴露范围
|
||||
- hooks 生命周期行为
|
||||
- 用户输入策略
|
||||
- infinite session 压缩策略
|
||||
- provider(可选)
|
||||
|
||||
实际落地方式:
|
||||
|
||||
1. 定义 `AgentProfile` 数据结构。
|
||||
2. 将 profile 转成 `session_config`。
|
||||
3. 调用 `client.create_session(session_config)`。
|
||||
|
||||
---
|
||||
|
||||
## 2)SDK 可用于定制 Agent 的能力
|
||||
|
||||
根据 `copilot-sdk/python/README.md`,关键可配置项包括:
|
||||
|
||||
- `model`
|
||||
- `reasoning_effort`
|
||||
- `tools`
|
||||
- `system_message`
|
||||
- `streaming`
|
||||
- `provider`
|
||||
- `infinite_sessions`
|
||||
- `on_user_input_request`
|
||||
- `hooks`
|
||||
|
||||
这些能力足够做出多个 agent 人设,而无需复制整套管线代码。
|
||||
|
||||
---
|
||||
|
||||
## 3)在 Pipe 中推荐的架构
|
||||
|
||||
建议采用:**Profile 注册表 + 单一工厂函数**。
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
@dataclass
|
||||
class AgentProfile:
|
||||
name: str
|
||||
model: str
|
||||
reasoning_effort: str = "medium"
|
||||
system_message: Optional[str] = None
|
||||
enable_tools: bool = True
|
||||
enable_openwebui_tools: bool = True
|
||||
enable_hooks: bool = False
|
||||
enable_user_input: bool = False
|
||||
infinite_sessions_enabled: bool = True
|
||||
compaction_threshold: float = 0.8
|
||||
buffer_exhaustion_threshold: float = 0.95
|
||||
```
|
||||
|
||||
profile -> session_config 的工厂函数:
|
||||
|
||||
```python
|
||||
def build_session_config(profile: AgentProfile, tools: list, hooks: dict, user_input_handler: Optional[Callable[..., Any]]):
|
||||
config = {
|
||||
"model": profile.model,
|
||||
"reasoning_effort": profile.reasoning_effort,
|
||||
"streaming": True,
|
||||
"infinite_sessions": {
|
||||
"enabled": profile.infinite_sessions_enabled,
|
||||
"background_compaction_threshold": profile.compaction_threshold,
|
||||
"buffer_exhaustion_threshold": profile.buffer_exhaustion_threshold,
|
||||
},
|
||||
}
|
||||
|
||||
if profile.system_message:
|
||||
config["system_message"] = {"content": profile.system_message}
|
||||
|
||||
if profile.enable_tools:
|
||||
config["tools"] = tools
|
||||
|
||||
if profile.enable_hooks and hooks:
|
||||
config["hooks"] = hooks
|
||||
|
||||
if profile.enable_user_input and user_input_handler:
|
||||
config["on_user_input_request"] = user_input_handler
|
||||
|
||||
return config
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4)示例 Profile 预设
|
||||
|
||||
```python
|
||||
AGENT_PROFILES = {
|
||||
"builder": AgentProfile(
|
||||
name="builder",
|
||||
model="claude-sonnet-4.6",
|
||||
reasoning_effort="high",
|
||||
system_message="You are a precise coding agent. Prefer minimal, verifiable changes.",
|
||||
enable_tools=True,
|
||||
enable_hooks=True,
|
||||
),
|
||||
"analyst": AgentProfile(
|
||||
name="analyst",
|
||||
model="gpt-5-mini",
|
||||
reasoning_effort="medium",
|
||||
system_message="You analyze and summarize with clear evidence mapping.",
|
||||
enable_tools=False,
|
||||
enable_hooks=False,
|
||||
),
|
||||
"reviewer": AgentProfile(
|
||||
name="reviewer",
|
||||
model="claude-sonnet-4.6",
|
||||
reasoning_effort="high",
|
||||
system_message="Review diffs, identify risks, and propose minimal fixes.",
|
||||
enable_tools=True,
|
||||
enable_hooks=True,
|
||||
),
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5)如何接入当前 Pipe
|
||||
|
||||
在 `github_copilot_sdk.py` 中:
|
||||
|
||||
1. 新增 Valve:`AGENT_PROFILE`(默认 `builder`)。
|
||||
2. 运行时从注册表解析 profile。
|
||||
3. 通过工厂函数生成 `session_config`。
|
||||
4. 把已有开关(如 `ENABLE_TOOLS`、`ENABLE_OPENWEBUI_TOOLS`)作为最终覆盖层。
|
||||
|
||||
推荐优先级:
|
||||
|
||||
- 显式运行时参数 > valve 开关 > profile 默认值
|
||||
|
||||
这样能保持向后兼容,同时支持按 profile 切换 agent 行为。
|
||||
|
||||
---
|
||||
|
||||
## 6)Hooks 策略(安全默认)
|
||||
|
||||
仅在必要时开启 hooks:
|
||||
|
||||
- `on_pre_tool_use`:工具调用前 allow/deny、参数净化
|
||||
- `on_post_tool_use`:补充简要上下文
|
||||
- `on_user_prompt_submitted`:提示词规范化
|
||||
- `on_error_occurred`:错误重试/跳过/中止策略
|
||||
|
||||
建议先用 no-op,再逐步加策略。
|
||||
|
||||
---
|
||||
|
||||
## 7)验证清单
|
||||
|
||||
- 可通过 valve 选择 profile,且生效。
|
||||
- session 使用了预期 model / reasoning。
|
||||
- 工具可用性符合 profile + valve 覆盖后的结果。
|
||||
- hooks 仅在启用时触发。
|
||||
- infinite session 的阈值配置已生效。
|
||||
- 传入未知 profile 时能安全回退到默认 profile。
|
||||
|
||||
---
|
||||
|
||||
## 8)常见反模式
|
||||
|
||||
- 把 profile 逻辑硬编码在多个位置。
|
||||
- 将工具注册逻辑与提示词格式化耦合。
|
||||
- 默认给所有 profile 开启高开销 hooks。
|
||||
- profile 名与模型 ID 强绑定且没有回退方案。
|
||||
|
||||
---
|
||||
|
||||
## 9)最小落地步骤
|
||||
|
||||
1. 增加 profile dataclass + registry。
|
||||
2. 增加一个 valve:`AGENT_PROFILE`。
|
||||
3. 增加 session_config 工厂函数。
|
||||
4. 将现有行为作为 default profile。
|
||||
5. 再加 `analyst`、`reviewer` 两个 profile 并验证。
|
||||
|
||||
---
|
||||
|
||||
## 10)当前 Pipe 的 SDK 能力差距(高价值项)
|
||||
|
||||
当前 pipe 已实现不少高级能力:
|
||||
|
||||
- `SessionConfig` 里的 `tools`、`system_message`、`infinite_sessions`、`provider`、`mcp_servers`
|
||||
- session 的 resume/create 路径
|
||||
- `list_models()` 模型缓存路径
|
||||
- `session.send(...)` 附件传递
|
||||
- hooks 接入(目前仅 `on_post_tool_use`)
|
||||
|
||||
但仍有高价值能力未实现或仅部分实现:
|
||||
|
||||
### A. `on_user_input_request`(ask-user 交互回路)
|
||||
|
||||
**价值**
|
||||
- 任务不明确时可主动追问,降低错误假设和幻觉。
|
||||
|
||||
**现状**
|
||||
- 尚未接入 `create_session(...)`。
|
||||
|
||||
**实现建议**
|
||||
- 增加 valves:
|
||||
- `ENABLE_USER_INPUT_REQUEST: bool`
|
||||
- `DEFAULT_USER_INPUT_ANSWER: str`
|
||||
- 在 `session_params` 中注入:
|
||||
- `session_params["on_user_input_request"] = handler`
|
||||
|
||||
### B. 完整生命周期 hooks(不仅 `on_post_tool_use`)
|
||||
|
||||
**价值**
|
||||
- 增强策略控制与可观测性。
|
||||
|
||||
**现状**
|
||||
- 目前只实现了 `on_post_tool_use`。
|
||||
|
||||
**实现建议**
|
||||
- 增加可选 handler:
|
||||
- `on_pre_tool_use`
|
||||
- `on_user_prompt_submitted`
|
||||
- `on_session_start`
|
||||
- `on_session_end`
|
||||
- `on_error_occurred`
|
||||
|
||||
### C. Provider 类型覆盖缺口(`azure`)
|
||||
|
||||
**价值**
|
||||
- 企业 Azure OpenAI 场景可直接接入。
|
||||
|
||||
**现状**
|
||||
- valve 仅支持 `openai | anthropic`。
|
||||
|
||||
**实现建议**
|
||||
- 扩展枚举支持 `azure`。
|
||||
- 增加 `BYOK_AZURE_API_VERSION`。
|
||||
- 选择 azure 时构造 provider 的 `azure` 配置块。
|
||||
|
||||
### D. Client 传输配置未暴露(`cli_url` / `use_stdio` / `port`)
|
||||
|
||||
**价值**
|
||||
- 支持远程/共享 Copilot 服务,便于部署与调优。
|
||||
|
||||
**现状**
|
||||
- `_build_client_config` 仅设置 `cli_path/cwd/config_dir/log_level/env`。
|
||||
|
||||
**实现建议**
|
||||
- 增加 valves:
|
||||
- `COPILOT_CLI_URL`
|
||||
- `COPILOT_USE_STDIO`
|
||||
- `COPILOT_PORT`
|
||||
- 在 `client_config` 中按需注入。
|
||||
|
||||
### E. 前台会话生命周期 API 未使用
|
||||
|
||||
**价值**
|
||||
- 多会话/运维场景下可增强可控性与可视化。
|
||||
|
||||
**现状**
|
||||
- 尚未显式使用:
|
||||
- `get_foreground_session_id()`
|
||||
- `set_foreground_session_id()`
|
||||
- `client.on("session.foreground", ...)`
|
||||
|
||||
**实现建议**
|
||||
- 作为 debug/admin 高级功能逐步接入。
|
||||
|
||||
---
|
||||
|
||||
## 11)建议实现优先级
|
||||
|
||||
1. `on_user_input_request`(收益高、风险低)
|
||||
2. 完整 lifecycle hooks(收益高、风险中)
|
||||
3. Azure provider 支持(企业价值高)
|
||||
4. client 传输配置 valves(`cli_url/use_stdio/port`)
|
||||
5. 前台会话生命周期 API(高级可选)
|
||||
@@ -1,6 +1,6 @@
|
||||
# GitHub Copilot SDK Pipe for OpenWebUI
|
||||
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.9.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.9.1 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
|
||||
|
||||
This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/open-webui) that integrates the official [GitHub Copilot SDK](https://github.com/github/copilot-sdk). It enables you to use **GitHub Copilot models** (e.g., `gpt-5.2-codex`, `claude-sonnet-4.5`,`gemini-3-pro`, `gpt-5-mini`) **AND** your own models via **BYOK** (OpenAI, Anthropic) directly within OpenWebUI, providing a unified agentic experience with **strict User & Chat-level Workspace Isolation**.
|
||||
|
||||
@@ -14,21 +14,17 @@ This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/
|
||||
|
||||
---
|
||||
|
||||
## ✨ v0.9.0: The Skills Revolution & Stability Update
|
||||
## ✨ v0.9.1: MCP Tool Filtering & Web Search Reliability Fix
|
||||
|
||||
- **🧩 Copilot SDK Skills Support**: Native support for Copilot SDK skill directories (`SKILL.md` + resources). Skills can now be loaded as first-class runtime context.
|
||||
- **🔄 OpenWebUI Skills Bridge**: Full bidirectional sync between OpenWebUI **Workspace > Skills** and SDK skill directories.
|
||||
- **🛠️ Deterministic `manage_skills` Tool**: Expert tool for stable install/create/list/edit/delete skill operations.
|
||||
- **🌊 Reinforced Status Bar**: Multi-layered locking mechanism (`session_finalized` guard) and atomic async delivery to prevent "stuck" indicators.
|
||||
- **⚡ Asynchronous Integrity**: Refactored status emission to route all updates through a centralized helper, ensuring atomic delivery and preventing race conditions in parallel execution streams.
|
||||
- **💓 Pulse-Lock Refresh**: Implemented a hardware-inspired "pulse" logic that forces a final UI state refresh at the end of each session, ensuring the status bar settling on "Task completed."
|
||||
- **🗂️ Persistent Config Directory**: Added `COPILOTSDK_CONFIG_DIR` for stable session-state persistence across container restarts.
|
||||
- **🐛 Fixed MCP tool filtering logic**: Resolved a critical issue where configuring `function_name_filter_list` (or selecting specific tools in UI) would cause all tools from that MCP server to be incorrectly hidden due to ID prefix mismatches (`server:mcp:`).
|
||||
- **🌐 Autonomous Web Search**: `web_search` is now always enabled for the agent (bypassing the UI toggle), leveraging the Copilot SDK's native ability to decide when to search.
|
||||
- **🔍 Improved filter stability**: Ensured tool-level whitelists apply reliably without breaking the entire server connection.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Key Capabilities
|
||||
|
||||
- **🔑 Unified Intelligence (Official + BYOK)**: Seamlessly switch between official GitHub Copilot models (o1, GPT-4o, Claude 3.5 Sonnet, Gemini 2.0 Flash) and your own models (OpenAI, Anthropic) via **Bring Your Own Key** mode.
|
||||
- **🔑 Unified Intelligence (Official + BYOK)**: Seamlessly switch between official GitHub Copilot models and your own models (OpenAI, Anthropic, DeepSeek, xAI) via **Bring Your Own Key** mode.
|
||||
- **🛡️ Physical Workspace Isolation**: Every session runs in its own isolated directory sandbox. This ensures absolute data privacy and prevents cross-chat file contamination while allowing the Agent full filesystem access.
|
||||
- **🔌 Universal Tool Protocol**:
|
||||
- **Native MCP**: Direct, high-performance connection to Model Context Protocol servers.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GitHub Copilot SDK 官方管道
|
||||
|
||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **版本:** 0.9.0 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT
|
||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **版本:** 0.9.1 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT
|
||||
|
||||
这是一个用于 [OpenWebUI](https://github.com/open-webui/open-webui) 的高级 Pipe 函数,深度集成了 **GitHub Copilot SDK**。它不仅支持 **GitHub Copilot 官方模型**(如 `gpt-5.2-codex`, `claude-sonnet-4.5`, `gemini-3-pro`, `gpt-5-mini`),还支持 **BYOK (自带 Key)** 模式对接自定义服务商(OpenAI, Anthropic),并具备**严格的用户与会话级工作区隔离**能力,提供统一且安全的 Agent 交互体验。
|
||||
|
||||
@@ -13,19 +13,17 @@
|
||||
|
||||
---
|
||||
|
||||
## ✨ 0.9.0 核心更新:技能革命与稳定性加固
|
||||
## ✨ 0.9.1 最新更新:MCP 工具过滤与网页搜索可靠性修复
|
||||
|
||||
- **🧩 Copilot SDK Skills 原生支持**: 技能可作为一等上下文能力被加载和使用。
|
||||
- **🔄 OpenWebUI Skills 桥接**: 实现 OpenWebUI **工作区 > Skills** 与 SDK 技能目录的深度双向同步。
|
||||
- **🛠️ 确定性 `manage_skills` 工具**: 通过稳定工具契约完成技能的生命周期管理。
|
||||
- **🌊 状态栏逻辑加固**: 引入 `session_finalized` 多层锁定机制,彻底解决任务完成后状态栏回弹或卡死的问题。
|
||||
- **🗂️ 环境目录持久化**: 增强 `COPILOTSDK_CONFIG_DIR` 逻辑,确保会话状态跨容器重启稳定存在。
|
||||
- **🐛 修复 MCP 工具过滤逻辑**:解决了在管理员后端配置 `function_name_filter_list`(或在聊天界面勾选特定工具)时,因 ID 前缀(`server:mcp:`)识别逻辑错误导致所选服务器下的全部工具意外失效的问题。
|
||||
- **🌐 自主网页搜索**:`web_search` 工具现已强制对 Agent 开启(绕过 UI 网页搜索开关),充分利用 Copilot 自身具备的搜索判断能力。
|
||||
- **🔍 提升过滤稳定性**:由于修复了 ID 归一化逻辑,现在手动点选或后端配置的工具白名单均能稳定生效,不再会导致整个服务被排除。
|
||||
|
||||
---
|
||||
|
||||
## ✨ 核心能力 (Key Capabilities)
|
||||
|
||||
- **🔑 统一智能体验 (官方 + BYOK)**: 自由切换官方模型(o1, GPT-4o, Claude 3.5 Sonnet, Gemini 2.0 Flash)与自定义服务商(OpenAI, Anthropic),支持 **BYOK (自带 Key)** 模式。
|
||||
- **🔑 统一智能体验 (官方 + BYOK)**: 自由切换官方模型与自定义服务商(OpenAI, Anthropic, DeepSeek, xAI),支持 **BYOK (自带 Key)** 模式。
|
||||
- **🛡️ 物理级工作区隔离**: 每个会话在独立的沙箱目录中运行。确保绝对的数据隐私,防止不同聊天间的文件污染,同时给予 Agent 完整的文件系统操作权限。
|
||||
- **🔌 通用工具协议**:
|
||||
- **原生 MCP**: 高性能直连 Model Context Protocol 服务器。
|
||||
|
||||
@@ -5,7 +5,7 @@ author_url: https://github.com/Fu-Jie/openwebui-extensions
|
||||
funding_url: https://github.com/open-webui
|
||||
openwebui_id: ce96f7b4-12fc-4ac3-9a01-875713e69359
|
||||
description: Integrate GitHub Copilot SDK. Supports dynamic models, multi-turn conversation, streaming, multimodal input, infinite sessions, bidirectional OpenWebUI Skills bridge, and manage_skills tool.
|
||||
version: 0.9.0
|
||||
version: 0.9.1
|
||||
requirements: github-copilot-sdk==0.1.25
|
||||
"""
|
||||
|
||||
@@ -923,9 +923,9 @@ class Pipe:
|
||||
return final_tools
|
||||
|
||||
# 4. Extract chat-level tool selection (P4: user selection from Chat UI)
|
||||
chat_tool_ids = None
|
||||
if __metadata__ and isinstance(__metadata__, dict):
|
||||
chat_tool_ids = __metadata__.get("tool_ids") or None
|
||||
chat_tool_ids = self._normalize_chat_tool_ids(
|
||||
__metadata__.get("tool_ids") if isinstance(__metadata__, dict) else None
|
||||
)
|
||||
|
||||
# 5. Load OpenWebUI tools dynamically (always fresh, no cache)
|
||||
openwebui_tools = await self._load_openwebui_tools(
|
||||
@@ -2190,11 +2190,12 @@ class Pipe:
|
||||
return []
|
||||
|
||||
# P4: Chat tool_ids whitelist — only active when user explicitly selected tools
|
||||
if chat_tool_ids:
|
||||
chat_tool_ids_set = set(chat_tool_ids)
|
||||
selected_custom_tool_ids = self._extract_selected_custom_tool_ids(chat_tool_ids)
|
||||
if selected_custom_tool_ids:
|
||||
chat_tool_ids_set = set(selected_custom_tool_ids)
|
||||
filtered = [tid for tid in tool_ids if tid in chat_tool_ids_set]
|
||||
await self._emit_debug_log(
|
||||
f"[Tools] tool_ids whitelist active: {len(tool_ids)} → {len(filtered)} (selected: {chat_tool_ids})",
|
||||
f"[Tools] custom tool_ids whitelist active: {len(tool_ids)} → {len(filtered)} (selected: {selected_custom_tool_ids})",
|
||||
__event_call__,
|
||||
)
|
||||
tool_ids = filtered
|
||||
@@ -2284,6 +2285,30 @@ class Pipe:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Force web_search enabled when OpenWebUI tools are enabled,
|
||||
# regardless of request feature flags, model meta defaults, or UI toggles.
|
||||
model_info = (
|
||||
model_dict.get("info") if isinstance(model_dict, dict) else None
|
||||
)
|
||||
if isinstance(model_info, dict):
|
||||
model_meta = model_info.get("meta")
|
||||
if not isinstance(model_meta, dict):
|
||||
model_meta = {}
|
||||
model_info["meta"] = model_meta
|
||||
builtin_meta = model_meta.get("builtinTools")
|
||||
if not isinstance(builtin_meta, dict):
|
||||
builtin_meta = {}
|
||||
builtin_meta["web_search"] = True
|
||||
model_meta["builtinTools"] = builtin_meta
|
||||
|
||||
# Force feature selection to True for web_search to bypass UI session toggles
|
||||
if isinstance(body, dict):
|
||||
features = body.get("features")
|
||||
if not isinstance(features, dict):
|
||||
features = {}
|
||||
body["features"] = features
|
||||
features["web_search"] = True
|
||||
|
||||
# Get builtin tools
|
||||
# Code interpreter is STRICT opt-in: only enabled when request
|
||||
# explicitly sets feature code_interpreter=true. Missing means disabled.
|
||||
@@ -2380,6 +2405,13 @@ class Pipe:
|
||||
|
||||
converted_tools = []
|
||||
for tool_name, t_dict in tools_dict.items():
|
||||
if isinstance(tool_name, str) and tool_name.startswith("_"):
|
||||
if self.valves.DEBUG:
|
||||
await self._emit_debug_log(
|
||||
f"[Tools] Skip private tool: {tool_name}",
|
||||
__event_call__,
|
||||
)
|
||||
continue
|
||||
try:
|
||||
copilot_tool = self._convert_openwebui_tool_to_sdk(
|
||||
tool_name,
|
||||
@@ -2410,6 +2442,7 @@ class Pipe:
|
||||
return None
|
||||
|
||||
mcp_servers = {}
|
||||
selected_custom_tool_ids = self._extract_selected_custom_tool_ids(chat_tool_ids)
|
||||
|
||||
# Read MCP servers directly from DB to avoid stale in-memory cache
|
||||
connections = self._read_tool_server_connections()
|
||||
@@ -2440,8 +2473,15 @@ class Pipe:
|
||||
)
|
||||
continue
|
||||
|
||||
# P4: chat_tool_ids whitelist — if user selected tools, only include matching servers
|
||||
if chat_tool_ids and f"server:{raw_id}" not in chat_tool_ids:
|
||||
# P4: chat tool whitelist for MCP servers
|
||||
# OpenWebUI MCP tool IDs use "server:mcp:{id}" (not just "server:{id}").
|
||||
# Only enforce MCP server filtering when MCP server IDs are explicitly selected.
|
||||
selected_mcp_server_ids = {
|
||||
tid[len("server:mcp:") :]
|
||||
for tid in selected_custom_tool_ids
|
||||
if isinstance(tid, str) and tid.startswith("server:mcp:")
|
||||
}
|
||||
if selected_mcp_server_ids and raw_id not in selected_mcp_server_ids:
|
||||
continue
|
||||
|
||||
# Sanitize server_id (using same logic as tools)
|
||||
@@ -2478,13 +2518,18 @@ class Pipe:
|
||||
function_filter = mcp_config.get("function_name_filter_list", "")
|
||||
|
||||
allowed_tools = ["*"]
|
||||
if function_filter:
|
||||
if isinstance(function_filter, str):
|
||||
allowed_tools = [
|
||||
f.strip() for f in function_filter.split(",") if f.strip()
|
||||
]
|
||||
elif isinstance(function_filter, list):
|
||||
allowed_tools = function_filter
|
||||
parsed_filter = self._parse_mcp_function_filter(function_filter)
|
||||
expanded_filter = self._expand_mcp_filter_aliases(
|
||||
parsed_filter,
|
||||
raw_server_id=raw_id,
|
||||
sanitized_server_id=server_id,
|
||||
)
|
||||
self._emit_debug_log_sync(
|
||||
f"[MCP] function_name_filter_list raw={function_filter!r} parsed={parsed_filter} expanded={expanded_filter}",
|
||||
__event_call__,
|
||||
)
|
||||
if expanded_filter:
|
||||
allowed_tools = expanded_filter
|
||||
|
||||
mcp_servers[server_id] = {
|
||||
"type": "http",
|
||||
@@ -2630,6 +2675,142 @@ class Pipe:
|
||||
items = [item.strip() for item in value.split(",")]
|
||||
return self._dedupe_preserve_order([item for item in items if item])
|
||||
|
||||
def _normalize_chat_tool_ids(self, raw_tool_ids: Any) -> List[str]:
|
||||
"""Normalize chat tool_ids payload to a clean list[str]."""
|
||||
if not raw_tool_ids:
|
||||
return []
|
||||
|
||||
normalized: List[str] = []
|
||||
|
||||
if isinstance(raw_tool_ids, str):
|
||||
text = raw_tool_ids.strip()
|
||||
if not text:
|
||||
return []
|
||||
if text.startswith("["):
|
||||
try:
|
||||
parsed = json.loads(text)
|
||||
return self._normalize_chat_tool_ids(parsed)
|
||||
except Exception:
|
||||
pass
|
||||
normalized = [p.strip() for p in re.split(r"[,\n;]+", text) if p.strip()]
|
||||
return self._dedupe_preserve_order(normalized)
|
||||
|
||||
if isinstance(raw_tool_ids, (list, tuple, set)):
|
||||
for item in raw_tool_ids:
|
||||
if isinstance(item, str):
|
||||
value = item.strip()
|
||||
if value:
|
||||
normalized.append(value)
|
||||
continue
|
||||
|
||||
if isinstance(item, dict):
|
||||
for key in ("id", "tool_id", "value", "name"):
|
||||
value = item.get(key)
|
||||
if isinstance(value, str) and value.strip():
|
||||
normalized.append(value.strip())
|
||||
break
|
||||
|
||||
return self._dedupe_preserve_order(normalized)
|
||||
|
||||
def _extract_selected_custom_tool_ids(self, chat_tool_ids: Any) -> List[str]:
|
||||
"""Return selected non-builtin tool IDs only."""
|
||||
normalized = self._normalize_chat_tool_ids(chat_tool_ids)
|
||||
return self._dedupe_preserve_order(
|
||||
[
|
||||
tid
|
||||
for tid in normalized
|
||||
if isinstance(tid, str) and not tid.startswith("builtin:")
|
||||
]
|
||||
)
|
||||
|
||||
def _parse_mcp_function_filter(self, raw_filter: Any) -> List[str]:
|
||||
"""Parse MCP function filter list from string/list/json into normalized names."""
|
||||
if not raw_filter:
|
||||
return []
|
||||
|
||||
if isinstance(raw_filter, (list, tuple, set)):
|
||||
return self._dedupe_preserve_order(
|
||||
[
|
||||
str(item).strip().strip('"').strip("'")
|
||||
for item in raw_filter
|
||||
if str(item).strip().strip('"').strip("'")
|
||||
]
|
||||
)
|
||||
|
||||
if isinstance(raw_filter, str):
|
||||
text = raw_filter.strip()
|
||||
if not text:
|
||||
return []
|
||||
|
||||
if text.startswith("["):
|
||||
try:
|
||||
parsed = json.loads(text)
|
||||
return self._parse_mcp_function_filter(parsed)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
parts = re.split(r"[,\n;,、]+", text)
|
||||
cleaned: List[str] = []
|
||||
for part in parts:
|
||||
value = part.strip().strip('"').strip("'")
|
||||
if value.startswith("- "):
|
||||
value = value[2:].strip()
|
||||
if value:
|
||||
cleaned.append(value)
|
||||
return self._dedupe_preserve_order(cleaned)
|
||||
|
||||
return []
|
||||
|
||||
def _expand_mcp_filter_aliases(
|
||||
self,
|
||||
tool_names: List[str],
|
||||
raw_server_id: str,
|
||||
sanitized_server_id: str,
|
||||
) -> List[str]:
|
||||
"""Expand MCP filter names with common server-prefixed aliases.
|
||||
|
||||
Some MCP providers expose namespaced tool names such as:
|
||||
- github__get_me
|
||||
- github/get_me
|
||||
- github.get_me
|
||||
while admins often configure bare names like `get_me`.
|
||||
"""
|
||||
if not tool_names:
|
||||
return []
|
||||
|
||||
prefixes = self._dedupe_preserve_order(
|
||||
[
|
||||
str(raw_server_id or "").strip(),
|
||||
str(sanitized_server_id or "").strip(),
|
||||
]
|
||||
)
|
||||
|
||||
variants: List[str] = []
|
||||
for name in tool_names:
|
||||
clean_name = str(name).strip()
|
||||
if not clean_name:
|
||||
continue
|
||||
|
||||
# Keep original configured name first.
|
||||
variants.append(clean_name)
|
||||
|
||||
# If admin already provided a namespaced value, keep it as-is only.
|
||||
if any(sep in clean_name for sep in ("__", "/", ".")):
|
||||
continue
|
||||
|
||||
for prefix in prefixes:
|
||||
if not prefix:
|
||||
continue
|
||||
variants.extend(
|
||||
[
|
||||
f"{prefix}__{clean_name}",
|
||||
f"{prefix}/{clean_name}",
|
||||
f"{prefix}.{clean_name}",
|
||||
]
|
||||
)
|
||||
|
||||
return self._dedupe_preserve_order(variants)
|
||||
|
||||
def _is_manage_skills_intent(self, text: str) -> bool:
|
||||
"""Detect whether the user is asking to manage/install skills.
|
||||
|
||||
@@ -4343,9 +4524,9 @@ class Pipe:
|
||||
)
|
||||
|
||||
# P4: Chat tool_ids whitelist — extract once, reuse for both OpenAPI and MCP
|
||||
chat_tool_ids = None
|
||||
if __metadata__ and isinstance(__metadata__, dict):
|
||||
chat_tool_ids = __metadata__.get("tool_ids") or None
|
||||
chat_tool_ids = self._normalize_chat_tool_ids(
|
||||
__metadata__.get("tool_ids") if isinstance(__metadata__, dict) else None
|
||||
)
|
||||
|
||||
user_ctx = await self._get_user_context(__user__, __event_call__, __request__)
|
||||
user_lang = user_ctx["user_language"]
|
||||
|
||||
Reference in New Issue
Block a user