Compare commits
10 Commits
v2026.02.2
...
v2026.03.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e8b15af46 | ||
|
|
658f37baa6 | ||
|
|
c65ba57553 | ||
|
|
7c17dbbe23 | ||
|
|
c6279240b9 | ||
|
|
a8a324500a | ||
|
|
369e8c900c | ||
|
|
83e317a335 | ||
|
|
c28c3c837b | ||
|
|
701ea0b18f |
@@ -8,7 +8,43 @@ description: Automatically synchronizes plugin READMEs to the official documenta
|
||||
## Overview
|
||||
Automates the mirroring of `plugins/{type}/{name}/README.md` to `docs/plugins/{type}/{name}.md`.
|
||||
|
||||
## Docs-Only Mode (No Release Changes)
|
||||
Use this mode when the request is "only sync docs".
|
||||
|
||||
- Only update documentation mirror files under `docs/plugins/**`.
|
||||
- Do **not** bump plugin version.
|
||||
- Do **not** modify plugin code (`plugins/**.py`) unless explicitly requested.
|
||||
- Do **not** update root badges/dates for release.
|
||||
- Do **not** run release preparation steps.
|
||||
|
||||
## Workflow
|
||||
1. Identify changed READMEs.
|
||||
2. Copy content to corresponding mirror paths.
|
||||
3. Update version badges in `docs/plugins/{type}/index.md`.
|
||||
|
||||
## Commands
|
||||
|
||||
### Sync all mirrors (EN + ZH)
|
||||
|
||||
```bash
|
||||
python .github/skills/doc-mirror-sync/scripts/sync.py
|
||||
```
|
||||
|
||||
### Sync only one plugin (EN only)
|
||||
|
||||
```bash
|
||||
cp plugins/<type>/<name>/README.md docs/plugins/<type>/<name>.md
|
||||
```
|
||||
|
||||
### Sync only one plugin (EN + ZH)
|
||||
|
||||
```bash
|
||||
cp plugins/<type>/<name>/README.md docs/plugins/<type>/<name>.md
|
||||
cp plugins/<type>/<name>/README_CN.md docs/plugins/<type>/<name>.zh.md
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- If asked for English-only update, sync only `README.md` -> `.md` mirror.
|
||||
- If both languages are requested, sync both `README.md` and `README_CN.md`.
|
||||
- After syncing, verify git diff only contains docs file changes.
|
||||
|
||||
36
.github/skills/doc-mirror-sync/SKILL.md
vendored
36
.github/skills/doc-mirror-sync/SKILL.md
vendored
@@ -8,7 +8,43 @@ description: Automatically synchronizes plugin READMEs to the official documenta
|
||||
## Overview
|
||||
Automates the mirroring of `plugins/{type}/{name}/README.md` to `docs/plugins/{type}/{name}.md`.
|
||||
|
||||
## Docs-Only Mode (No Release Changes)
|
||||
Use this mode when the request is "only sync docs".
|
||||
|
||||
- Only update documentation mirror files under `docs/plugins/**`.
|
||||
- Do **not** bump plugin version.
|
||||
- Do **not** modify plugin code (`plugins/**.py`) unless explicitly requested.
|
||||
- Do **not** update root badges/dates for release.
|
||||
- Do **not** run release preparation steps.
|
||||
|
||||
## Workflow
|
||||
1. Identify changed READMEs.
|
||||
2. Copy content to corresponding mirror paths.
|
||||
3. Update version badges in `docs/plugins/{type}/index.md`.
|
||||
|
||||
## Commands
|
||||
|
||||
### Sync all mirrors (EN + ZH)
|
||||
|
||||
```bash
|
||||
python .github/skills/doc-mirror-sync/scripts/sync.py
|
||||
```
|
||||
|
||||
### Sync only one plugin (EN only)
|
||||
|
||||
```bash
|
||||
cp plugins/<type>/<name>/README.md docs/plugins/<type>/<name>.md
|
||||
```
|
||||
|
||||
### Sync only one plugin (EN + ZH)
|
||||
|
||||
```bash
|
||||
cp plugins/<type>/<name>/README.md docs/plugins/<type>/<name>.md
|
||||
cp plugins/<type>/<name>/README_CN.md docs/plugins/<type>/<name>.zh.md
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- If asked for English-only update, sync only `README.md` -> `.md` mirror.
|
||||
- If both languages are requested, sync both `README.md` and `README_CN.md`.
|
||||
- After syncing, verify git diff only contains docs file changes.
|
||||
|
||||
13
.github/skills/release-prep/SKILL.md
vendored
13
.github/skills/release-prep/SKILL.md
vendored
@@ -89,6 +89,18 @@ Each file must include:
|
||||
|
||||
If a release notes file already exists for this version, update it rather than creating a new one.
|
||||
|
||||
#### Full Coverage Rule (Mandatory)
|
||||
|
||||
Release notes must cover **all updates in the current release scope** and not only headline features.
|
||||
|
||||
Minimum required coverage in both EN/CN files:
|
||||
- New features and capability enhancements
|
||||
- Bug fixes and reliability fixes
|
||||
- Documentation/README/doc-mirror updates that affect user understanding or usage
|
||||
- Terminology/i18n/wording fixes that change visible behavior or messaging
|
||||
|
||||
Before commit, cross-check release notes against `git diff` and ensure no meaningful update is omitted.
|
||||
|
||||
### Step 5 — Verify Consistency (Pre-Commit Check)
|
||||
|
||||
Run the consistency check script:
|
||||
@@ -130,6 +142,7 @@ Confirm the commit hash and list the number of files changed.
|
||||
- [ ] Both `index.md` version badges updated
|
||||
- [ ] Root `README.md` and `README_CN.md` date badges updated to today
|
||||
- [ ] `What's New` / `最新更新` contains ONLY the latest release
|
||||
- [ ] Release notes include all meaningful updates from the current diff (feature + fix + docs/i18n)
|
||||
- [ ] `v{version}.md` and `v{version}_CN.md` created or updated
|
||||
- [ ] `python3 scripts/check_version_consistency.py` returns no errors
|
||||
- [ ] Commit message is English-only Conventional Commits format
|
||||
|
||||
77
.github/workflows/release.yml
vendored
77
.github/workflows/release.yml
vendored
@@ -22,6 +22,11 @@ on:
|
||||
- main
|
||||
paths:
|
||||
- 'plugins/**/*.py'
|
||||
- 'plugins/**/README.md'
|
||||
- 'plugins/**/README_CN.md'
|
||||
- 'plugins/**/v*.md'
|
||||
- 'plugins/**/v*_CN.md'
|
||||
- 'docs/plugins/**/*.md'
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
@@ -59,6 +64,8 @@ jobs:
|
||||
has_changes: ${{ steps.detect.outputs.has_changes }}
|
||||
changed_plugins: ${{ steps.detect.outputs.changed_plugins }}
|
||||
release_notes: ${{ steps.detect.outputs.release_notes }}
|
||||
has_doc_changes: ${{ steps.detect.outputs.has_doc_changes }}
|
||||
changed_doc_files: ${{ steps.detect.outputs.changed_doc_files }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -95,17 +102,19 @@ jobs:
|
||||
python scripts/extract_plugin_versions.py --json --output current_versions.json
|
||||
|
||||
# Get previous plugin versions by checking out old plugins
|
||||
if git worktree add /tmp/old_repo ${COMPARE_REF} 2>/dev/null; then
|
||||
if [ -d /tmp/old_repo/plugins ]; then
|
||||
python scripts/extract_plugin_versions.py --plugins-dir /tmp/old_repo/plugins --json --output old_versions.json
|
||||
OLD_WORKTREE=$(mktemp -d)
|
||||
if git worktree add "$OLD_WORKTREE" ${COMPARE_REF} 2>/dev/null; then
|
||||
if [ -d "$OLD_WORKTREE/plugins" ]; then
|
||||
python scripts/extract_plugin_versions.py --plugins-dir "$OLD_WORKTREE/plugins" --json --output old_versions.json
|
||||
else
|
||||
echo "[]" > old_versions.json
|
||||
fi
|
||||
git worktree remove /tmp/old_repo 2>/dev/null || true
|
||||
git worktree remove "$OLD_WORKTREE" 2>/dev/null || true
|
||||
else
|
||||
echo "Failed to create worktree, using empty version list"
|
||||
echo "[]" > old_versions.json
|
||||
fi
|
||||
rm -rf "$OLD_WORKTREE" 2>/dev/null || true
|
||||
|
||||
# Compare versions and generate release notes
|
||||
python scripts/extract_plugin_versions.py --compare old_versions.json --ignore-removed --output changes.md
|
||||
@@ -114,9 +123,32 @@ jobs:
|
||||
echo "=== Version Changes ==="
|
||||
cat changes.md
|
||||
|
||||
# Detect documentation/release-note changes that should be reflected in release notes
|
||||
git diff --name-only "$COMPARE_REF"..HEAD -- \
|
||||
'plugins/**/README.md' \
|
||||
'plugins/**/README_CN.md' \
|
||||
'plugins/**/v*.md' \
|
||||
'plugins/**/v*_CN.md' \
|
||||
'docs/plugins/**/*.md' > changed_docs.txt || true
|
||||
|
||||
if [ -s changed_docs.txt ]; then
|
||||
echo "has_doc_changes=true" >> $GITHUB_OUTPUT
|
||||
echo "changed_doc_files<<EOF" >> $GITHUB_OUTPUT
|
||||
cat changed_docs.txt >> $GITHUB_OUTPUT
|
||||
echo "" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_doc_changes=false" >> $GITHUB_OUTPUT
|
||||
echo "changed_doc_files=" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
# Check if there are any changes
|
||||
if grep -q "No changes detected" changes.md; then
|
||||
if [ -s changed_docs.txt ]; then
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
echo "changed_plugins=" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
@@ -146,6 +178,12 @@ jobs:
|
||||
{
|
||||
echo 'release_notes<<EOF'
|
||||
cat changes.md
|
||||
if [ -s changed_docs.txt ]; then
|
||||
echo ""
|
||||
echo "## Documentation Changes"
|
||||
echo ""
|
||||
sed 's/^/- /' changed_docs.txt
|
||||
fi
|
||||
echo ""
|
||||
echo 'EOF'
|
||||
} >> $GITHUB_OUTPUT
|
||||
@@ -214,7 +252,6 @@ jobs:
|
||||
id: plugins
|
||||
run: |
|
||||
python scripts/extract_plugin_versions.py --json --output plugin_versions.json
|
||||
python scripts/extract_plugin_versions.py --json --output plugin_versions.json
|
||||
|
||||
- name: Collect plugin files for release
|
||||
id: collect_files
|
||||
@@ -328,6 +365,7 @@ jobs:
|
||||
NOTES: ${{ github.event.inputs.release_notes }}
|
||||
DETECTED_CHANGES: ${{ needs.check-changes.outputs.release_notes }}
|
||||
COMMITS: ${{ steps.commits.outputs.commits }}
|
||||
DOC_FILES: ${{ needs.check-changes.outputs.changed_doc_files }}
|
||||
run: |
|
||||
> release_notes.md
|
||||
|
||||
@@ -357,6 +395,34 @@ jobs:
|
||||
echo "" >> release_notes.md
|
||||
fi
|
||||
|
||||
if [ -n "$DOC_FILES" ]; then
|
||||
echo "## Documentation Content" >> release_notes.md
|
||||
echo "" >> release_notes.md
|
||||
|
||||
# Prefer release-note files (v*.md / v*_CN.md), then include other doc files
|
||||
RELEASE_NOTE_FILES=$(echo "$DOC_FILES" | grep -E '^plugins/.*/v[^/]*\.md$' || true)
|
||||
OTHER_DOC_FILES=$(echo "$DOC_FILES" | grep -Ev '^plugins/.*/v[^/]*\.md$' || true)
|
||||
|
||||
if [ -n "$RELEASE_NOTE_FILES" ]; then
|
||||
while IFS= read -r file; do
|
||||
[ -z "$file" ] && continue
|
||||
if [ -f "$file" ]; then
|
||||
echo "### ${file}" >> release_notes.md
|
||||
echo "" >> release_notes.md
|
||||
cat "$file" >> release_notes.md
|
||||
echo "" >> release_notes.md
|
||||
fi
|
||||
done <<< "$RELEASE_NOTE_FILES"
|
||||
fi
|
||||
|
||||
if [ -n "$OTHER_DOC_FILES" ]; then
|
||||
echo "### Additional Documentation Files" >> release_notes.md
|
||||
echo "" >> release_notes.md
|
||||
echo "$OTHER_DOC_FILES" | sed 's/^/- /' >> release_notes.md
|
||||
echo "" >> release_notes.md
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
cat >> release_notes.md << 'EOF'
|
||||
@@ -389,6 +455,7 @@ jobs:
|
||||
cat release_notes.md
|
||||
|
||||
- name: Create Git Tag
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
|
||||
|
||||
28
README.md
28
README.md
@@ -9,7 +9,6 @@ A collection of enhancements, plugins, and prompts for [OpenWebUI](https://githu
|
||||
|
||||
<!-- STATS_START -->
|
||||
## 📊 Community Stats
|
||||
>
|
||||
> 
|
||||
|
||||
| 👤 Author | 👥 Followers | ⭐ Points | 🏆 Contributions |
|
||||
@@ -20,19 +19,19 @@ A collection of enhancements, plugins, and prompts for [OpenWebUI](https://githu
|
||||
| :---: | :---: | :---: | :---: | :---: |
|
||||
|  |  |  |  |  |
|
||||
|
||||
### 🔥 Top 6 Popular Plugins
|
||||
|
||||
### 🔥 Top 6 Popular Plugins
|
||||
| Rank | Plugin | Version | Downloads | Views | 📅 Updated |
|
||||
| :---: | :--- | :---: | :---: | :---: | :---: |
|
||||
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) |  |  |  |  |
|
||||
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) |  |  |  |  |
|
||||
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) |  |  |  |  |
|
||||
| 🥉 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) |  |  |  |  |
|
||||
| 🆕 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) |  |  |  |  |
|
||||
| 🥉 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) |  |  |  |  |
|
||||
| 4️⃣ | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) |  |  |  |  |
|
||||
| 5️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) |  |  |  |  |
|
||||
| 5️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) |  |  |  |  |
|
||||
| 6️⃣ | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) |  |  |  |  |
|
||||
|
||||
### 📈 Total Downloads Trend
|
||||
|
||||

|
||||
|
||||
*See full stats and charts in [Community Stats Report](./docs/community-stats.md)*
|
||||
@@ -40,20 +39,27 @@ A collection of enhancements, plugins, and prompts for [OpenWebUI](https://githu
|
||||
|
||||
## 🌟 Star Features
|
||||
|
||||
### 1. [GitHub Copilot SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) [](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4)
|
||||
### 1. [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) [](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) 
|
||||
|
||||
**The ultimate autonomous Agent for OpenWebUI.** Transforming your LLM into a powerful OS-level engineer with native code execution, deep tool autonomy, and professional skill management.
|
||||
**The ultimate autonomous Agent integration for OpenWebUI.** Deeply bridging GitHub Copilot SDK with your OpenWebUI ecosystem. It enables the Agent to autonomously perform **intent recognition**, **web search**, and **context compaction** while reusing your existing tools, skills, and configurations for a professional, full-featured experience.
|
||||
|
||||
> [!TIP]
|
||||
> **No GitHub Copilot subscription required!** Supports **BYOK (Bring Your Own Key)** mode using your own OpenAI/Anthropic API keys.
|
||||
|
||||
#### 🚀 Key Leap (v0.9.0+)
|
||||
#### 🚀 Key Leap (v0.9.1+)
|
||||
|
||||
- **🧩 Skills Revolution**: Native support for **SKILL directories** (scripts, templates, resources) coupled with a **Bidirectional Bridge** to OpenWebUI Workspace Skills.
|
||||
- **🔌 Seamless Ecosystem Integration**: Automatically injects and reuses your OpenWebUI **Tools**, **MCP**, **OpenAPI Servers**, and **Skills**, significantly enhancing the Agent's capabilities through your existing setup.
|
||||
- **🌐 Language Consistency**: System prompts mandate that Agent output language remains strictly consistent with user input.
|
||||
- **🧩 Skills Revolution**: Native support for **SKILL directories** and a **Bidirectional Bridge** to OpenWebUI Workspace Skills.
|
||||
- **🛡️ Secure Isolation**: Strict user/session-level **Workspace Sandboxing** with persistent configuration.
|
||||
- **📊 Interactive Delivery**: Professional **File Delivery Protocol** for instant HTML artifacts and persistent downloadable results.
|
||||
- **📊 Interactive Delivery**: Full support for **HTML Artifacts** and **RichUI** rendering, providing instant interactive previews and persistent downloadable results.
|
||||
- **🛠️ Deterministic Toolchain**: Built-in specialized tools for skill lifecycles (`manage_skills`) and system optimization.
|
||||
|
||||
> [!TIP]
|
||||
> **💡 Pro Tip: Enhanced Visualization**
|
||||
> We highly recommend asking the Agent to install the [Visual Explainer](https://github.com/nicobailon/visual-explainer) skill during your conversation. It dramatically improves the aesthetics and interactivity of generated **HTML Artifacts**. Simply tell the AI:
|
||||
> "Please install this skill: https://github.com/nicobailon/visual-explainer" to get started.
|
||||
|
||||
#### 📺 Demo: Visual Skills & Data Analysis
|
||||
|
||||

|
||||
|
||||
30
README_CN.md
30
README_CN.md
@@ -6,7 +6,6 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
|
||||
|
||||
<!-- STATS_START -->
|
||||
## 📊 社区统计
|
||||
>
|
||||
> 
|
||||
|
||||
| 👤 作者 | 👥 粉丝 | ⭐ 积分 | 🏆 贡献 |
|
||||
@@ -17,19 +16,19 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
|
||||
| :---: | :---: | :---: | :---: | :---: |
|
||||
|  |  |  |  |  |
|
||||
|
||||
### 🔥 热门插件 Top 6
|
||||
|
||||
### 🔥 热门插件 Top 6
|
||||
| 排名 | 插件 | 版本 | 下载 | 浏览 | 📅 更新 |
|
||||
| :---: | :--- | :---: | :---: | :---: | :---: |
|
||||
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) |  |  |  |  |
|
||||
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) |  |  |  |  |
|
||||
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) |  |  |  |  |
|
||||
| 🥉 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) |  |  |  |  |
|
||||
| 🆕 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) |  |  |  |  |
|
||||
| 🥉 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) |  |  |  |  |
|
||||
| 4️⃣ | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) |  |  |  |  |
|
||||
| 5️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) |  |  |  |  |
|
||||
| 5️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) |  |  |  |  |
|
||||
| 6️⃣ | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) |  |  |  |  |
|
||||
|
||||
### 📈 总下载量累计趋势
|
||||
|
||||

|
||||
|
||||
*完整统计与趋势图请查看 [社区统计报告](./docs/community-stats.zh.md)*
|
||||
@@ -37,19 +36,26 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
|
||||
|
||||
## 🌟 精选功能
|
||||
|
||||
### 1. [GitHub Copilot SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) [](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4)
|
||||
### 1. [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) [](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) 
|
||||
|
||||
**OpenWebUI 终极自主 Agent 增强。** 将 LLM 转化为具备 OS 级操作能力的专业工程师,支持原生代码执行、深度工具自治以及专业技能管理。
|
||||
**OpenWebUI 终极自主 Agent 深度集成。** 将 GitHub Copilot SDK 与 OpenWebUI 生态完美桥接。它允许 Agent 具备**智能意图识别**、**自主网页搜索**与**自动上下文压缩**能力,同时直接复用您现有的工具、技能与配置,通过全功能 Skill 体系带来极致的专业交互体验。
|
||||
|
||||
> [!TIP]
|
||||
> **无需 GitHub Copilot 订阅!** 支持 **BYOK (Bring Your Own Key)** 模式,使用你自己的 OpenAI/Anthropic API Key。
|
||||
|
||||
#### 🚀 核心进化 (v0.9.0+)
|
||||
#### 🚀 核心进化 (v0.9.1+)
|
||||
|
||||
- **🧩 技能革命**: 原生支持 **SKILL 目录**(含脚本、模板与资源),并实现与 OpenWebUI **工作区 > Skills** 的深度双向桥接。
|
||||
- **🔌 生态深度注入**: 自动读取并复用 OpenWebUI **工具 (Tools)**、**MCP**、**OpenAPI Server** 与 **技能 (Skills)**,显著增强 Agent 的实战能力。
|
||||
- **🧩 技能革命**: 原生支持 **SKILL 目录**,并实现与 OpenWebUI **工作区 > Skills** 的深度双向桥接。
|
||||
- **🛡️ 安全沙箱**: 严格的用户/会话级 **工作区隔离** 与持久化配置环境。
|
||||
- **📊 交互交付**: 专业 **文件交付协议**,支持即时预览交互式 HTML Artifacts 与持久化结果下载。
|
||||
- **📊 交互交付**: 完整支持 **HTML Artifacts** 与 **RichUI** 渲染,提供即时预览交互式应用程序与持久化结果下载。
|
||||
- **🛠️ 确定性工具链**: 内置 `manage_skills` 等专业工具,赋予 Agent 完整的技能生命周期管理能力。
|
||||
- **🌐 语言一致性**: 提示词强制要求 Agent 输出语言与用户输入保持一致,确保国际化体验。
|
||||
|
||||
> [!TIP]
|
||||
> **💡 进阶实战建议**
|
||||
> 强烈推荐在对话中让 Agent 为其安装 [Visual Explainer](https://github.com/nicobailon/visual-explainer) 技能。该技能能显著提升 **HTML Artifacts** 的美观度与交互深度,只需对 AI 说:
|
||||
> “请帮我安装这个技能:https://github.com/nicobailon/visual-explainer” 即可瞬间启用。
|
||||
|
||||
#### 📺 演示:可视化技能与数据分析
|
||||
|
||||
@@ -105,7 +111,7 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
|
||||
|
||||
### Pipes (模型管道)
|
||||
|
||||
- **GitHub Copilot SDK** (`github-copilot-sdk`): GitHub Copilot SDK 官方集成。支持动态模型、多轮对话、流式输出、图片输入及无限会话。
|
||||
- **GitHub Copilot SDK** (`github-copilot-sdk`): 深度集成 GitHub Copilot SDK 的强大 Agent。支持智能意图识别、自主网页搜索与上下文压缩,并能够无缝复用 OpenWebUI 的工具 (Tools)、MCP 与 OpenAPI Server。
|
||||
|
||||
### Pipelines (工作流管道)
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
{
|
||||
"total_posts": 25,
|
||||
"total_downloads": 6379,
|
||||
"total_views": 67827,
|
||||
"total_upvotes": 254,
|
||||
"total_downvotes": 3,
|
||||
"total_saves": 337,
|
||||
"total_downloads": 7058,
|
||||
"total_views": 75199,
|
||||
"total_upvotes": 273,
|
||||
"total_downvotes": 4,
|
||||
"total_saves": 372,
|
||||
"total_comments": 58,
|
||||
"by_type": {
|
||||
"post": 6,
|
||||
"tool": 1,
|
||||
"post": 5,
|
||||
"pipe": 1,
|
||||
"action": 12,
|
||||
"filter": 4,
|
||||
"action": 12,
|
||||
"prompt": 1,
|
||||
"review": 1
|
||||
},
|
||||
@@ -22,13 +23,13 @@
|
||||
"version": "1.0.0",
|
||||
"author": "Fu-Jie",
|
||||
"description": "Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.",
|
||||
"downloads": 1328,
|
||||
"views": 11410,
|
||||
"upvotes": 23,
|
||||
"saves": 59,
|
||||
"downloads": 1426,
|
||||
"views": 12082,
|
||||
"upvotes": 26,
|
||||
"saves": 63,
|
||||
"comments": 15,
|
||||
"created_at": "2025-12-30",
|
||||
"updated_at": "2026-02-27",
|
||||
"created_at": "2025-12-31",
|
||||
"updated_at": "2026-02-28",
|
||||
"url": "https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a"
|
||||
},
|
||||
{
|
||||
@@ -38,10 +39,10 @@
|
||||
"version": "1.5.0",
|
||||
"author": "Fu-Jie",
|
||||
"description": "AI-powered infographic generator based on AntV Infographic. Supports professional templates, auto-icon matching, and SVG/PNG downloads.",
|
||||
"downloads": 1076,
|
||||
"views": 10746,
|
||||
"downloads": 1155,
|
||||
"views": 11609,
|
||||
"upvotes": 25,
|
||||
"saves": 40,
|
||||
"saves": 45,
|
||||
"comments": 10,
|
||||
"created_at": "2025-12-28",
|
||||
"updated_at": "2026-02-13",
|
||||
@@ -54,13 +55,13 @@
|
||||
"version": "1.2.7",
|
||||
"author": "Fu-Jie",
|
||||
"description": "A content normalizer filter that fixes common Markdown formatting issues in LLM outputs, such as broken code blocks, LaTeX formulas, and list formatting. Including LaTeX command protection.",
|
||||
"downloads": 609,
|
||||
"views": 6795,
|
||||
"upvotes": 18,
|
||||
"saves": 37,
|
||||
"downloads": 661,
|
||||
"views": 7239,
|
||||
"upvotes": 20,
|
||||
"saves": 40,
|
||||
"comments": 5,
|
||||
"created_at": "2026-01-12",
|
||||
"updated_at": "2026-02-27",
|
||||
"updated_at": "2026-02-28",
|
||||
"url": "https://openwebui.com/posts/markdown_normalizer_baaa8732"
|
||||
},
|
||||
{
|
||||
@@ -70,10 +71,10 @@
|
||||
"version": "0.4.4",
|
||||
"author": "Fu-Jie",
|
||||
"description": "Export current conversation from Markdown to Word (.docx) with Mermaid diagrams rendered client-side (Mermaid.js, SVG+PNG), LaTeX math, real hyperlinks, improved tables, syntax highlighting, and blockquote support.",
|
||||
"downloads": 578,
|
||||
"views": 4611,
|
||||
"downloads": 628,
|
||||
"views": 4995,
|
||||
"upvotes": 16,
|
||||
"saves": 30,
|
||||
"saves": 35,
|
||||
"comments": 5,
|
||||
"created_at": "2026-01-03",
|
||||
"updated_at": "2026-02-13",
|
||||
@@ -86,13 +87,13 @@
|
||||
"version": "1.3.0",
|
||||
"author": "Fu-Jie",
|
||||
"description": "Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.",
|
||||
"downloads": 559,
|
||||
"views": 5452,
|
||||
"upvotes": 15,
|
||||
"saves": 41,
|
||||
"downloads": 619,
|
||||
"views": 5875,
|
||||
"upvotes": 16,
|
||||
"saves": 46,
|
||||
"comments": 0,
|
||||
"created_at": "2025-11-08",
|
||||
"updated_at": "2026-02-21",
|
||||
"updated_at": "2026-02-28",
|
||||
"url": "https://openwebui.com/posts/async_context_compression_b1655bc8"
|
||||
},
|
||||
{
|
||||
@@ -102,10 +103,10 @@
|
||||
"version": "0.3.7",
|
||||
"author": "Fu-Jie",
|
||||
"description": "Extracts tables from chat messages and exports them to Excel (.xlsx) files with smart formatting.",
|
||||
"downloads": 492,
|
||||
"views": 2693,
|
||||
"downloads": 523,
|
||||
"views": 2898,
|
||||
"upvotes": 10,
|
||||
"saves": 8,
|
||||
"saves": 9,
|
||||
"comments": 0,
|
||||
"created_at": "2025-05-30",
|
||||
"updated_at": "2026-02-13",
|
||||
@@ -118,8 +119,8 @@
|
||||
"version": "",
|
||||
"author": "",
|
||||
"description": "",
|
||||
"downloads": 473,
|
||||
"views": 5498,
|
||||
"downloads": 523,
|
||||
"views": 6055,
|
||||
"upvotes": 9,
|
||||
"saves": 14,
|
||||
"comments": 0,
|
||||
@@ -127,22 +128,6 @@
|
||||
"updated_at": "2026-01-28",
|
||||
"url": "https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37"
|
||||
},
|
||||
{
|
||||
"title": "Flash Card",
|
||||
"slug": "flash_card_65a2ea8f",
|
||||
"type": "action",
|
||||
"version": "0.2.4",
|
||||
"author": "Fu-Jie",
|
||||
"description": "Quickly generates beautiful flashcards from text, extracting key points and categories.",
|
||||
"downloads": 285,
|
||||
"views": 4128,
|
||||
"upvotes": 13,
|
||||
"saves": 18,
|
||||
"comments": 2,
|
||||
"created_at": "2025-12-30",
|
||||
"updated_at": "2026-02-13",
|
||||
"url": "https://openwebui.com/posts/flash_card_65a2ea8f"
|
||||
},
|
||||
{
|
||||
"title": "GitHub Copilot Official SDK Pipe",
|
||||
"slug": "github_copilot_official_sdk_pipe_ce96f7b4",
|
||||
@@ -150,15 +135,31 @@
|
||||
"version": "0.9.0",
|
||||
"author": "Fu-Jie",
|
||||
"description": "Integrate GitHub Copilot SDK. Supports dynamic models, multi-turn conversation, streaming, multimodal input, infinite sessions, bidirectional OpenWebUI Skills bridge, and manage_skills tool.",
|
||||
"downloads": 263,
|
||||
"views": 4106,
|
||||
"upvotes": 14,
|
||||
"downloads": 301,
|
||||
"views": 4540,
|
||||
"upvotes": 16,
|
||||
"saves": 10,
|
||||
"comments": 6,
|
||||
"created_at": "2026-01-26",
|
||||
"updated_at": "2026-02-27",
|
||||
"updated_at": "2026-02-28",
|
||||
"url": "https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4"
|
||||
},
|
||||
{
|
||||
"title": "Flash Card",
|
||||
"slug": "flash_card_65a2ea8f",
|
||||
"type": "action",
|
||||
"version": "0.2.4",
|
||||
"author": "Fu-Jie",
|
||||
"description": "Quickly generates beautiful flashcards from text, extracting key points and categories.",
|
||||
"downloads": 295,
|
||||
"views": 4297,
|
||||
"upvotes": 13,
|
||||
"saves": 20,
|
||||
"comments": 2,
|
||||
"created_at": "2025-12-30",
|
||||
"updated_at": "2026-02-13",
|
||||
"url": "https://openwebui.com/posts/flash_card_65a2ea8f"
|
||||
},
|
||||
{
|
||||
"title": "Deep Dive",
|
||||
"slug": "deep_dive_c0b846e4",
|
||||
@@ -166,15 +167,31 @@
|
||||
"version": "1.0.0",
|
||||
"author": "Fu-Jie",
|
||||
"description": "A comprehensive thinking lens that dives deep into any content - from context to logic, insights, and action paths.",
|
||||
"downloads": 204,
|
||||
"views": 1631,
|
||||
"downloads": 211,
|
||||
"views": 1699,
|
||||
"upvotes": 6,
|
||||
"saves": 13,
|
||||
"saves": 14,
|
||||
"comments": 0,
|
||||
"created_at": "2026-01-08",
|
||||
"updated_at": "2026-01-08",
|
||||
"url": "https://openwebui.com/posts/deep_dive_c0b846e4"
|
||||
},
|
||||
{
|
||||
"title": "OpenWebUI Skills Manager Tool",
|
||||
"slug": "openwebui_skills_manager_tool_b4bce8e4",
|
||||
"type": "tool",
|
||||
"version": "",
|
||||
"author": "",
|
||||
"description": "",
|
||||
"downloads": 169,
|
||||
"views": 2629,
|
||||
"upvotes": 6,
|
||||
"saves": 7,
|
||||
"comments": 0,
|
||||
"created_at": "2026-02-28",
|
||||
"updated_at": "2026-02-28",
|
||||
"url": "https://openwebui.com/posts/openwebui_skills_manager_tool_b4bce8e4"
|
||||
},
|
||||
{
|
||||
"title": "导出为Word增强版",
|
||||
"slug": "导出为_word_支持公式流程图表格和代码块_8a6306c0",
|
||||
@@ -182,8 +199,8 @@
|
||||
"version": "0.4.4",
|
||||
"author": "Fu-Jie",
|
||||
"description": "将对话导出为 Word (.docx),支持 Mermaid 图表 (客户端渲染 SVG+PNG)、LaTeX 数学公式、真实超链接、增强表格格式、代码高亮和引用块。",
|
||||
"downloads": 153,
|
||||
"views": 2631,
|
||||
"downloads": 157,
|
||||
"views": 2732,
|
||||
"upvotes": 14,
|
||||
"saves": 7,
|
||||
"comments": 4,
|
||||
@@ -198,8 +215,8 @@
|
||||
"version": "0.1.0",
|
||||
"author": "Fu-Jie",
|
||||
"description": "Automatically extracts project rules from conversations and injects them into the folder's system prompt.",
|
||||
"downloads": 99,
|
||||
"views": 1839,
|
||||
"downloads": 106,
|
||||
"views": 1911,
|
||||
"upvotes": 7,
|
||||
"saves": 11,
|
||||
"comments": 0,
|
||||
@@ -207,6 +224,22 @@
|
||||
"updated_at": "2026-01-20",
|
||||
"url": "https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2"
|
||||
},
|
||||
{
|
||||
"title": "GitHub Copilot SDK Files Filter",
|
||||
"slug": "github_copilot_sdk_files_filter_403a62ee",
|
||||
"type": "filter",
|
||||
"version": "0.1.3",
|
||||
"author": "Fu-Jie",
|
||||
"description": "A specialized filter to bypass OpenWebUI's default RAG for GitHub Copilot SDK models. It moves uploaded files to a safe location ('copilot_files') so the Copilot Pipe can process them natively without interference.",
|
||||
"downloads": 69,
|
||||
"views": 2231,
|
||||
"upvotes": 4,
|
||||
"saves": 1,
|
||||
"comments": 0,
|
||||
"created_at": "2026-02-09",
|
||||
"updated_at": "2026-02-26",
|
||||
"url": "https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee"
|
||||
},
|
||||
{
|
||||
"title": "智能信息图",
|
||||
"slug": "智能信息图_e04a48ff",
|
||||
@@ -215,7 +248,7 @@
|
||||
"author": "Fu-Jie",
|
||||
"description": "基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。",
|
||||
"downloads": 65,
|
||||
"views": 1304,
|
||||
"views": 1370,
|
||||
"upvotes": 10,
|
||||
"saves": 1,
|
||||
"comments": 0,
|
||||
@@ -223,22 +256,6 @@
|
||||
"updated_at": "2026-02-13",
|
||||
"url": "https://openwebui.com/posts/智能信息图_e04a48ff"
|
||||
},
|
||||
{
|
||||
"title": "GitHub Copilot SDK Files Filter",
|
||||
"slug": "github_copilot_sdk_files_filter_403a62ee",
|
||||
"type": "filter",
|
||||
"version": "0.1.3",
|
||||
"author": "Fu-Jie",
|
||||
"description": "A specialized filter to bypass OpenWebUI's default RAG for GitHub Copilot SDK models. It moves uploaded files to a safe location ('copilot_files') so the Copilot Pipe can process them natively without interference.",
|
||||
"downloads": 54,
|
||||
"views": 2098,
|
||||
"upvotes": 3,
|
||||
"saves": 1,
|
||||
"comments": 0,
|
||||
"created_at": "2026-02-09",
|
||||
"updated_at": "2026-02-25",
|
||||
"url": "https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee"
|
||||
},
|
||||
{
|
||||
"title": "思维导图",
|
||||
"slug": "智能生成交互式思维导图帮助用户可视化知识_8d4b097b",
|
||||
@@ -246,8 +263,8 @@
|
||||
"version": "0.9.2",
|
||||
"author": "Fu-Jie",
|
||||
"description": "智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。",
|
||||
"downloads": 45,
|
||||
"views": 691,
|
||||
"downloads": 50,
|
||||
"views": 734,
|
||||
"upvotes": 6,
|
||||
"saves": 2,
|
||||
"comments": 0,
|
||||
@@ -263,7 +280,7 @@
|
||||
"author": "Fu-Jie",
|
||||
"description": "通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。",
|
||||
"downloads": 38,
|
||||
"views": 783,
|
||||
"views": 814,
|
||||
"upvotes": 7,
|
||||
"saves": 5,
|
||||
"comments": 0,
|
||||
@@ -278,8 +295,8 @@
|
||||
"version": "0.2.4",
|
||||
"author": "Fu-Jie",
|
||||
"description": "快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类。",
|
||||
"downloads": 32,
|
||||
"views": 830,
|
||||
"downloads": 33,
|
||||
"views": 863,
|
||||
"upvotes": 7,
|
||||
"saves": 1,
|
||||
"comments": 0,
|
||||
@@ -294,8 +311,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": "Fu-Jie",
|
||||
"description": "全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。",
|
||||
"downloads": 26,
|
||||
"views": 581,
|
||||
"downloads": 29,
|
||||
"views": 626,
|
||||
"upvotes": 5,
|
||||
"saves": 1,
|
||||
"comments": 0,
|
||||
@@ -304,51 +321,35 @@
|
||||
"url": "https://openwebui.com/posts/精读_99830b0f"
|
||||
},
|
||||
{
|
||||
"title": "🚀 GitHub Copilot SDK Pipe v0.9.0: Copilot SDK Skills Core Capabilities & Extended Delivery",
|
||||
"title": "🚀 GitHub Copilot SDK Pipe v0.9.0: Skills & RichUI",
|
||||
"slug": "github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452",
|
||||
"type": "post",
|
||||
"version": "",
|
||||
"author": "",
|
||||
"description": "",
|
||||
"downloads": 0,
|
||||
"views": 7,
|
||||
"upvotes": 0,
|
||||
"saves": 0,
|
||||
"views": 1162,
|
||||
"upvotes": 5,
|
||||
"saves": 1,
|
||||
"comments": 0,
|
||||
"created_at": "2026-02-27",
|
||||
"updated_at": "2026-02-27",
|
||||
"created_at": "2026-02-28",
|
||||
"updated_at": "2026-02-28",
|
||||
"url": "https://openwebui.com/posts/github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452"
|
||||
},
|
||||
{
|
||||
"title": "🚀 GitHub Copilot SDK Pipe v0.8.0: Conditional Tool Filtering & Publish Reliability 🎛️",
|
||||
"slug": "github_copilot_sdk_pipe_v080_conditional_tool_filt_a5a3322d",
|
||||
"type": "post",
|
||||
"version": "",
|
||||
"author": "",
|
||||
"description": "",
|
||||
"downloads": 0,
|
||||
"views": 1059,
|
||||
"upvotes": 2,
|
||||
"saves": 2,
|
||||
"comments": 0,
|
||||
"created_at": "2026-02-25",
|
||||
"updated_at": "2026-02-25",
|
||||
"url": "https://openwebui.com/posts/github_copilot_sdk_pipe_v080_conditional_tool_filt_a5a3322d"
|
||||
},
|
||||
{
|
||||
"title": "🚀 GitHub Copilot SDK Pipe v0.7.0: Native Tool UI & Zero-Config CLI 🛠️",
|
||||
"title": "🚀 GitHub Copilot SDK Pipe v0.7.0: Skills & Rich UI 🛠️",
|
||||
"slug": "github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131",
|
||||
"type": "post",
|
||||
"version": "",
|
||||
"author": "",
|
||||
"description": "",
|
||||
"downloads": 0,
|
||||
"views": 2162,
|
||||
"upvotes": 7,
|
||||
"views": 2504,
|
||||
"upvotes": 8,
|
||||
"saves": 2,
|
||||
"comments": 1,
|
||||
"created_at": "2026-02-22",
|
||||
"updated_at": "2026-02-22",
|
||||
"created_at": "2026-02-23",
|
||||
"updated_at": "2026-02-28",
|
||||
"url": "https://openwebui.com/posts/github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131"
|
||||
},
|
||||
{
|
||||
@@ -359,7 +360,7 @@
|
||||
"author": "",
|
||||
"description": "",
|
||||
"downloads": 0,
|
||||
"views": 2257,
|
||||
"views": 2341,
|
||||
"upvotes": 7,
|
||||
"saves": 4,
|
||||
"comments": 0,
|
||||
@@ -375,12 +376,12 @@
|
||||
"author": "",
|
||||
"description": "",
|
||||
"downloads": 0,
|
||||
"views": 1839,
|
||||
"views": 1887,
|
||||
"upvotes": 12,
|
||||
"saves": 19,
|
||||
"saves": 21,
|
||||
"comments": 8,
|
||||
"created_at": "2026-01-25",
|
||||
"updated_at": "2026-01-28",
|
||||
"updated_at": "2026-01-29",
|
||||
"url": "https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e"
|
||||
},
|
||||
{
|
||||
@@ -391,7 +392,7 @@
|
||||
"author": "",
|
||||
"description": "",
|
||||
"downloads": 0,
|
||||
"views": 234,
|
||||
"views": 246,
|
||||
"upvotes": 2,
|
||||
"saves": 0,
|
||||
"comments": 0,
|
||||
@@ -407,9 +408,9 @@
|
||||
"author": "",
|
||||
"description": "",
|
||||
"downloads": 0,
|
||||
"views": 1502,
|
||||
"views": 1531,
|
||||
"upvotes": 16,
|
||||
"saves": 11,
|
||||
"saves": 12,
|
||||
"comments": 2,
|
||||
"created_at": "2026-01-10",
|
||||
"updated_at": "2026-01-10",
|
||||
@@ -421,10 +422,10 @@
|
||||
"name": "Fu-Jie",
|
||||
"profile_url": "https://openwebui.com/u/Fu-Jie",
|
||||
"profile_image": "https://community.s3.openwebui.com/uploads/users/b15d1348-4347-42b4-b815-e053342d6cb0/profile_d9510745-4bd4-4f8f-a997-4a21847d9300.webp",
|
||||
"followers": 295,
|
||||
"followers": 307,
|
||||
"following": 6,
|
||||
"total_points": 299,
|
||||
"post_points": 251,
|
||||
"total_points": 319,
|
||||
"post_points": 271,
|
||||
"comment_points": 48,
|
||||
"contributions": 54
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
> *Blue: Downloads | Purple: Views (Real-time dynamic)*
|
||||
|
||||
### 📂 Content Distribution
|
||||

|
||||

|
||||
|
||||
|
||||
## 📈 Overview
|
||||
@@ -25,10 +25,11 @@
|
||||
|
||||
## 📂 By Type
|
||||
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
|
||||
@@ -36,28 +37,28 @@
|
||||
|
||||
| Rank | Title | Type | Version | Downloads | Views | Upvotes | Saves | Updated |
|
||||
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action |  |  |  |  |  | 2026-02-27 |
|
||||
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action |  |  |  |  |  | 2026-02-28 |
|
||||
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 3 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | filter |  |  |  |  |  | 2026-02-27 |
|
||||
| 3 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | filter |  |  |  |  |  | 2026-02-28 |
|
||||
| 4 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 5 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | filter |  |  |  |  |  | 2026-02-21 |
|
||||
| 5 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | filter |  |  |  |  |  | 2026-02-28 |
|
||||
| 6 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 7 | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | prompt |  |  |  |  |  | 2026-01-28 |
|
||||
| 8 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 9 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe |  |  |  |  |  | 2026-02-27 |
|
||||
| 8 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe |  |  |  |  |  | 2026-02-28 |
|
||||
| 9 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 10 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action |  |  |  |  |  | 2026-01-08 |
|
||||
| 11 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 12 | [📂 Folder Memory – Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter |  |  |  |  |  | 2026-01-20 |
|
||||
| 13 | [智能信息图](https://openwebui.com/posts/智能信息图_e04a48ff) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 14 | [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee) | filter |  |  |  |  |  | 2026-02-25 |
|
||||
| 15 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 16 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 17 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 18 | [精读](https://openwebui.com/posts/精读_99830b0f) | action |  |  |  |  |  | 2026-01-08 |
|
||||
| 19 | [🚀 GitHub Copilot SDK Pipe v0.9.0: Copilot SDK Skills Core Capabilities & Extended Delivery](https://openwebui.com/posts/github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452) | post |  |  |  |  |  | 2026-02-27 |
|
||||
| 20 | [🚀 GitHub Copilot SDK Pipe v0.8.0: Conditional Tool Filtering & Publish Reliability 🎛️](https://openwebui.com/posts/github_copilot_sdk_pipe_v080_conditional_tool_filt_a5a3322d) | post |  |  |  |  |  | 2026-02-25 |
|
||||
| 21 | [🚀 GitHub Copilot SDK Pipe v0.7.0: Native Tool UI & Zero-Config CLI 🛠️](https://openwebui.com/posts/github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131) | post |  |  |  |  |  | 2026-02-22 |
|
||||
| 11 | [OpenWebUI Skills Manager Tool](https://openwebui.com/posts/openwebui_skills_manager_tool_b4bce8e4) | tool |  |  |  |  |  | 2026-02-28 |
|
||||
| 12 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 13 | [📂 Folder Memory – Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter |  |  |  |  |  | 2026-01-20 |
|
||||
| 14 | [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee) | filter |  |  |  |  |  | 2026-02-26 |
|
||||
| 15 | [智能信息图](https://openwebui.com/posts/智能信息图_e04a48ff) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 16 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 17 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 18 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 19 | [精读](https://openwebui.com/posts/精读_99830b0f) | action |  |  |  |  |  | 2026-01-08 |
|
||||
| 20 | [🚀 GitHub Copilot SDK Pipe v0.9.0: Skills & RichUI](https://openwebui.com/posts/github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452) | post |  |  |  |  |  | 2026-02-28 |
|
||||
| 21 | [🚀 GitHub Copilot SDK Pipe v0.7.0: Skills & Rich UI 🛠️](https://openwebui.com/posts/github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131) | post |  |  |  |  |  | 2026-02-28 |
|
||||
| 22 | [🚀 GitHub Copilot SDK Pipe: AI That Executes, Not Just Talks](https://openwebui.com/posts/github_copilot_sdk_for_openwebui_elevate_your_ai_t_a140f293) | post |  |  |  |  |  | 2026-02-10 |
|
||||
| 23 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | post |  |  |  |  |  | 2026-01-28 |
|
||||
| 23 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | post |  |  |  |  |  | 2026-01-29 |
|
||||
| 24 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | review |  |  |  |  |  | 2026-01-14 |
|
||||
| 25 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | post |  |  |  |  |  | 2026-01-10 |
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
> *蓝色: 总下载量 | 紫色: 总浏览量 (实时动态生成)*
|
||||
|
||||
### 📂 内容分类占比 (Distribution)
|
||||

|
||||

|
||||
|
||||
|
||||
## 📈 总览
|
||||
@@ -25,10 +25,11 @@
|
||||
|
||||
## 📂 按类型分类
|
||||
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
|
||||
@@ -36,28 +37,28 @@
|
||||
|
||||
| 排名 | 标题 | 类型 | 版本 | 下载 | 浏览 | 点赞 | 收藏 | 更新日期 |
|
||||
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action |  |  |  |  |  | 2026-02-27 |
|
||||
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action |  |  |  |  |  | 2026-02-28 |
|
||||
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 3 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | filter |  |  |  |  |  | 2026-02-27 |
|
||||
| 3 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | filter |  |  |  |  |  | 2026-02-28 |
|
||||
| 4 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 5 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | filter |  |  |  |  |  | 2026-02-21 |
|
||||
| 5 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | filter |  |  |  |  |  | 2026-02-28 |
|
||||
| 6 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 7 | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | prompt |  |  |  |  |  | 2026-01-28 |
|
||||
| 8 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 9 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe |  |  |  |  |  | 2026-02-27 |
|
||||
| 8 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe |  |  |  |  |  | 2026-02-28 |
|
||||
| 9 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 10 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action |  |  |  |  |  | 2026-01-08 |
|
||||
| 11 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 12 | [📂 Folder Memory – Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter |  |  |  |  |  | 2026-01-20 |
|
||||
| 13 | [智能信息图](https://openwebui.com/posts/智能信息图_e04a48ff) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 14 | [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee) | filter |  |  |  |  |  | 2026-02-25 |
|
||||
| 15 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 16 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 17 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 18 | [精读](https://openwebui.com/posts/精读_99830b0f) | action |  |  |  |  |  | 2026-01-08 |
|
||||
| 19 | [🚀 GitHub Copilot SDK Pipe v0.9.0: Copilot SDK Skills Core Capabilities & Extended Delivery](https://openwebui.com/posts/github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452) | post |  |  |  |  |  | 2026-02-27 |
|
||||
| 20 | [🚀 GitHub Copilot SDK Pipe v0.8.0: Conditional Tool Filtering & Publish Reliability 🎛️](https://openwebui.com/posts/github_copilot_sdk_pipe_v080_conditional_tool_filt_a5a3322d) | post |  |  |  |  |  | 2026-02-25 |
|
||||
| 21 | [🚀 GitHub Copilot SDK Pipe v0.7.0: Native Tool UI & Zero-Config CLI 🛠️](https://openwebui.com/posts/github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131) | post |  |  |  |  |  | 2026-02-22 |
|
||||
| 11 | [OpenWebUI Skills Manager Tool](https://openwebui.com/posts/openwebui_skills_manager_tool_b4bce8e4) | tool |  |  |  |  |  | 2026-02-28 |
|
||||
| 12 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 13 | [📂 Folder Memory – Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter |  |  |  |  |  | 2026-01-20 |
|
||||
| 14 | [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee) | filter |  |  |  |  |  | 2026-02-26 |
|
||||
| 15 | [智能信息图](https://openwebui.com/posts/智能信息图_e04a48ff) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 16 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 17 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 18 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action |  |  |  |  |  | 2026-02-13 |
|
||||
| 19 | [精读](https://openwebui.com/posts/精读_99830b0f) | action |  |  |  |  |  | 2026-01-08 |
|
||||
| 20 | [🚀 GitHub Copilot SDK Pipe v0.9.0: Skills & RichUI](https://openwebui.com/posts/github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452) | post |  |  |  |  |  | 2026-02-28 |
|
||||
| 21 | [🚀 GitHub Copilot SDK Pipe v0.7.0: Skills & Rich UI 🛠️](https://openwebui.com/posts/github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131) | post |  |  |  |  |  | 2026-02-28 |
|
||||
| 22 | [🚀 GitHub Copilot SDK Pipe: AI That Executes, Not Just Talks](https://openwebui.com/posts/github_copilot_sdk_for_openwebui_elevate_your_ai_t_a140f293) | post |  |  |  |  |  | 2026-02-10 |
|
||||
| 23 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | post |  |  |  |  |  | 2026-01-28 |
|
||||
| 23 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | post |  |  |  |  |  | 2026-01-29 |
|
||||
| 24 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | review |  |  |  |  |  | 2026-01-14 |
|
||||
| 25 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | post |  |  |  |  |  | 2026-01-10 |
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
# 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**.
|
||||
This is a powerful **GitHub Copilot SDK** Pipe for **OpenWebUI** that provides a unified **Agentic experience**. It goes beyond simple model access by enabling autonomous **Intent Recognition**, **Web Search**, and **Context Compaction**. It seamlessly reuses your existing **Tools, MCP servers, OpenAPI servers, and Skills** from OpenWebUI to create a truly integrated ecosystem.
|
||||
|
||||
- **🧠 Autonomous Intent Recognition**: The Agent independently analyzes user goals to determine the most effective path forward.
|
||||
- **🌐 Smart Web Search**: Built-in capability to trigger web searches autonomously based on task requirements.
|
||||
- **♾️ Infinite Session (Context Compaction)**: Automatically manages long-running conversations by compacting context (summarization + TODO persistence) to maintain project focus.
|
||||
- **🧩 Ecosystem Injection**: Directly reads and leverages your configured **OpenWebUI Tools, MCPs, OpenAPI Servers, and Skills**.
|
||||
- **🎨 Interactive Delivery**: Native support for **HTML Artifacts** and **RichUI** components for real-time visualization and reporting.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Essential Companion**
|
||||
@@ -14,19 +20,19 @@ This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/
|
||||
|
||||
---
|
||||
|
||||
## ✨ v0.9.0: The Skills Revolution & Stability Update
|
||||
## ✨ v0.9.1: Autonomous Web Search & Reliability Fix
|
||||
|
||||
- **🧩 Copilot SDK Skills Support**: Native support for Copilot SDK skill directories (`SKILL.md` + resources).
|
||||
- **🔄 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.
|
||||
- **🗂️ Persistent Config Directory**: Added `COPILOTSDK_CONFIG_DIR` for stable session-state persistence across container restarts.
|
||||
- **🌐 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.
|
||||
- **🛠️ Terminology Alignment**: Standardized all references to **"Agent"** and **"Context Compaction"** (for Infinite Session) across all languages to better reflect the technical capabilities.
|
||||
- **🌐 Language Consistency**: System prompts mandate that Agent output language remains strictly consistent with user input.
|
||||
- **🐛 Fixed MCP Tool Filtering**: 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:`).
|
||||
- **🔍 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.
|
||||
@@ -38,7 +44,13 @@ This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/
|
||||
- **Live HTML/JS**: Instantly render and interact with apps, dashboards, or reports generated by the Agent.
|
||||
- **Persistent Publishing**: Agents can "publish" generated files (Excel, CSV, docs) to OpenWebUI's file storage, providing permanent download links.
|
||||
- **🌊 UX-First Streaming**: Full support for "Thinking" processes (Chain of Thought), status indicators, and real-time progress bars for long-running tasks.
|
||||
- **🧠 Deep Database Integration**: Real-time persistence of TOD·O lists and session metadata ensures your workflow state is always visible in the UI.
|
||||
- **🧠 Deep Database Integration**: Real-time persistence of TODO lists and session metadata ensures your workflow state is always visible in the UI.
|
||||
|
||||
> [!TIP]
|
||||
> **💡 Visualization Pro-Tip**
|
||||
> To get the most out of **HTML Artifacts** and **RichUI**, we highly recommend asking the Agent to install the skill via its GitHub URL:
|
||||
> "Install this skill: https://github.com/nicobailon/visual-explainer".
|
||||
> This skill is specifically optimized for generating high-quality visual components and integrates perfectly with this Pipe.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,46 +1,57 @@
|
||||
# GitHub Copilot SDK 官方管道
|
||||
# GitHub Copilot Official SDK Pipe
|
||||
|
||||
**作者:** [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 交互体验。
|
||||
这是一个将 **GitHub Copilot SDK** 深度集成到 **OpenWebUI** 中的强大 Agent SDK 管道。它不仅实现了 SDK 的核心功能,还支持 **智能意图识别**、**自主网页搜索** 与 **自动上下文压缩**,并能够无缝读取 OpenWebUI 已有的配置进行智能注入,让 Agent 能够具备以下能力:
|
||||
|
||||
- **🧠 智能意图识别**:Agent 能自主分析用户任务的深层意图,决定最有效的处理路径。
|
||||
- **🌐 自主网页搜索**:具备独立的网页搜索触发判断力,无需用户手动干预。
|
||||
- **♾️ 自动压缩上下文**:支持 Infinite Session,自动对长对话进行上下文压缩与摘要,确保长期任务跟进。
|
||||
- **🛠️ 全功能 Skill 体系**:完美支持本地自定义 Skill 目录,通过脚本与资源的结合实现真正的功能增强。
|
||||
- **🧩 深度生态复用**:直接复用您在 OpenWebUI 中配置的各种 **工具 (Tools)**、**MCP**、**OpenAPI Server** 和 **技能 (Skills)**。
|
||||
|
||||
为您带来更强、更完整的交互体验。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **核心伴侣组件**
|
||||
> 如需启用文件处理与数据分析能力,请务必安装 [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc24dd6)。
|
||||
>
|
||||
>## ✨ 0.9.0 核心更新:技能革命与稳定性加固
|
||||
|
||||
- **🧩 Copilot SDK Skills 原生支持**: 技能可作为一等上下文能力被加载和使用。
|
||||
- **🔄 OpenWebUI Skills 桥接**: 实现 OpenWebUI **工作区 > Skills** 与 SDK 技能目录的深度双向同步。
|
||||
- **🛠️ 确定性 `manage_skills` 工具**: 通过稳定工具契约完成技能的生命周期管理。
|
||||
- **🌊 状态栏逻辑加固**: 引入 `session_finalized` 多层锁定机制,彻底解决任务完成后状态栏回弹或卡死的问题。
|
||||
- **🗂️ 环境目录持久化**: 增强 `COPILOTSDK_CONFIG_DIR` 逻辑,确保会话状态跨容器重启稳定存在。
|
||||
- **🌐 持续化共享缓存(扩展)**: 技能统一存储在 `OPENWEBUI_SKILLS_SHARED_DIR/shared/`,跨会话与容器重启复用。
|
||||
- **🎯 智能意图路由(扩展)**: 自动识别技能管理请求并优先路由到 `manage_skills`,确保执行确定性。
|
||||
- **🗂️ 环境目录升级**: 新增 `COPILOTSDK_CONFIG_DIR`,并自动回退到 `/app/backend/data/.copilot`,确保 SDK 配置与会话状态在容器重启后稳定持久化。
|
||||
- **🧭 CLI 提示词护栏**: 系统提示词明确区分可执行的 **tools** 与不可调用的 **skills**,并要求技能生命周期操作优先走 `manage_skills`,同时强化 CLI/Python 执行规范。
|
||||
|
||||
> 如需启用文件处理与数据分析能力,请务必安装 [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)。
|
||||
> [!TIP]
|
||||
> **BYOK 模式无需订阅**
|
||||
> 如果您使用自带的 API Key (BYOK 模式对接 OpenAI/Anthropic),**您不需要 GitHub Copilot 官方订阅**。只有在访问 GitHub 官方模型时才需要订阅。
|
||||
|
||||
---
|
||||
|
||||
## ✨ 0.9.1 最新更新:自主网页搜索与可靠性修复
|
||||
|
||||
- **🌐 强化自主网页搜索**:`web_search` 工具现已强制对 Agent 开启(绕过 UI 网页搜索开关),充分利用 Copilot 自身具备的搜索判断能力。
|
||||
- **🛠️ 术语一致性优化**:全语种同步将“助手”更改为 **"Agent"**,并将“优化会话”统一为 **"压缩上下文"**,更准确地描述 Infinite Session 的技术本质。
|
||||
- **🌐 语言一致性**:内置指令确保 Agent 输出语言与用户输入严格对齐,提供无缝的国际化交互体验。
|
||||
- **🐛 修复 MCP 工具过滤逻辑**:解决了在管理员后端配置 `function_name_filter_list`(或在聊天界面勾选特定工具)时,因 ID 前缀(`server:mcp:`)识别逻辑错误导致工具意外失效的问题。
|
||||
- **🔍 提升过滤稳定性**:修复了工具 ID 归一化逻辑,确保点选的工具白名单在 SDK 会话中精确生效。
|
||||
|
||||
---
|
||||
|
||||
## ✨ 核心能力 (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 服务器。
|
||||
- **OpenAPI 桥接**: 将任何外部 REST API 一键转换为 Agent 可调用的工具。
|
||||
- **OpenWebUI 原生桥接**: 零配置接入现有的 OpenWebUI 工具及内置功能(网页搜索、记忆等)。
|
||||
- **🧩 OpenWebUI Skills 桥接**: 将简单的 OpenWebUI Markdown 指令转化为包含脚本、模板和数据的强大 SDK 技能文件夹。
|
||||
- **🧩 OpenWebUI Skills 桥接**: 将简单的 OpenWebUI Markdown 指令转化为包含脚本、模板 and 数据的强大 SDK 技能文件夹。
|
||||
- **♾️ 无限会话管理**: 先进的上下文窗口管理,支持自动“压缩”(摘要提取 + TODO 列表持久化)。支持长达数周的项目跟踪而不会丢失核心上下文。
|
||||
- **📊 交互式产物与发布**:
|
||||
- **实时 HTML/JS**: 瞬间渲染并交互 Agent 生成的应用程序、可视化看板或报告。
|
||||
- **持久化发布**: Agent 可将生成的产物(Excel, CSV, 文档)发布至 OpenWebUI 文件存储,并在聊天中提供永久下载链接。
|
||||
- **🌊 极致交互体验**: 完整支持深度思考过程 (Thinking Process) 流式渲染、状态指示器以及长任务实时进度条。
|
||||
- **🧠 深度数据库集成**: TOD·O 列表与会话元数据的实时持久化,确保任务执行状态在 UI 上清晰可见。
|
||||
- **🧠 深度数据库集成**: TODO 列表与会话元数据的实时持久化,确保任务执行状态在 UI 上清晰可见。
|
||||
|
||||
> [!TIP]
|
||||
> **💡 增强渲染建议**
|
||||
> 为了获得最精美的 **HTML Artifacts** 与 **RichUI** 效果,建议在对话中通过提供的 GitHub 链接直接命令 Agent 安装:
|
||||
> “请安装此技能:https://github.com/nicobailon/visual-explainer”。
|
||||
> 该技能专为生成高质量可视化组件而设计,能够与本 Pipe 完美协作。
|
||||
|
||||
---
|
||||
|
||||
@@ -49,162 +60,51 @@
|
||||
`GitHub Copilot SDK Files Filter` 是本 Pipe 的配套插件,用于阻止 OpenWebUI 默认 RAG 在 Pipe 接手前抢先处理上传文件。
|
||||
|
||||
- **作用**: 将上传文件移动到 `copilot_files`,让 Pipe 能直接读取原始二进制。
|
||||
- **必要性**: 若未安装,文件可能被提前解析/向量化,Agent 难以拿到原始文件。
|
||||
- **必要性**: 若未安装,文件可能被提前解析/向量化,Agent 拿到原始文件。
|
||||
- **v0.1.3 重点**:
|
||||
- 修复 BYOK 模型 ID 识别(支持 `github_copilot_official_sdk_pipe.xxx` 前缀匹配)。
|
||||
- 新增双通道调试日志(`show_debug_log`):后端 logger + 浏览器控制台。
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ 核心配置参数 (Valves)
|
||||
## 🚀 快速开始 (Quick Start)
|
||||
|
||||
### 1. 管理员配置 (基础设置)
|
||||
1. **安装本插件**: 在 OpenWebUI 管道管理界面添加并启用。
|
||||
2. **安装 [Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)** (必须): 以获得文件处理能力。
|
||||
3. **配置凭据**:
|
||||
- **官方模式**: 默认即可。确保环境中安装了 `github-copilot-sdk`。
|
||||
- **BYOK 模式**: 填入 OpenAI/Anthropic/DeepSeek 的 Base URL 与 Key。
|
||||
4. **选择模型**: 在聊天界面选择 `GitHub Copilot Official SDK Pipe` 系列模型。
|
||||
5. **开始对话**: 直接上传文件或发送复杂指令。
|
||||
|
||||
管理员可在函数设置中定义全局默认行为。
|
||||
---
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
## ⚙️ 配置参数 (Configuration Valves)
|
||||
|
||||
| 参数 | 默认值 | 描述 |
|
||||
| :--- | :--- | :--- |
|
||||
| `GH_TOKEN` | `""` | 全局 GitHub Token (需具备 'Copilot Requests' 权限)。 |
|
||||
| `COPILOTSDK_CONFIG_DIR` | `""` | SDK 配置与会话状态持久化目录 (例如: `/app/backend/data/.copilot`)。 |
|
||||
| `ENABLE_OPENWEBUI_TOOLS` | `True` | 启用 OpenWebUI 工具 (包括定义工具和内置工具)。 |
|
||||
| `ENABLE_OPENAPI_SERVER` | `True` | 启用 OpenAPI 工具服务器连接。 |
|
||||
| `ENABLE_MCP_SERVER` | `True` | 启用直接 MCP 客户端连接 (推荐)。 |
|
||||
| `ENABLE_OPENWEBUI_SKILLS` | `True` | 开启与 OpenWebUI **工作区 > Skills** 的双向同步桥接。 |
|
||||
| `OPENWEBUI_SKILLS_SHARED_DIR` | `/app/backend/data/cache/copilot-openwebui-skills` | OpenWebUI skills 转换后的共享缓存目录。 |
|
||||
| `GITHUB_SKILLS_SOURCE_URL` | `""` | 可选 GitHub tree 地址,用于批量导入 skills(例如 anthropic/skills)。 |
|
||||
| `DISABLED_SKILLS` | `""` | 逗号分隔的 skill 名称黑名单(如 `docs-writer,webapp-testing`)。 |
|
||||
| `REASONING_EFFORT` | `medium` | 推理强度:low, medium, high。 |
|
||||
| `SHOW_THINKING` | `True` | 显示模型推理/思考过程。 |
|
||||
| `INFINITE_SESSION` | `True` | 启用无限会话 (自动上下文压缩)。 |
|
||||
| `MAX_MULTIPLIER` | `1.0` | 最大允许的模型计费倍率 (0x 为仅限免费模型)。 |
|
||||
| `EXCLUDE_KEYWORDS` | `""` | 排除包含这些关键字的模型 (逗号分隔)。 |
|
||||
| `TIMEOUT` | `300` | 每个流数据块的超时时间 (秒)。 |
|
||||
| `BYOK_TYPE` | `openai` | BYOK 服务商类型:`openai`, `anthropic`。 |
|
||||
| `BYOK_BASE_URL` | `""` | BYOK 基础 URL (例如: <https://api.openai.com/v1)。> |
|
||||
| `BYOK_MODELS` | `""` | BYOK 模型列表 (逗号分隔)。留空则从 API 获取。 |
|
||||
| `CUSTOM_ENV_VARS` | `""` | 自定义环境变量 (JSON 格式)。 |
|
||||
| `DEBUG` | `False` | 开启此项以在前端控制台输出详细调试日志。 |
|
||||
|
||||
### 2. 用户配置 (个人覆盖)
|
||||
|
||||
普通用户可在各自的个人设置中根据需要覆盖以下参数。
|
||||
|
||||
| 参数 | 说明 |
|
||||
| :--- | :--- |
|
||||
| `GH_TOKEN` | 使用个人的 GitHub Token。 |
|
||||
| `REASONING_EFFORT` | 个人偏好的推理强度。 |
|
||||
| `SHOW_THINKING` | 显示模型推理/思考过程。 |
|
||||
| `MAX_MULTIPLIER` | 最大允许的模型计费倍率覆盖。 |
|
||||
| `EXCLUDE_KEYWORDS` | 排除包含这些关键字的模型。 |
|
||||
| `ENABLE_OPENWEBUI_SKILLS` | 启用将当前用户可读的全部已启用 OpenWebUI skills 转换并加载为 SDK `SKILL.md` 目录。 |
|
||||
| `GITHUB_SKILLS_SOURCE_URL` | 为当前用户会话设置可选 GitHub tree 地址以批量导入 skills。 |
|
||||
| `DISABLED_SKILLS` | 为当前用户会话禁用指定 skills(逗号分隔)。 |
|
||||
| `BYOK_API_KEY` | 使用个人的 OpenAI/Anthropic API Key。 |
|
||||
| `github_token` | - | GitHub Copilot 官方 Token (如果您有官方订阅且不方便本地登录时填入)。 |
|
||||
| `llm_base_url` | - | BYOK 模式的基础 URL。填入后将绕过 GitHub 官方服务。 |
|
||||
| `llm_api_key` | - | BYOK 模式的 API 密钥。 |
|
||||
| `llm_model_id` | `gpt-4o` | 使用的模型 ID (官方、BYOK 均适用)。 |
|
||||
| `workspace_root` | `./copilot_workspaces` | 所有会话沙盒的根目录。 |
|
||||
| `skills_directory` | `./copilot_skills` | 自定义 SDK 技能文件夹所在的目录。 |
|
||||
| `show_status` | `True` | 是否在 UI 显示 Agent 的实时运行状态和思考过程。 |
|
||||
| `enable_infinite_session` | `True` | 是否开启自动上下文压缩和 TODO 列表持久化。 |
|
||||
| `enable_html_artifacts` | `True` | 是否允许 Agent 生成并实时预览 HTML 应用。 |
|
||||
| `enable_rich_ui` | `True` | 是否启用进度条和增强型工具调用面板。 |
|
||||
|
||||
---
|
||||
|
||||
### 🌊 细粒度反馈与流畅体验 (Fluid UX)
|
||||
## 🤝 支持 (Support)
|
||||
|
||||
彻底告别复杂任务执行过程中的“卡顿”感:
|
||||
|
||||
- **🔄 实时状态气泡**: 将 SDK 内部事件(如 `turn_start`, `compaction`, `subagent_started`)直接映射为 OpenWebUI 的状态栏信息。
|
||||
- **🧭 分阶段状态描述增强**: 状态栏会明确显示处理阶段(处理中、技能触发、工具执行、工具完成/失败、发布中、任务完成)。
|
||||
- **⏱️ 长任务心跳提示**: 长时间处理中会周期性显示“仍在处理中(已耗时 X 秒)”,避免用户误判为卡死。
|
||||
- **📈 工具执行进度追踪**: 长耗时工具(如代码分析)会在状态栏实时显示进度百分比及当前子任务描述。
|
||||
- **⚡ 即时响应反馈**: 从响应开始第一秒即显示“助手正在处理您的请求...”,减少等待空窗感。
|
||||
如果这个插件对你有帮助,欢迎到 [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) 点个 Star,这将是我持续改进的动力,感谢支持。
|
||||
|
||||
---
|
||||
|
||||
### 🛡️ 智能版本兼容
|
||||
## ⚠️ 故障排除 (Troubleshooting)
|
||||
|
||||
插件会自动根据您的 OpenWebUI 版本调整功能集:
|
||||
|
||||
- **v0.8.0+**: 开启 Rich UI、实时状态气泡及集成 HTML 预览。
|
||||
- **旧版本**: 自动回退至标准 Markdown 代码块模式,确保最大稳定性。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 典型应用场景 (Use Cases)
|
||||
|
||||
- **📁 全自主仓库维护**: Agent 在隔离工作区内自动分析代码、运行测试并应用补丁。
|
||||
- **📊 深度财务数据审计**: 直接通过 Python 加载 Excel/CSV 原始数据(绕过 RAG),生成图表并实时预览。
|
||||
- **📝 长任务项目管理**: 自动拆解复杂任务并持久化 TOD·O 进度,跨会话跟踪执行状态。
|
||||
|
||||
---
|
||||
|
||||
## ⭐ 支持与交流 (Support)
|
||||
|
||||
如果这个插件对您有所帮助,请在 [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) 项目上点个 **Star** 💫,这是对我最大的鼓励。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 安装与配置 (Installation)
|
||||
|
||||
### 1) 导入函数
|
||||
|
||||
1. 打开 OpenWebUI,前往 **工作区** -> **函数**。
|
||||
2. 点击 **+** (创建函数),完整粘贴 `github_copilot_sdk.py` 的内容。
|
||||
3. 点击保存并确保已启用。
|
||||
|
||||
### 2) 获取 Token (Get Token)
|
||||
|
||||
1. 访问 [GitHub Token 设置](https://github.com/settings/tokens?type=beta)。
|
||||
2. 创建 **Fine-grained token**,授予 **Account permissions** -> **Copilot Requests** 访问权限。
|
||||
3. 将生成的 Token 填入插件的 `GH_TOKEN` 配置项中。
|
||||
|
||||
### 3) 认证配置要求(必填)
|
||||
|
||||
你必须至少配置以下一种凭据:
|
||||
|
||||
- `GH_TOKEN`(GitHub Copilot 官方订阅路径),或
|
||||
- `BYOK_API_KEY`(OpenAI/Anthropic 自带 Key 路径)。
|
||||
|
||||
如果两者都未配置,模型列表将不会出现。
|
||||
|
||||
### 4) 配套插件 (强烈推荐)
|
||||
|
||||
为了获得最佳的文件处理体验,请安装 [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)。
|
||||
|
||||
---
|
||||
|
||||
### 📤 增强型发布工具与交互式组件
|
||||
|
||||
`publish_file_from_workspace` 现采用更清晰、可落地的交付规范:
|
||||
|
||||
- **Artifacts 模式(`artifacts`,默认)**:返回 `[Preview]` + `[Download]`,并可附带 `html_embed`,在 ```html 代码块中直接渲染。
|
||||
- **Rich UI 模式(`richui`)**:仅返回 `[Preview]` + `[Download]`,由发射器自动触发集成式预览(消息中不输出 iframe 代码块)。
|
||||
- **📄 PDF 安全交付规则**:仅输出 Markdown 链接(可用时为 `[Preview]` + `[Download]`)。**禁止通过 iframe/html 方式嵌入 PDF。**
|
||||
- **⚡ 稳定双通道发布**:在本地与对象存储后端下,保持交互预览与持久下载链接一致可用。
|
||||
- **✅ 状态集成**:通过 OpenWebUI 状态栏实时反馈发布进度与完成状态。
|
||||
- **📘 发布工具指南(GitHub)**:[publish_file_from_workspace 工具指南(中文)](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/pipes/github-copilot-sdk/PUBLISH_FILE_FROM_WORKSPACE_CN.md)
|
||||
|
||||
---
|
||||
|
||||
### 🧩 OpenWebUI Skills 桥接与 `manage_skills` 工具
|
||||
|
||||
SDK 现在具备与 OpenWebUI **工作区 > Skills** 的双向同步能力:
|
||||
|
||||
- **🔄 自动同步**: 每次请求时,前端定义的技能会自动作为 `SKILL.md` 文件夹同步至 SDK 共享缓存,Agent 可直接调用。
|
||||
- **🛠️ `manage_skills` 工具**: 内置专业工具,赋予 Agent (或用户) 绝对的技能管理权。
|
||||
- `list`: 列出所有已安装技能及描述。
|
||||
- `install`: 从 GitHub URL (自动转换归档链接) 或直接从 `.zip`/`.tar.gz` 安装。
|
||||
- `create`: 从当前会话内容创建新技能目录,支持写入 `SKILL.md` 及辅助资源文件 (脚本、模板)。
|
||||
- `edit`: 更新现有技能文件夹。
|
||||
- `delete`: 原子化删除本地目录及关联的数据库条目,防止僵尸技能复活。
|
||||
- **📁 完整的文件夹支持**: 不同于数据库中单文件存储,SDK 会加载技能的**整个目录**。这使得技能可以携带二进制脚本、数据文件或复杂模板。
|
||||
- **🌐 持久化共享缓存**: 技能存储在 `OPENWEBUI_SKILLS_SHARED_DIR/shared/`,跨会话及容器重启持久存在。
|
||||
- **📚 技能完整文档(GitHub)**: [manage_skills 工具指南(中文)](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/pipes/github-copilot-sdk/SKILLS_MANAGER_CN.md) | [Skills Best Practices(中文)](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/pipes/github-copilot-sdk/SKILLS_BEST_PRACTICES_CN.md)
|
||||
|
||||
---
|
||||
|
||||
## 📋 常见问题与依赖 (Troubleshooting)
|
||||
|
||||
- **Agent 无法识别文件?**: 请确保已安装并启用了 Files Filter 插件,否则原始文件会被 RAG 干扰。
|
||||
- **看不到状态更新或 TODO 进度条?**: 状态气泡会覆盖处理/工具阶段;而 TODO 进度条仅在 Agent 使用 `update_todo` 工具(通常是复杂任务)时出现。
|
||||
- **依赖安装**: 本管道会自动管理 `github-copilot-sdk` (Python 包) 并优先直接使用内置的二进制 CLI,无需手动干预。
|
||||
|
||||
---
|
||||
|
||||
## 更新日志 (Changelog)
|
||||
|
||||
完整历史记录请见 GitHub: [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)
|
||||
- **工具无法使用?** 请检查是否安装了 `github-copilot-sdk`。
|
||||
- **文件找不到?** 确保已启用配套的 `Files Filter` 插件。
|
||||
- **BYOK 报错?** 确认 `llm_base_url` 包含协议前缀(如 `https://`)且模型 ID 准确无误。
|
||||
- **卡在 "Thinking..."?** 检查后端网络连接,流式传输可能受某些代理拦截。
|
||||
|
||||
@@ -15,7 +15,7 @@ Pipes allow you to:
|
||||
|
||||
## Available Pipe Plugins
|
||||
|
||||
- [GitHub Copilot SDK](github-copilot-sdk.md) (v0.9.0) - Official GitHub Copilot SDK integration. Features **Workspace Isolation**, **Zero-config OpenWebUI Tool Bridge**, **BYOK** support, and **dynamic MCP discovery**. **NEW in v0.9.0: OpenWebUI Skills Bridge**, reinforced status bar stability, and persistent SDK config management. [View Deep Dive](github-copilot-sdk-deep-dive.md) | [**View Advanced Tutorial**](github-copilot-sdk-tutorial.md) | [**View Detailed Usage Guide**](github-copilot-sdk-usage-guide.md).
|
||||
- [GitHub Copilot SDK](github-copilot-sdk.md) (v0.9.1) - Official GitHub Copilot SDK integration. Features **Workspace Isolation**, **Zero-config OpenWebUI Tool Bridge**, **BYOK** support, and **dynamic MCP discovery**. **NEW in v0.9.1: MCP filter reliability fix** for `server:mcp:{id}` chat selection and function filter consistency. [View Deep Dive](github-copilot-sdk-deep-dive.md) | [**View Advanced Tutorial**](github-copilot-sdk-tutorial.md) | [**View Detailed Usage Guide**](github-copilot-sdk-usage-guide.md).
|
||||
- **[Case Study: GitHub 100 Star Growth Analysis](star-prediction-example.md)** - Learn how to use the GitHub Copilot SDK Pipe with Minimax 2.1 to automatically analyze CSV data and generate project growth reports.
|
||||
- **[Case Study: High-Quality Video to GIF Conversion](video-processing-example.md)** - See how the model uses system-level FFmpeg to accelerate, scale, and optimize colors for screen recordings.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ Pipes 可以用于:
|
||||
|
||||
## 可用的 Pipe 插件
|
||||
|
||||
- [GitHub Copilot SDK](github-copilot-sdk.zh.md) (v0.9.0) - GitHub Copilot SDK 官方集成。具备**工作区安全隔离**、**零配置工具桥接**与**BYOK (自带 Key) 支持**。**v0.9.0 重量级更新:OpenWebUI Skills 桥接**、状态栏稳定性加固,以及持久化 SDK 配置目录管理(`COPILOTSDK_CONFIG_DIR`)。[查看深度架构解析](github-copilot-sdk-deep-dive.zh.md) | [**查看进阶实战教程**](github-copilot-sdk-tutorial.zh.md) | [**查看详细使用手册**](github-copilot-sdk-usage-guide.zh.md)。
|
||||
- [GitHub Copilot SDK](github-copilot-sdk.zh.md) (v0.9.1) - GitHub Copilot SDK 官方集成。具备**工作区安全隔离**、**零配置工具桥接**与**BYOK (自带 Key) 支持**。**v0.9.1 更新:MCP 过滤可靠性修复**,修正 `server:mcp:{id}` 聊天选择匹配并提升函数过滤一致性。[查看深度架构解析](github-copilot-sdk-deep-dive.zh.md) | [**查看进阶实战教程**](github-copilot-sdk-tutorial.zh.md) | [**查看详细使用手册**](github-copilot-sdk-usage-guide.zh.md)。
|
||||
- **[实战案例:GitHub 100 Star 增长预测](star-prediction-example.zh.md)** - 展示如何使用 GitHub Copilot SDK Pipe 结合 Minimax 2.1 模型,自动编写脚本分析 CSV 数据并生成详细的项目增长报告。
|
||||
- **[实战案例:视频高质量 GIF 转换与加速](video-processing-example.zh.md)** - 演示模型如何通过底层 FFmpeg 工具对录屏进行加速、缩放及双阶段色彩优化处理。
|
||||
|
||||
|
||||
@@ -27,6 +27,10 @@ A standalone OpenWebUI Tool plugin for managing native Workspace Skills across m
|
||||
## Installation
|
||||
|
||||
1. Open OpenWebUI → Workspace → Tools
|
||||
2. Create Tool and paste:
|
||||
- `plugins/tools/openwebui-skills-manager/openwebui_skills_manager.py`
|
||||
2. Install **OpenWebUI Skills Manager Tool** from the official marketplace
|
||||
3. Save and enable for your chat/model
|
||||
|
||||
### Manual Installation (Alternative)
|
||||
|
||||
- Create Tool and paste:
|
||||
- `plugins/tools/openwebui-skills-manager/openwebui_skills_manager.py`
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
## 安装方式
|
||||
|
||||
1. 打开 OpenWebUI → Workspace → Tools
|
||||
2. 新建 Tool 并粘贴:
|
||||
- `plugins/tools/openwebui-skills-manager/openwebui_skills_manager.py`
|
||||
2. 在官方市场安装 **OpenWebUI Skills 管理工具**
|
||||
3. 保存并在模型/聊天中启用
|
||||
|
||||
### 手动安装(备选)
|
||||
|
||||
- 新建 Tool 并粘贴:
|
||||
- `plugins/tools/openwebui-skills-manager/openwebui_skills_manager.py`
|
||||
|
||||
138
plugins/debug/copilot-sdk/USAGE_CN.md
Normal file
138
plugins/debug/copilot-sdk/USAGE_CN.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# Copilot SDK 自动任务脚本使用说明
|
||||
|
||||
本目录提供了一个通用任务执行脚本,以及两个示例任务脚本:
|
||||
- `auto_programming_task.py`(通用)
|
||||
- `run_mindmap_action_to_tool.sh`(示例:mind map action → tool)
|
||||
- `run_infographic_action_to_tool.sh`(示例:infographic action → tool)
|
||||
|
||||
## 1. 先决条件
|
||||
|
||||
- 在仓库根目录执行(非常重要)
|
||||
- Python 3 可用
|
||||
- 当前环境中可正常使用 Copilot SDK / CLI
|
||||
|
||||
建议先验证:
|
||||
|
||||
python3 plugins/debug/copilot-sdk/auto_programming_task.py --help | head -40
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心行为(当前默认)
|
||||
|
||||
`auto_programming_task.py` 默认是 **两阶段自动执行**:
|
||||
|
||||
1) 先规划(Planning):AI 根据你的需求自动补全上下文、扩展为可执行计划。
|
||||
2) 再执行(Execution):AI 按计划直接改代码并给出结果。
|
||||
|
||||
如果你要关闭“先规划”,可使用 `--no-plan-first`。
|
||||
|
||||
---
|
||||
|
||||
## 3. 可复制命令(通用)
|
||||
|
||||
### 3.1 最常用:直接写任务文本
|
||||
|
||||
python3 plugins/debug/copilot-sdk/auto_programming_task.py \
|
||||
--task "把 plugins/actions/xxx/xxx.py 转成 plugins/tools/xxx-tool/ 下的单文件 Tool 插件。保留 i18n 和语言回退逻辑。不要升级 SDK 版本。" \
|
||||
--cwd "$PWD" \
|
||||
--model "gpt-5.3-codex" \
|
||||
--reasoning-effort "xhigh" \
|
||||
--timeout 3600 \
|
||||
--stream \
|
||||
--trace-events \
|
||||
--heartbeat-seconds 8
|
||||
|
||||
### 3.2 使用任务文件(长任务推荐)
|
||||
|
||||
先写任务文件(例如 task.txt),再执行:
|
||||
|
||||
python3 plugins/debug/copilot-sdk/auto_programming_task.py \
|
||||
--task-file "./task.txt" \
|
||||
--cwd "$PWD" \
|
||||
--model "gpt-5.3-codex" \
|
||||
--reasoning-effort "xhigh" \
|
||||
--timeout 3600 \
|
||||
--stream \
|
||||
--trace-events \
|
||||
--heartbeat-seconds 8
|
||||
|
||||
### 3.3 关闭规划阶段(仅直接执行)
|
||||
|
||||
python3 plugins/debug/copilot-sdk/auto_programming_task.py \
|
||||
--task "你的任务" \
|
||||
--cwd "$PWD" \
|
||||
--model "gpt-5-mini" \
|
||||
--reasoning-effort "medium" \
|
||||
--timeout 1800 \
|
||||
--no-plan-first
|
||||
|
||||
---
|
||||
|
||||
## 4. 可复制命令(示例脚本)
|
||||
|
||||
### 4.1 Mind Map 示例任务
|
||||
|
||||
./plugins/debug/copilot-sdk/run_mindmap_action_to_tool.sh
|
||||
|
||||
### 4.2 Infographic 示例任务
|
||||
|
||||
./plugins/debug/copilot-sdk/run_infographic_action_to_tool.sh
|
||||
|
||||
说明:这两个脚本是“固定任务模板”,适合当前仓库;复制到其他仓库时通常需要改任务内容。
|
||||
|
||||
---
|
||||
|
||||
## 5. 结果如何判定“完成”
|
||||
|
||||
建议同时满足以下条件:
|
||||
|
||||
1) 进程退出码为 0
|
||||
2) 输出中出现阶段结束信息(含最终摘要)
|
||||
3) 看到 `session.idle`(若是 `session.error` 则未完成)
|
||||
4) `git diff --name-only` 显示改动范围符合你的约束
|
||||
|
||||
可复制检查命令:
|
||||
|
||||
echo $?
|
||||
git diff --name-only
|
||||
git status --short
|
||||
|
||||
---
|
||||
|
||||
## 6. 参数速查
|
||||
|
||||
- `--task`:直接传任务文本
|
||||
- `--task-file`:从文件读取任务文本(与 `--task` 二选一)
|
||||
- `--cwd`:工作区目录(建议用 `$PWD`)
|
||||
- `--model`:模型(例如 `gpt-5.3-codex`、`gpt-5-mini`)
|
||||
- `--reasoning-effort`:`low|medium|high|xhigh`
|
||||
- `--timeout`:超时秒数
|
||||
- `--stream`:实时输出增量内容
|
||||
- `--trace-events`:输出事件流,便于排错
|
||||
- `--heartbeat-seconds`:心跳输出间隔
|
||||
- `--no-plan-first`:关闭默认“先规划后执行”
|
||||
|
||||
---
|
||||
|
||||
## 7. 常见问题
|
||||
|
||||
### Q1:为什么提示找不到脚本?
|
||||
你大概率不在仓库根目录。先执行:
|
||||
|
||||
pwd
|
||||
|
||||
确认后再运行命令。
|
||||
|
||||
### Q2:执行很久没有输出?
|
||||
加上 `--trace-events --stream`,并适当增大 `--timeout`。
|
||||
|
||||
### Q3:改动超出预期范围?
|
||||
把范围约束明确写进任务文本,例如:
|
||||
|
||||
“不要修改其他文件代码,可以读取整个项目作为代码库。”
|
||||
|
||||
并在完成后用:
|
||||
|
||||
git diff --name-only
|
||||
|
||||
进行核对。
|
||||
447
plugins/debug/copilot-sdk/auto_programming_task.py
Normal file
447
plugins/debug/copilot-sdk/auto_programming_task.py
Normal file
@@ -0,0 +1,447 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Run an autonomous programming task via Copilot SDK.
|
||||
|
||||
Usage:
|
||||
python plugins/debug/copilot-sdk/auto_programming_task.py \
|
||||
--task "Fix failing tests in tests/test_xxx.py" \
|
||||
--cwd /Users/fujie/app/python/oui/openwebui-extensions
|
||||
|
||||
Notes:
|
||||
- Default model is gpt-5-mini (low-cost for repeated runs).
|
||||
- This script DOES NOT pin/upgrade SDK versions.
|
||||
- Copilot CLI must be available (or set COPILOT_CLI_PATH).
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
||||
DEFAULT_TASK = (
|
||||
"Convert plugins/actions/smart-mind-map/smart_mind_map.py (Action plugin) "
|
||||
"into a Tool plugin implementation under plugins/tools/. "
|
||||
"Keep Copilot SDK version unchanged, follow patterns from "
|
||||
"plugins/pipes/github-copilot-sdk/, and implement a runnable MVP with "
|
||||
"i18n/status events/basic validation."
|
||||
)
|
||||
|
||||
|
||||
def _ensure_copilot_importable() -> None:
|
||||
"""Try local SDK path fallback if `copilot` package is not installed."""
|
||||
try:
|
||||
import copilot # noqa: F401
|
||||
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
candidates = []
|
||||
|
||||
env_path = os.environ.get("COPILOT_SDK_PYTHON_PATH", "").strip()
|
||||
if env_path:
|
||||
candidates.append(Path(env_path))
|
||||
|
||||
# Default sibling repo path: ../copilot-sdk/python
|
||||
# Current file: plugins/debug/copilot-sdk/auto_programming_task.py
|
||||
repo_root = Path(__file__).resolve().parents[3]
|
||||
candidates.append(repo_root.parent / "copilot-sdk" / "python")
|
||||
|
||||
for path in candidates:
|
||||
if path.exists():
|
||||
sys.path.insert(0, str(path))
|
||||
try:
|
||||
import copilot # noqa: F401
|
||||
|
||||
return
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
raise RuntimeError(
|
||||
"Cannot import `copilot` package. Install copilot-sdk python package "
|
||||
"or set COPILOT_SDK_PYTHON_PATH to copilot-sdk/python directory."
|
||||
)
|
||||
|
||||
|
||||
def _build_agent_prompt(task: str, cwd: str, extra_context: Optional[str]) -> str:
|
||||
extra = extra_context.strip() if extra_context else ""
|
||||
return textwrap.dedent(
|
||||
f"""
|
||||
You are an autonomous coding agent running in workspace: {cwd}
|
||||
|
||||
Primary task:
|
||||
{task}
|
||||
|
||||
Requirements:
|
||||
1. Inspect relevant files and implement changes directly in the workspace.
|
||||
2. Keep changes minimal and focused on the task.
|
||||
3. If tests/build commands exist, run targeted validation for changed scope.
|
||||
4. If blocked, explain the blocker and propose concrete next steps.
|
||||
5. At the end, provide a concise summary of:
|
||||
- files changed
|
||||
- what was implemented
|
||||
- validation results
|
||||
|
||||
{f'Additional context:\n{extra}' if extra else ''}
|
||||
"""
|
||||
).strip()
|
||||
|
||||
|
||||
def _build_planning_prompt(task: str, cwd: str, extra_context: Optional[str]) -> str:
|
||||
extra = extra_context.strip() if extra_context else ""
|
||||
return textwrap.dedent(
|
||||
f"""
|
||||
You are a senior autonomous coding planner running in workspace: {cwd}
|
||||
|
||||
User requirement (may be underspecified):
|
||||
{task}
|
||||
|
||||
Goal:
|
||||
Expand the requirement into an actionable implementation plan that can be executed end-to-end without extra clarification whenever possible.
|
||||
|
||||
Output format (strict):
|
||||
1) Expanded Objective (clear, concrete, scoped)
|
||||
2) Assumptions (only necessary assumptions)
|
||||
3) Step-by-step Plan (ordered, verifiable)
|
||||
4) Validation Plan (how to verify changes)
|
||||
5) Execution Brief (concise instruction for implementation agent)
|
||||
|
||||
Constraints:
|
||||
- Keep scope minimal and aligned with the user requirement.
|
||||
- Do not invent unrelated features.
|
||||
- Prefer practical MVP completion.
|
||||
|
||||
{f'Additional context:\n{extra}' if extra else ''}
|
||||
"""
|
||||
).strip()
|
||||
|
||||
|
||||
def _build_execution_prompt(
|
||||
task: str, cwd: str, extra_context: Optional[str], plan_text: str
|
||||
) -> str:
|
||||
extra = extra_context.strip() if extra_context else ""
|
||||
return textwrap.dedent(
|
||||
f"""
|
||||
You are an autonomous coding agent running in workspace: {cwd}
|
||||
|
||||
User requirement:
|
||||
{task}
|
||||
|
||||
Planner output (must follow):
|
||||
{plan_text}
|
||||
|
||||
Execution requirements:
|
||||
1. Execute the plan directly; do not stop after analysis.
|
||||
2. If the original requirement is underspecified, use the planner assumptions and continue.
|
||||
3. Keep changes minimal, focused, and runnable.
|
||||
4. Run targeted validation for changed scope where possible.
|
||||
5. If blocked by missing prerequisites, report blocker and the smallest next action.
|
||||
6. Finish with concise summary:
|
||||
- files changed
|
||||
- implemented behavior
|
||||
- validation results
|
||||
|
||||
{f'Additional context:\n{extra}' if extra else ''}
|
||||
"""
|
||||
).strip()
|
||||
|
||||
|
||||
async def _run_single_session(
|
||||
client,
|
||||
args: argparse.Namespace,
|
||||
prompt: str,
|
||||
stage_name: str,
|
||||
stream_output: bool,
|
||||
) -> tuple[int, str]:
|
||||
from copilot.types import PermissionHandler
|
||||
|
||||
def _auto_user_input_handler(request, _invocation):
|
||||
question = ""
|
||||
if isinstance(request, dict):
|
||||
question = str(request.get("question", "")).lower()
|
||||
choices = request.get("choices") or []
|
||||
if choices and isinstance(choices, list):
|
||||
preferred = args.auto_user_answer.strip()
|
||||
for choice in choices:
|
||||
c = str(choice)
|
||||
if preferred and preferred.lower() == c.lower():
|
||||
return {"answer": c, "wasFreeform": False}
|
||||
return {"answer": str(choices[0]), "wasFreeform": False}
|
||||
|
||||
preferred = args.auto_user_answer.strip() or "continue"
|
||||
if "confirm" in question or "proceed" in question:
|
||||
preferred = "yes"
|
||||
return {"answer": preferred, "wasFreeform": True}
|
||||
|
||||
session_config = {
|
||||
"model": args.model,
|
||||
"reasoning_effort": args.reasoning_effort,
|
||||
"streaming": True,
|
||||
"infinite_sessions": {
|
||||
"enabled": True,
|
||||
},
|
||||
"on_permission_request": PermissionHandler.approve_all,
|
||||
"on_user_input_request": _auto_user_input_handler,
|
||||
}
|
||||
|
||||
session = await client.create_session(session_config)
|
||||
|
||||
done = asyncio.Event()
|
||||
full_messages = []
|
||||
has_error = False
|
||||
|
||||
def on_event(event):
|
||||
nonlocal has_error
|
||||
etype = getattr(event, "type", "unknown")
|
||||
if hasattr(etype, "value"):
|
||||
etype = etype.value
|
||||
|
||||
if args.trace_events:
|
||||
print(f"\n[{stage_name}][EVENT] {etype}", flush=True)
|
||||
|
||||
if etype == "assistant.message_delta" and stream_output:
|
||||
delta = getattr(event.data, "delta_content", "") or ""
|
||||
if delta:
|
||||
print(delta, end="", flush=True)
|
||||
|
||||
elif etype == "assistant.message":
|
||||
content = getattr(event.data, "content", "") or ""
|
||||
if content:
|
||||
full_messages.append(content)
|
||||
|
||||
elif etype == "session.error":
|
||||
has_error = True
|
||||
done.set()
|
||||
elif etype == "session.idle":
|
||||
done.set()
|
||||
|
||||
unsubscribe = session.on(on_event)
|
||||
heartbeat_task = None
|
||||
|
||||
async def _heartbeat():
|
||||
while not done.is_set():
|
||||
await asyncio.sleep(max(3, int(args.heartbeat_seconds)))
|
||||
if not done.is_set():
|
||||
print(
|
||||
f"[{stage_name}][heartbeat] waiting for assistant events...",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
try:
|
||||
heartbeat_task = asyncio.create_task(_heartbeat())
|
||||
await session.send({"prompt": prompt, "mode": "immediate"})
|
||||
await asyncio.wait_for(done.wait(), timeout=args.timeout)
|
||||
|
||||
if stream_output:
|
||||
print("\n")
|
||||
|
||||
final_message = full_messages[-1] if full_messages else ""
|
||||
if final_message:
|
||||
print(f"\n===== {stage_name} FINAL MESSAGE =====\n")
|
||||
print(final_message)
|
||||
|
||||
if has_error:
|
||||
return 1, final_message
|
||||
return 0, final_message
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
print(f"\n❌ [{stage_name}] Timeout after {args.timeout}s")
|
||||
return 124, ""
|
||||
except Exception as exc:
|
||||
print(f"\n❌ [{stage_name}] Run failed: {exc}")
|
||||
return 1, ""
|
||||
finally:
|
||||
if heartbeat_task:
|
||||
heartbeat_task.cancel()
|
||||
try:
|
||||
unsubscribe()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
await session.destroy()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
async def run_task(args: argparse.Namespace) -> int:
|
||||
_ensure_copilot_importable()
|
||||
|
||||
from copilot import CopilotClient
|
||||
|
||||
task_text = (args.task or "").strip()
|
||||
if args.task_file:
|
||||
task_text = Path(args.task_file).read_text(encoding="utf-8").strip()
|
||||
|
||||
if not task_text:
|
||||
task_text = DEFAULT_TASK
|
||||
|
||||
direct_prompt = _build_agent_prompt(task_text, args.cwd, args.extra_context)
|
||||
|
||||
client_options = {
|
||||
"cwd": args.cwd,
|
||||
"log_level": args.log_level,
|
||||
}
|
||||
|
||||
if args.cli_path:
|
||||
client_options["cli_path"] = args.cli_path
|
||||
|
||||
if args.github_token:
|
||||
client_options["github_token"] = args.github_token
|
||||
|
||||
print(f"🚀 Starting Copilot SDK task runner")
|
||||
print(f" cwd: {args.cwd}")
|
||||
print(f" model: {args.model}")
|
||||
print(f" reasoning_effort: {args.reasoning_effort}")
|
||||
print(f" plan_first: {args.plan_first}")
|
||||
|
||||
client = CopilotClient(client_options)
|
||||
await client.start()
|
||||
|
||||
try:
|
||||
if args.plan_first:
|
||||
planning_prompt = _build_planning_prompt(
|
||||
task_text, args.cwd, args.extra_context
|
||||
)
|
||||
print("\n🧭 Stage 1/2: Planning and requirement expansion")
|
||||
plan_code, plan_text = await _run_single_session(
|
||||
client=client,
|
||||
args=args,
|
||||
prompt=planning_prompt,
|
||||
stage_name="PLANNING",
|
||||
stream_output=False,
|
||||
)
|
||||
if plan_code != 0:
|
||||
return plan_code
|
||||
|
||||
execution_prompt = _build_execution_prompt(
|
||||
task=task_text,
|
||||
cwd=args.cwd,
|
||||
extra_context=args.extra_context,
|
||||
plan_text=plan_text or "(No planner output provided)",
|
||||
)
|
||||
print("\n⚙️ Stage 2/2: Execute plan autonomously")
|
||||
exec_code, _ = await _run_single_session(
|
||||
client=client,
|
||||
args=args,
|
||||
prompt=execution_prompt,
|
||||
stage_name="EXECUTION",
|
||||
stream_output=args.stream,
|
||||
)
|
||||
return exec_code
|
||||
|
||||
print("\n⚙️ Direct mode: Execute task without planning stage")
|
||||
exec_code, _ = await _run_single_session(
|
||||
client=client,
|
||||
args=args,
|
||||
prompt=direct_prompt,
|
||||
stage_name="EXECUTION",
|
||||
stream_output=args.stream,
|
||||
)
|
||||
return exec_code
|
||||
finally:
|
||||
try:
|
||||
await client.stop()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run one autonomous programming task with Copilot SDK"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--task",
|
||||
default="",
|
||||
help="Task description text (if empty, uses built-in default task)",
|
||||
)
|
||||
parser.add_argument("--task-file", default="", help="Path to a task text file")
|
||||
parser.add_argument("--cwd", default=os.getcwd(), help="Workspace directory")
|
||||
parser.add_argument(
|
||||
"--model",
|
||||
default="gpt-5-mini",
|
||||
help="Model id for Copilot session (default: gpt-5-mini)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--reasoning-effort",
|
||||
default="medium",
|
||||
choices=["low", "medium", "high", "xhigh"],
|
||||
help="Reasoning effort",
|
||||
)
|
||||
parser.add_argument("--timeout", type=int, default=1800, help="Timeout seconds")
|
||||
parser.add_argument(
|
||||
"--log-level",
|
||||
default="info",
|
||||
choices=["trace", "debug", "info", "warn", "error"],
|
||||
help="Copilot client log level",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--github-token",
|
||||
default=os.environ.get("GH_TOKEN", ""),
|
||||
help="Optional GitHub token; defaults to GH_TOKEN",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cli-path",
|
||||
default=os.environ.get("COPILOT_CLI_PATH", ""),
|
||||
help="Optional Copilot CLI path",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--extra-context",
|
||||
default="",
|
||||
help="Optional extra context appended to the task prompt",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stream",
|
||||
action="store_true",
|
||||
help="Print assistant delta stream in real-time",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--trace-events",
|
||||
action="store_true",
|
||||
help="Print each SDK event type for debugging",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--auto-user-answer",
|
||||
default="continue",
|
||||
help="Default answer for on_user_input_request",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--heartbeat-seconds",
|
||||
type=int,
|
||||
default=12,
|
||||
help="Heartbeat interval while waiting for events",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--plan-first",
|
||||
action="store_true",
|
||||
help="Run planning stage before execution (default behavior)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-plan-first",
|
||||
action="store_true",
|
||||
help="Disable planning stage and run direct execution",
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.task_file and (args.task or "").strip():
|
||||
parser.error("Use either --task or --task-file, not both")
|
||||
|
||||
args.plan_first = True
|
||||
if args.no_plan_first:
|
||||
args.plan_first = False
|
||||
elif args.plan_first:
|
||||
args.plan_first = True
|
||||
|
||||
return asyncio.run(run_task(args))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
137
plugins/debug/copilot-sdk/run_owui_api_docs_phases.sh
Executable file
137
plugins/debug/copilot-sdk/run_owui_api_docs_phases.sh
Executable file
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env bash
|
||||
# run_owui_api_docs_phases.sh
|
||||
# One-click runner: generate OpenWebUI API documentation across 8 phases.
|
||||
#
|
||||
# Usage:
|
||||
# ./plugins/debug/copilot-sdk/run_owui_api_docs_phases.sh
|
||||
# ./plugins/debug/copilot-sdk/run_owui_api_docs_phases.sh --start-phase 3
|
||||
# ./plugins/debug/copilot-sdk/run_owui_api_docs_phases.sh --only-phase 1
|
||||
#
|
||||
# Working directory: /Users/fujie/app/python/oui/open-webui (open-webui source)
|
||||
# Task files: plugins/debug/copilot-sdk/tasks/owui-api-docs/phases/
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Resolve paths ────────────────────────────────────────────────────────────
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" # openwebui-extensions root
|
||||
TASKS_DIR="${SCRIPT_DIR}/tasks/owui-api-docs/phases"
|
||||
TARGET_CWD="/Users/fujie/app/python/oui/open-webui" # source repo to scan
|
||||
RUNNER="${SCRIPT_DIR}/auto_programming_task.py"
|
||||
PYTHON="${PYTHON:-python3}"
|
||||
|
||||
# ── Arguments ────────────────────────────────────────────────────────────────
|
||||
START_PHASE=1
|
||||
ONLY_PHASE=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--start-phase)
|
||||
START_PHASE="$2"; shift 2 ;;
|
||||
--only-phase)
|
||||
ONLY_PHASE="$2"; shift 2 ;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ── Phase definitions ─────────────────────────────────────────────────────────
|
||||
declare -a PHASE_FILES=(
|
||||
"01_route_index.txt"
|
||||
"02_auth_users_groups_models.txt"
|
||||
"03_chats_channels_memories_notes.txt"
|
||||
"04_files_folders_knowledge_retrieval.txt"
|
||||
"05_ollama_openai_audio_images.txt"
|
||||
"06_tools_functions_pipelines_skills_tasks.txt"
|
||||
"07_configs_prompts_evaluations_analytics_scim_utils.txt"
|
||||
"08_consolidation_index.txt"
|
||||
)
|
||||
|
||||
declare -a PHASE_LABELS=(
|
||||
"Route Index (master table)"
|
||||
"Auth / Users / Groups / Models"
|
||||
"Chats / Channels / Memories / Notes"
|
||||
"Files / Folders / Knowledge / Retrieval"
|
||||
"Ollama / OpenAI / Audio / Images"
|
||||
"Tools / Functions / Pipelines / Skills / Tasks"
|
||||
"Configs / Prompts / Evaluations / Analytics / SCIM / Utils"
|
||||
"Consolidation — README + JSON"
|
||||
)
|
||||
|
||||
# ── Pre-flight checks ─────────────────────────────────────────────────────────
|
||||
echo "============================================================"
|
||||
echo " OpenWebUI API Docs — Phase Runner"
|
||||
echo "============================================================"
|
||||
echo " Source (--cwd): ${TARGET_CWD}"
|
||||
echo " Task files: ${TASKS_DIR}"
|
||||
echo " Runner: ${RUNNER}"
|
||||
echo ""
|
||||
|
||||
if [[ ! -d "${TARGET_CWD}" ]]; then
|
||||
echo "ERROR: Target source directory not found: ${TARGET_CWD}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "${RUNNER}" ]]; then
|
||||
echo "ERROR: Runner script not found: ${RUNNER}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Run phases ────────────────────────────────────────────────────────────────
|
||||
TOTAL=${#PHASE_FILES[@]}
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
|
||||
for i in "${!PHASE_FILES[@]}"; do
|
||||
PHASE_NUM=$((i + 1))
|
||||
TASK_FILE="${TASKS_DIR}/${PHASE_FILES[$i]}"
|
||||
LABEL="${PHASE_LABELS[$i]}"
|
||||
|
||||
# --only-phase filter
|
||||
if [[ -n "${ONLY_PHASE}" && "${PHASE_NUM}" != "${ONLY_PHASE}" ]]; then
|
||||
echo " [SKIP] Phase ${PHASE_NUM}: ${LABEL}"
|
||||
continue
|
||||
fi
|
||||
|
||||
# --start-phase filter
|
||||
if [[ "${PHASE_NUM}" -lt "${START_PHASE}" ]]; then
|
||||
echo " [SKIP] Phase ${PHASE_NUM}: ${LABEL} (before start phase)"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ ! -f "${TASK_FILE}" ]]; then
|
||||
echo " [ERROR] Task file not found: ${TASK_FILE}" >&2
|
||||
FAILED=$((FAILED + 1))
|
||||
break
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "──────────────────────────────────────────────────────────"
|
||||
echo " Phase ${PHASE_NUM}/${TOTAL}: ${LABEL}"
|
||||
echo " Task file: ${PHASE_FILES[$i]}"
|
||||
echo "──────────────────────────────────────────────────────────"
|
||||
|
||||
if "${PYTHON}" "${RUNNER}" \
|
||||
--task-file "${TASK_FILE}" \
|
||||
--cwd "${TARGET_CWD}" \
|
||||
--model "claude-sonnet-4.6" \
|
||||
--reasoning-effort high \
|
||||
--no-plan-first; then
|
||||
echo " ✓ Phase ${PHASE_NUM} completed successfully."
|
||||
PASSED=$((PASSED + 1))
|
||||
else
|
||||
EXIT_CODE=$?
|
||||
echo ""
|
||||
echo " ✗ Phase ${PHASE_NUM} FAILED (exit code: ${EXIT_CODE})." >&2
|
||||
echo " Fix the issue and re-run with: --start-phase ${PHASE_NUM}" >&2
|
||||
FAILED=$((FAILED + 1))
|
||||
exit "${EXIT_CODE}"
|
||||
fi
|
||||
done
|
||||
|
||||
# ── Summary ──────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo " Run complete: ${PASSED} passed, ${FAILED} failed"
|
||||
echo " Output: ${TARGET_CWD}/api_docs/"
|
||||
echo "============================================================"
|
||||
@@ -0,0 +1,74 @@
|
||||
# OpenWebUI API Documentation — Phase Run Order
|
||||
|
||||
## Overview
|
||||
|
||||
This task set reads the OpenWebUI backend source code and generates a complete
|
||||
API reference in `api_docs/` inside the open-webui repository.
|
||||
|
||||
**Source repo:** `/Users/fujie/app/python/oui/open-webui`
|
||||
**Output directory:** `/Users/fujie/app/python/oui/open-webui/api_docs/`
|
||||
**Task files dir:** `plugins/debug/copilot-sdk/tasks/owui-api-docs/phases/`
|
||||
|
||||
---
|
||||
|
||||
## Phase Execution Order
|
||||
|
||||
Run phases sequentially. Each phase depends on the previous.
|
||||
|
||||
| Order | Task File | Coverage | ~Lines Read |
|
||||
|-------|-----------|----------|-------------|
|
||||
| 1 | `01_route_index.txt` | main.py + all 26 router files → master route table | ~15,000 |
|
||||
| 2 | `02_auth_users_groups_models.txt` | auths, users, groups, models | ~4,600 |
|
||||
| 3 | `03_chats_channels_memories_notes.txt` | chats, channels, memories, notes | ~5,500 |
|
||||
| 4 | `04_files_folders_knowledge_retrieval.txt` | files, folders, knowledge, retrieval | ~5,200 |
|
||||
| 5 | `05_ollama_openai_audio_images.txt` | ollama, openai, audio, images | ~6,900 |
|
||||
| 6 | `06_tools_functions_pipelines_skills_tasks.txt` | tools, functions, pipelines, skills, tasks | ~3,200 |
|
||||
| 7 | `07_configs_prompts_evaluations_analytics_scim_utils.txt` | configs, prompts, evaluations, analytics, scim, utils | ~3,400 |
|
||||
| 8 | `08_consolidation_index.txt` | Consolidates all outputs → README.md + JSON | (reads generated files) |
|
||||
|
||||
---
|
||||
|
||||
## Output Files (after all phases complete)
|
||||
|
||||
```
|
||||
open-webui/api_docs/
|
||||
├── README.md ← Master index + quick reference
|
||||
├── 00_route_index.md ← Complete route table (200+ endpoints)
|
||||
├── 02_auths.md
|
||||
├── 02_users.md
|
||||
├── 02_groups.md
|
||||
├── 02_models.md
|
||||
├── 03_chats.md
|
||||
├── 03_channels.md
|
||||
├── 03_memories.md
|
||||
├── 03_notes.md
|
||||
├── 04_files.md
|
||||
├── 04_folders.md
|
||||
├── 04_knowledge.md
|
||||
├── 04_retrieval.md
|
||||
├── 05_ollama.md
|
||||
├── 05_openai.md
|
||||
├── 05_audio.md
|
||||
├── 05_images.md
|
||||
├── 06_tools.md
|
||||
├── 06_functions.md
|
||||
├── 06_pipelines.md
|
||||
├── 06_skills.md
|
||||
├── 06_tasks.md
|
||||
├── 07_configs.md
|
||||
├── 07_prompts.md
|
||||
├── 07_evaluations.md
|
||||
├── 07_analytics.md
|
||||
├── 07_scim.md
|
||||
├── 07_utils.md
|
||||
└── openwebui_api.json ← Machine-readable summary (all routes)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Each phase uses `--no-plan-first` (detailed instructions already provided).
|
||||
- Working directory for all phases: `/Users/fujie/app/python/oui/open-webui`
|
||||
- The one-click runner: `run_owui_api_docs_phases.sh`
|
||||
- If a phase fails, fix the issue and re-run that single phase before continuing.
|
||||
@@ -0,0 +1,41 @@
|
||||
Phase 1 Mission:
|
||||
Scan the entire OpenWebUI backend source and produce a master route index table.
|
||||
|
||||
Source root: backend/open_webui/
|
||||
Target output directory: api_docs/
|
||||
|
||||
Constraints:
|
||||
- Read-only on ALL files EXCEPT under api_docs/ (create it if missing).
|
||||
- Do NOT generate per-endpoint detail yet — only the master table.
|
||||
- Cover every router file in backend/open_webui/routers/.
|
||||
- Also read backend/open_webui/main.py to capture route prefixes (app.include_router calls).
|
||||
|
||||
Deliverables:
|
||||
1) Create directory: api_docs/
|
||||
2) Create file: api_docs/00_route_index.md
|
||||
|
||||
Content of 00_route_index.md must contain:
|
||||
- A table with columns: Module | HTTP Method | Path | Handler Function | Auth Required | Brief Description
|
||||
- One row per route decorator found in every router file.
|
||||
- "Auth Required" = YES if the route depends on get_verified_user / get_admin_user / similar dependency, NO otherwise.
|
||||
- "Brief Description" = first sentence of the handler's docstring, or empty string if none.
|
||||
- Group rows by Module (router file name without .py).
|
||||
- At the top: a summary section listing total_route_count and module_count.
|
||||
|
||||
Process:
|
||||
1. Read main.py — extract all app.include_router() calls, note prefix and tags per router.
|
||||
2. For each router file in backend/open_webui/routers/, read it fully.
|
||||
3. Find every @router.get/@router.post/@router.put/@router.delete/@router.patch decorator.
|
||||
4. For each decorator: record path, method, function name, auth dependency, docstring.
|
||||
5. Write the combined table to api_docs/00_route_index.md.
|
||||
|
||||
Exit Criteria:
|
||||
- api_docs/00_route_index.md exists.
|
||||
- Table contains at least 100 rows (the codebase has 200+ routes).
|
||||
- No placeholder or TBD.
|
||||
- Total route count printed at the top.
|
||||
|
||||
Final output format:
|
||||
- List of files created/updated.
|
||||
- Total routes found.
|
||||
- Any router files that could not be parsed and why.
|
||||
@@ -0,0 +1,82 @@
|
||||
Phase 2 Mission:
|
||||
Generate detailed API reference documentation for authentication, users, groups, and models endpoints.
|
||||
|
||||
Prerequisites:
|
||||
- api_docs/00_route_index.md must already exist (from Phase 1).
|
||||
|
||||
Source files to read (fully):
|
||||
- backend/open_webui/routers/auths.py
|
||||
- backend/open_webui/routers/users.py
|
||||
- backend/open_webui/routers/groups.py
|
||||
- backend/open_webui/routers/models.py
|
||||
- backend/open_webui/models/auths.py (Pydantic models)
|
||||
- backend/open_webui/models/users.py
|
||||
- backend/open_webui/models/groups.py (if exists)
|
||||
- backend/open_webui/models/models.py
|
||||
|
||||
Output files to create under api_docs/:
|
||||
- 02_auths.md
|
||||
- 02_users.md
|
||||
- 02_groups.md
|
||||
- 02_models.md
|
||||
|
||||
Per-endpoint format (use this EXACTLY for every endpoint in each file):
|
||||
|
||||
---
|
||||
|
||||
### {HTTP_METHOD} {full_path}
|
||||
|
||||
**Summary:** One sentence description.
|
||||
|
||||
**Auth:** Admin only | Verified user | Public
|
||||
|
||||
**Request**
|
||||
|
||||
| Location | Field | Type | Required | Description |
|
||||
|----------|-------|------|----------|-------------|
|
||||
| Header | Authorization | Bearer token | Yes | JWT token |
|
||||
| Body | field_name | type | Yes/No | description |
|
||||
| Query | param_name | type | No | description |
|
||||
| Path | param_name | type | Yes | description |
|
||||
|
||||
*If no request body/params, write: "No additional parameters."*
|
||||
|
||||
**Response `200`**
|
||||
|
||||
```json
|
||||
{
|
||||
"example_field": "example_value"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| field_name | type | description |
|
||||
|
||||
**Error Responses**
|
||||
|
||||
| Status | Meaning |
|
||||
|--------|---------|
|
||||
| 400 | Bad request / validation error |
|
||||
| 401 | Not authenticated |
|
||||
| 403 | Insufficient permissions |
|
||||
| 404 | Resource not found |
|
||||
|
||||
---
|
||||
|
||||
Instructions:
|
||||
1. Read each router file fully to understand every route.
|
||||
2. Trace Pydantic model definitions from the corresponding models/ file.
|
||||
3. Fill in every field from actual code — no guessing.
|
||||
4. If a field is Optional with a default, mark Required = No.
|
||||
5. For auth: check FastAPI dependency injection (Depends(get_verified_user) → "Verified user", Depends(get_admin_user) → "Admin only").
|
||||
6. List ALL endpoints in the router — do not skip any.
|
||||
|
||||
Exit Criteria:
|
||||
- 4 output files created.
|
||||
- Every route from 00_route_index.md for these modules is covered.
|
||||
- No placeholder or TBD.
|
||||
|
||||
Final output format:
|
||||
- List of files created.
|
||||
- Count of endpoints documented per file.
|
||||
@@ -0,0 +1,87 @@
|
||||
Phase 3 Mission:
|
||||
Generate detailed API reference documentation for chat, channels, memories, and notes endpoints.
|
||||
|
||||
Prerequisites:
|
||||
- api_docs/00_route_index.md must already exist (from Phase 1).
|
||||
|
||||
Source files to read (fully):
|
||||
- backend/open_webui/routers/chats.py
|
||||
- backend/open_webui/routers/channels.py
|
||||
- backend/open_webui/routers/memories.py
|
||||
- backend/open_webui/routers/notes.py
|
||||
- backend/open_webui/models/chats.py (Pydantic models)
|
||||
- backend/open_webui/models/channels.py
|
||||
- backend/open_webui/models/memories.py
|
||||
- backend/open_webui/models/notes.py (if exists)
|
||||
- backend/open_webui/models/messages.py (shared message models)
|
||||
|
||||
Output files to create under api_docs/:
|
||||
- 03_chats.md
|
||||
- 03_channels.md
|
||||
- 03_memories.md
|
||||
- 03_notes.md
|
||||
|
||||
Per-endpoint format:
|
||||
|
||||
---
|
||||
|
||||
### {HTTP_METHOD} {full_path}
|
||||
|
||||
**Summary:** One sentence description.
|
||||
|
||||
**Auth:** Admin only | Verified user | Public
|
||||
|
||||
**Request**
|
||||
|
||||
| Location | Field | Type | Required | Description |
|
||||
|----------|-------|------|----------|-------------|
|
||||
| Body | field_name | type | Yes/No | description |
|
||||
|
||||
*If no parameters, write: "No additional parameters."*
|
||||
|
||||
**Response `200`**
|
||||
|
||||
```json
|
||||
{
|
||||
"example_field": "example_value"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| field_name | type | description |
|
||||
|
||||
**Error Responses**
|
||||
|
||||
| Status | Meaning |
|
||||
|--------|---------|
|
||||
| 401 | Not authenticated |
|
||||
| 403 | Insufficient permissions |
|
||||
| 404 | Resource not found |
|
||||
|
||||
---
|
||||
|
||||
Special notes for this phase:
|
||||
- chats.py is 1527 lines with ~40 routes — document ALL of them.
|
||||
- channels.py is 2133 lines — document ALL routes; note WebSocket upgrade endpoints separately.
|
||||
- For WebSocket endpoints: note the protocol (ws://) and describe events/message payload format.
|
||||
- Pay special attention to chat history structure: messages array, history.messages dict.
|
||||
- Note pagination parameters (skip, limit, page) where applicable.
|
||||
|
||||
Instructions:
|
||||
1. Read each router file fully.
|
||||
2. Trace Pydantic model definitions from the corresponding models/ file.
|
||||
3. For complex response types (list of chats, paginated results), show the wrapper structure.
|
||||
4. If a route modifies chat history, document the exact history object shape.
|
||||
5. List ALL endpoints — do not skip paginated variants.
|
||||
|
||||
Exit Criteria:
|
||||
- 4 output files created.
|
||||
- Every route from 00_route_index.md for these modules is covered.
|
||||
- WebSocket endpoints documented with payload shape.
|
||||
- No placeholder or TBD.
|
||||
|
||||
Final output format:
|
||||
- List of files created.
|
||||
- Count of endpoints documented per file.
|
||||
- Note any complex schemas that required deep tracing.
|
||||
@@ -0,0 +1,94 @@
|
||||
Phase 4 Mission:
|
||||
Generate detailed API reference documentation for files, folders, knowledge base, and retrieval endpoints.
|
||||
|
||||
Prerequisites:
|
||||
- api_docs/00_route_index.md must already exist (from Phase 1).
|
||||
|
||||
Source files to read (fully):
|
||||
- backend/open_webui/routers/files.py (~911 lines)
|
||||
- backend/open_webui/routers/folders.py (~351 lines)
|
||||
- backend/open_webui/routers/knowledge.py (~1139 lines)
|
||||
- backend/open_webui/routers/retrieval.py (~2820 lines — LARGEST FILE)
|
||||
- backend/open_webui/models/files.py
|
||||
- backend/open_webui/models/folders.py
|
||||
- backend/open_webui/models/knowledge.py
|
||||
|
||||
Output files to create under api_docs/:
|
||||
- 04_files.md
|
||||
- 04_folders.md
|
||||
- 04_knowledge.md
|
||||
- 04_retrieval.md
|
||||
|
||||
Per-endpoint format:
|
||||
|
||||
---
|
||||
|
||||
### {HTTP_METHOD} {full_path}
|
||||
|
||||
**Summary:** One sentence description.
|
||||
|
||||
**Auth:** Admin only | Verified user | Public
|
||||
|
||||
**Request**
|
||||
|
||||
| Location | Field | Type | Required | Description |
|
||||
|----------|-------|------|----------|-------------|
|
||||
| Body | field_name | type | Yes/No | description |
|
||||
|
||||
*If no parameters, write: "No additional parameters."*
|
||||
|
||||
**Response `200`**
|
||||
|
||||
```json
|
||||
{
|
||||
"example_field": "example_value"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| field_name | type | description |
|
||||
|
||||
**Error Responses**
|
||||
|
||||
| Status | Meaning |
|
||||
|--------|---------|
|
||||
| 401 | Not authenticated |
|
||||
| 404 | Resource not found |
|
||||
|
||||
---
|
||||
|
||||
Special notes for this phase:
|
||||
|
||||
FILES:
|
||||
- File upload uses multipart/form-data — document the form fields.
|
||||
- File metadata response: id, filename, meta.content_type, size, user_id.
|
||||
- File content endpoint: returns raw bytes — note Content-Type header behavior.
|
||||
|
||||
KNOWLEDGE:
|
||||
- Knowledge base endpoints interact with vector store — note which ones trigger embedding/indexing.
|
||||
- Document the "files" array in knowledge base objects (which file IDs are linked).
|
||||
- Add/remove files from knowledge base: document the exact request shape.
|
||||
|
||||
RETRIEVAL:
|
||||
- retrieval.py is 2820 lines; it configures the RAG pipeline (embedding models, chunk settings, etc.).
|
||||
- Prioritize documenting: query endpoint, config GET/POST endpoints, embedding model endpoints.
|
||||
- For config endpoints: document ALL configuration fields (chunk_size, chunk_overlap, top_k, etc.).
|
||||
- Document the "process" endpoints (process_doc, process_web, process_youtube) with their request shapes.
|
||||
|
||||
Instructions:
|
||||
1. Read ALL source files listed above.
|
||||
2. For retrieval.py: focus on public API surface (router endpoints), not internal helper functions.
|
||||
3. Document file upload endpoints with multipart form fields clearly marked.
|
||||
4. Trace vector DB config models in retrieval.py to document all configurable fields.
|
||||
|
||||
Exit Criteria:
|
||||
- 4 output files created.
|
||||
- retrieval.py endpoints fully documented including all config fields.
|
||||
- File upload endpoints show form-data field names.
|
||||
- No placeholder or TBD.
|
||||
|
||||
Final output format:
|
||||
- List of files created.
|
||||
- Count of endpoints documented per file.
|
||||
- Note any tricky schemas (nested config objects, etc.).
|
||||
@@ -0,0 +1,98 @@
|
||||
Phase 5 Mission:
|
||||
Generate detailed API reference documentation for AI provider endpoints: Ollama, OpenAI-compatible, Audio, and Images.
|
||||
|
||||
Prerequisites:
|
||||
- api_docs/00_route_index.md must already exist (from Phase 1).
|
||||
|
||||
Source files to read (fully):
|
||||
- backend/open_webui/routers/ollama.py (~1884 lines)
|
||||
- backend/open_webui/routers/openai.py (~1466 lines)
|
||||
- backend/open_webui/routers/audio.py (~1397 lines)
|
||||
- backend/open_webui/routers/images.py (~1164 lines)
|
||||
|
||||
Output files to create under api_docs/:
|
||||
- 05_ollama.md
|
||||
- 05_openai.md
|
||||
- 05_audio.md
|
||||
- 05_images.md
|
||||
|
||||
Per-endpoint format:
|
||||
|
||||
---
|
||||
|
||||
### {HTTP_METHOD} {full_path}
|
||||
|
||||
**Summary:** One sentence description.
|
||||
|
||||
**Auth:** Admin only | Verified user | Public
|
||||
|
||||
**Request**
|
||||
|
||||
| Location | Field | Type | Required | Description |
|
||||
|----------|-------|------|----------|-------------|
|
||||
| Body | field_name | type | Yes/No | description |
|
||||
|
||||
**Response `200`**
|
||||
|
||||
```json
|
||||
{
|
||||
"example_field": "example_value"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| field_name | type | description |
|
||||
|
||||
**Streaming:** Yes / No *(add this line for endpoints that support SSE/streaming)*
|
||||
|
||||
**Error Responses**
|
||||
|
||||
| Status | Meaning |
|
||||
|--------|---------|
|
||||
| 401 | Not authenticated |
|
||||
| 503 | Upstream provider unavailable |
|
||||
|
||||
---
|
||||
|
||||
Special notes for this phase:
|
||||
|
||||
OLLAMA:
|
||||
- Endpoints are mostly pass-through proxies to Ollama's own API.
|
||||
- Document which endpoints are admin-only (model management) vs user-accessible (generate/chat).
|
||||
- For streaming endpoints (generate, chat), note: "Supports SSE streaming via stream=true."
|
||||
- Document the model pull/push/delete management endpoints carefully.
|
||||
|
||||
OPENAI:
|
||||
- Endpoints proxy to configured OpenAI-compatible backend.
|
||||
- Document the /api/openai/models endpoint (returns merged model list).
|
||||
- Note which endpoints pass through request body to upstream unchanged.
|
||||
- Document admin endpoints for adding/removing OpenAI API connections.
|
||||
|
||||
AUDIO:
|
||||
- Document: transcription (STT), TTS synthesis, and audio config endpoints.
|
||||
- For file upload endpoints: specify multipart/form-data field names.
|
||||
- Document supported audio formats and any size limits visible in code.
|
||||
- Note: Engine types (openai, whisper, etc.) and configuration endpoints.
|
||||
|
||||
IMAGES:
|
||||
- Document: image generation endpoints and image engine config.
|
||||
- Note DALL-E vs ComfyUI vs Automatic1111 backend differences if documented in code.
|
||||
- Document image config GET/POST: size, steps, model, and other parameters.
|
||||
|
||||
Instructions:
|
||||
1. Read each file fully — they are complex proxying routers.
|
||||
2. For pass-through proxy routes: still document the expected request/response shape.
|
||||
3. Distinguish between admin configuration routes and user-facing generation routes.
|
||||
4. Streaming endpoints must be clearly marked with "Streaming: Yes" and note the SSE event format.
|
||||
|
||||
Exit Criteria:
|
||||
- 4 output files created.
|
||||
- Every route from 00_route_index.md for these modules is covered.
|
||||
- Streaming endpoints clearly annotated.
|
||||
- No placeholder or TBD.
|
||||
|
||||
Final output format:
|
||||
- List of files created.
|
||||
- Count of endpoints documented per file.
|
||||
- Note streaming endpoints count per module.
|
||||
@@ -0,0 +1,103 @@
|
||||
Phase 6 Mission:
|
||||
Generate detailed API reference documentation for tools, functions, pipelines, skills, and tasks endpoints.
|
||||
|
||||
Prerequisites:
|
||||
- docs/open_webui_api/00_route_index.md must already exist (from Phase 1).
|
||||
- NOTE: Output directory is api_docs/ (not docs/open_webui_api/).
|
||||
|
||||
Source files to read (fully):
|
||||
- backend/open_webui/routers/tools.py (~868 lines)
|
||||
- backend/open_webui/routers/functions.py (~605 lines)
|
||||
- backend/open_webui/routers/pipelines.py (~540 lines)
|
||||
- backend/open_webui/routers/skills.py (~447 lines)
|
||||
- backend/open_webui/routers/tasks.py (~764 lines)
|
||||
- backend/open_webui/models/tools.py
|
||||
- backend/open_webui/models/functions.py
|
||||
- backend/open_webui/models/skills.py
|
||||
|
||||
Output files to create under api_docs/:
|
||||
- 06_tools.md
|
||||
- 06_functions.md
|
||||
- 06_pipelines.md
|
||||
- 06_skills.md
|
||||
- 06_tasks.md
|
||||
|
||||
Per-endpoint format:
|
||||
|
||||
---
|
||||
|
||||
### {HTTP_METHOD} {full_path}
|
||||
|
||||
**Summary:** One sentence description.
|
||||
|
||||
**Auth:** Admin only | Verified user | Public
|
||||
|
||||
**Request**
|
||||
|
||||
| Location | Field | Type | Required | Description |
|
||||
|----------|-------|------|----------|-------------|
|
||||
| Body | field_name | type | Yes/No | description |
|
||||
|
||||
**Response `200`**
|
||||
|
||||
```json
|
||||
{
|
||||
"example_field": "example_value"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| field_name | type | description |
|
||||
|
||||
**Error Responses**
|
||||
|
||||
| Status | Meaning |
|
||||
|--------|---------|
|
||||
| 401 | Not authenticated |
|
||||
| 404 | Resource not found |
|
||||
|
||||
---
|
||||
|
||||
Special notes for this phase:
|
||||
|
||||
TOOLS:
|
||||
- Tools are user-created Python functions exposed to LLM. Document CRUD operations.
|
||||
- The tool "specs" field: document its structure (list of OpenAI function call specs).
|
||||
- Document the "export" endpoint if present.
|
||||
|
||||
FUNCTIONS:
|
||||
- Functions include filters, actions, pipes registered by admin.
|
||||
- Document the `type` field values: "filter", "action", "pipe".
|
||||
- Document the `meta` and `valves` fields structure.
|
||||
|
||||
PIPELINES:
|
||||
- Pipelines connect to external pipeline servers.
|
||||
- Document: add pipeline (URL + API key), list pipelines, get valves, set valves.
|
||||
- Note: pipelines proxy through to an external server; document that behavior.
|
||||
|
||||
SKILLS:
|
||||
- Skills are agent-style plugins with multi-step execution.
|
||||
- Document the skills schema: name, content (Python source), meta.
|
||||
- Note if there's a "call" endpoint for executing a skill.
|
||||
|
||||
TASKS:
|
||||
- Tasks module handles background processing (title generation, tag generation, etc.).
|
||||
- Document config endpoints (GET/POST for task-specific LLM settings).
|
||||
- Document any direct invocation endpoints.
|
||||
|
||||
Instructions:
|
||||
1. Read all source files fully.
|
||||
2. For valves/specs/meta fields with complex structure, show the full nested schema.
|
||||
3. Distinguish admin-only CRUD from user-accessible execution endpoints.
|
||||
4. For endpoints that execute code (tools, functions, skills), clearly note security implications.
|
||||
|
||||
Exit Criteria:
|
||||
- 5 output files created.
|
||||
- Every route from 00_route_index.md for these modules is covered.
|
||||
- Complex nested schemas (valves, specs, meta) fully documented.
|
||||
- No placeholder or TBD.
|
||||
|
||||
Final output format:
|
||||
- List of files created.
|
||||
- Count of endpoints documented per file.
|
||||
@@ -0,0 +1,109 @@
|
||||
Phase 7 Mission:
|
||||
Generate detailed API reference documentation for configuration, prompts, evaluations, analytics, SCIM, and utility endpoints.
|
||||
|
||||
Prerequisites:
|
||||
- api_docs/00_route_index.md must already exist (from Phase 1).
|
||||
|
||||
Source files to read (fully):
|
||||
- backend/open_webui/routers/configs.py (~548 lines)
|
||||
- backend/open_webui/routers/prompts.py (~759 lines)
|
||||
- backend/open_webui/routers/evaluations.py (~466 lines)
|
||||
- backend/open_webui/routers/analytics.py (~454 lines)
|
||||
- backend/open_webui/routers/scim.py (~1030 lines)
|
||||
- backend/open_webui/routers/utils.py (~123 lines)
|
||||
- backend/open_webui/models/prompts.py
|
||||
- backend/open_webui/config.py (for config field definitions)
|
||||
|
||||
Output files to create under api_docs/:
|
||||
- 07_configs.md
|
||||
- 07_prompts.md
|
||||
- 07_evaluations.md
|
||||
- 07_analytics.md
|
||||
- 07_scim.md
|
||||
- 07_utils.md
|
||||
|
||||
Per-endpoint format:
|
||||
|
||||
---
|
||||
|
||||
### {HTTP_METHOD} {full_path}
|
||||
|
||||
**Summary:** One sentence description.
|
||||
|
||||
**Auth:** Admin only | Verified user | Public
|
||||
|
||||
**Request**
|
||||
|
||||
| Location | Field | Type | Required | Description |
|
||||
|----------|-------|------|----------|-------------|
|
||||
| Body | field_name | type | Yes/No | description |
|
||||
|
||||
**Response `200`**
|
||||
|
||||
```json
|
||||
{
|
||||
"example_field": "example_value"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| field_name | type | description |
|
||||
|
||||
**Error Responses**
|
||||
|
||||
| Status | Meaning |
|
||||
|--------|---------|
|
||||
| 401 | Not authenticated |
|
||||
| 404 | Resource not found |
|
||||
|
||||
---
|
||||
|
||||
Special notes for this phase:
|
||||
|
||||
CONFIGS:
|
||||
- This is the most important module in this phase.
|
||||
- The global config GET/POST endpoints control system-wide settings.
|
||||
- Read backend/open_webui/config.py to enumerate ALL configurable fields.
|
||||
- Document every config field with its type, default, and effect.
|
||||
- Group config fields by category (auth, RAG, models, UI, etc.) in the output.
|
||||
|
||||
PROMPTS:
|
||||
- System prompts stored by users.
|
||||
- Document CRUD operations and the command field (trigger word like "/summarize").
|
||||
- Note the "access_control" field structure.
|
||||
|
||||
EVALUATIONS:
|
||||
- Feedback/rating data for model responses.
|
||||
- Document the feedback object structure (rating, comment, model_id, etc.).
|
||||
- Note any aggregation/analytics endpoints.
|
||||
|
||||
ANALYTICS:
|
||||
- Usage statistics endpoints.
|
||||
- Document what metrics are tracked and aggregation options.
|
||||
|
||||
SCIM:
|
||||
- SCIM 2.0 protocol for enterprise user/group provisioning.
|
||||
- Document: /Users, /Groups, /ServiceProviderConfig, /ResourceTypes endpoints.
|
||||
- Note: SCIM uses different Content-Type and auth mechanism — document these.
|
||||
- Follow SCIM 2.0 RFC schema format for user/group objects.
|
||||
|
||||
UTILS:
|
||||
- Miscellaneous utility endpoints.
|
||||
- Document all available utilities (markdown renderer, code executor, etc.).
|
||||
|
||||
Instructions:
|
||||
1. Read config.py in addition to router files to get complete field lists.
|
||||
2. For SCIM: follow SCIM 2.0 RFC conventions in documentation format.
|
||||
3. For configs: produce a separate "All Config Fields" appendix table.
|
||||
|
||||
Exit Criteria:
|
||||
- 6 output files created.
|
||||
- configs.md includes appendix table of ALL config fields with defaults.
|
||||
- scim.md follows SCIM 2.0 documentation conventions.
|
||||
- No placeholder or TBD.
|
||||
|
||||
Final output format:
|
||||
- List of files created.
|
||||
- Count of endpoints documented per file.
|
||||
- Count of config fields documented in configs.md appendix.
|
||||
@@ -0,0 +1,89 @@
|
||||
Phase 8 Mission:
|
||||
Consolidate all previously generated phase outputs into a polished master index and a machine-readable summary.
|
||||
|
||||
Prerequisites:
|
||||
- ALL phase 1~7 output files must exist under api_docs/.
|
||||
- Specifically, these files must exist:
|
||||
- api_docs/00_route_index.md
|
||||
- api_docs/02_auths.md, 02_users.md, 02_groups.md, 02_models.md
|
||||
- api_docs/03_chats.md, 03_channels.md, 03_memories.md, 03_notes.md
|
||||
- api_docs/04_files.md, 04_folders.md, 04_knowledge.md, 04_retrieval.md
|
||||
- api_docs/05_ollama.md, 05_openai.md, 05_audio.md, 05_images.md
|
||||
- api_docs/06_tools.md, 06_functions.md, 06_pipelines.md, 06_skills.md, 06_tasks.md
|
||||
- api_docs/07_configs.md, 07_prompts.md, 07_evaluations.md, 07_analytics.md, 07_scim.md, 07_utils.md
|
||||
|
||||
Output files to create/update under api_docs/:
|
||||
1. api_docs/README.md — human-readable master index
|
||||
2. api_docs/openwebui_api.json — machine-readable OpenAPI-style JSON summary
|
||||
|
||||
Content of README.md:
|
||||
- Title: "OpenWebUI Backend API Reference"
|
||||
- Subtitle: "Auto-generated from source code. Do not edit manually."
|
||||
- Generation date (today's date)
|
||||
- Table of Contents (links to every .md file above)
|
||||
- Statistics:
|
||||
- Total module count
|
||||
- Total route count (from 00_route_index.md)
|
||||
- Admin-only route count
|
||||
- Public route count
|
||||
- Streaming endpoint count
|
||||
- Quick Reference: a condensed table of the 20 most commonly used endpoints (chat creation, message send, file upload, model list, auth login/logout, etc.)
|
||||
- Authentication Guide section:
|
||||
- How to get a JWT token (reference auths.md)
|
||||
- How to include it in requests (Authorization: Bearer <token>)
|
||||
- Token expiry behavior
|
||||
- Common Patterns section:
|
||||
- Pagination (skip/limit parameters)
|
||||
- Error response shape: {detail: string}
|
||||
- Rate limiting (if documented in code)
|
||||
|
||||
Content of openwebui_api.json:
|
||||
A JSON object with this structure:
|
||||
{
|
||||
"meta": {
|
||||
"generated_date": "YYYY-MM-DD",
|
||||
"source": "backend/open_webui/routers/",
|
||||
"total_routes": <number>,
|
||||
"modules": [<list of module names>]
|
||||
},
|
||||
"routes": [
|
||||
{
|
||||
"module": "auths",
|
||||
"method": "POST",
|
||||
"path": "/api/v1/auths/signin",
|
||||
"handler": "signin",
|
||||
"auth_required": false,
|
||||
"auth_type": "public",
|
||||
"summary": "Authenticate user and return JWT token.",
|
||||
"request_body": {
|
||||
"email": {"type": "str", "required": true},
|
||||
"password": {"type": "str", "required": true}
|
||||
},
|
||||
"response_200": {
|
||||
"token": {"type": "str"},
|
||||
"token_type": {"type": "str"},
|
||||
"id": {"type": "str"}
|
||||
},
|
||||
"streaming": false
|
||||
}
|
||||
]
|
||||
}
|
||||
- Include ALL routes from all modules.
|
||||
- For streaming endpoints: "streaming": true.
|
||||
|
||||
Instructions:
|
||||
1. Read ALL generated phase output files (00 through 07).
|
||||
2. Parse or summarize endpoint data from each file to populate the JSON.
|
||||
3. Write README.md with complete statistics and quick reference.
|
||||
4. Validate: total_routes in README.md must match count in openwebui_api.json.
|
||||
|
||||
Exit Criteria:
|
||||
- api_docs/README.md exists with statistics and ToC.
|
||||
- api_docs/openwebui_api.json exists with all routes (valid JSON).
|
||||
- Route counts in README.md and JSON are consistent.
|
||||
- No placeholder or TBD.
|
||||
|
||||
Final output format:
|
||||
- Confirmation of files created.
|
||||
- Total routes count in JSON.
|
||||
- Any modules with missing or incomplete data (for manual review).
|
||||
@@ -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,8 +1,14 @@
|
||||
# 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**.
|
||||
This is a powerful **GitHub Copilot SDK** Pipe for **OpenWebUI** that provides a unified **Agentic experience**. It goes beyond simple model access by enabling autonomous **Intent Recognition**, **Web Search**, and **Context Compaction**. It seamlessly reuses your existing **Tools, MCP servers, OpenAPI servers, and Skills** from OpenWebUI to create a truly integrated ecosystem.
|
||||
|
||||
- **🧠 Autonomous Intent Recognition**: The Agent independently analyzes user goals to determine the most effective path forward.
|
||||
- **🌐 Smart Web Search**: Built-in capability to trigger web searches autonomously based on task requirements.
|
||||
- **♾️ Infinite Session (Context Compaction)**: Automatically manages long-running conversations by compacting context (summarization + TODO persistence) to maintain project focus.
|
||||
- **🧩 Ecosystem Injection**: Directly reads and leverages your configured **OpenWebUI Tools, MCPs, OpenAPI Servers, and Skills**.
|
||||
- **🎨 Interactive Delivery**: Native support for **HTML Artifacts** and **RichUI** components for real-time visualization and reporting.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Essential Companion**
|
||||
@@ -14,21 +20,19 @@ This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/
|
||||
|
||||
---
|
||||
|
||||
## ✨ v0.9.0: The Skills Revolution & Stability Update
|
||||
## ✨ v0.9.1: Autonomous 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.
|
||||
- **🌐 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.
|
||||
- **🛠️ Terminology Alignment**: Standardized all references to **"Agent"** and **"Context Compaction"** (for Infinite Session) across all languages to better reflect the technical capabilities.
|
||||
- **🌐 Language Consistency**: System prompts mandate that Agent output language remains strictly consistent with user input.
|
||||
- **🐛 Fixed MCP Tool Filtering**: 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:`).
|
||||
- **🔍 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.
|
||||
@@ -40,7 +44,13 @@ This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/
|
||||
- **Live HTML/JS**: Instantly render and interact with apps, dashboards, or reports generated by the Agent.
|
||||
- **Persistent Publishing**: Agents can "publish" generated files (Excel, CSV, docs) to OpenWebUI's file storage, providing permanent download links.
|
||||
- **🌊 UX-First Streaming**: Full support for "Thinking" processes (Chain of Thought), status indicators, and real-time progress bars for long-running tasks.
|
||||
- **🧠 Deep Database Integration**: Real-time persistence of TOD·O lists and session metadata ensures your workflow state is always visible in the UI.
|
||||
- **🧠 Deep Database Integration**: Real-time persistence of TODO lists and session metadata ensures your workflow state is always visible in the UI.
|
||||
|
||||
> [!TIP]
|
||||
> **💡 Visualization Pro-Tip**
|
||||
> To get the most out of **HTML Artifacts** and **RichUI**, we highly recommend asking the Agent to install the skill via its GitHub URL:
|
||||
> "Install this skill: https://github.com/nicobailon/visual-explainer".
|
||||
> This skill is specifically optimized for generating high-quality visual components and integrates perfectly with this Pipe.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
# GitHub Copilot SDK 官方管道
|
||||
# GitHub Copilot Official SDK Pipe
|
||||
|
||||
**作者:** [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 交互体验。
|
||||
这是一个将 **GitHub Copilot SDK** 深度集成到 **OpenWebUI** 中的强大 Agent SDK 管道。它不仅实现了 SDK 的核心功能,还支持 **智能意图识别**、**自主网页搜索** 与 **自动上下文压缩**,并能够无缝读取 OpenWebUI 已有的配置进行智能注入,让 Agent 能够具备以下能力:
|
||||
|
||||
- **🧠 智能意图识别**:Agent 能自主分析用户任务的深层意图,决定最有效的处理路径。
|
||||
- **🌐 自主网页搜索**:具备独立的网页搜索触发判断力,无需用户手动干预。
|
||||
- **♾️ 自动压缩上下文**:支持 Infinite Session,自动对长对话进行上下文压缩与摘要,确保长期任务跟进。
|
||||
- **🛠️ 全功能 Skill 体系**:完美支持本地自定义 Skill 目录,通过脚本与资源的结合实现真正的功能增强。
|
||||
- **🧩 深度生态复用**:直接复用您在 OpenWebUI 中配置的各种 **工具 (Tools)**、**MCP**、**OpenAPI Server** 和 **技能 (Skills)**。
|
||||
|
||||
为您带来更强、更完整的交互体验。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **核心伴侣组件**
|
||||
@@ -13,31 +21,37 @@
|
||||
|
||||
---
|
||||
|
||||
## ✨ 0.9.0 核心更新:技能革命与稳定性加固
|
||||
## ✨ 0.9.1 最新更新:自主网页搜索与可靠性修复
|
||||
|
||||
- **🧩 Copilot SDK Skills 原生支持**: 技能可作为一等上下文能力被加载和使用。
|
||||
- **🔄 OpenWebUI Skills 桥接**: 实现 OpenWebUI **工作区 > Skills** 与 SDK 技能目录的深度双向同步。
|
||||
- **🛠️ 确定性 `manage_skills` 工具**: 通过稳定工具契约完成技能的生命周期管理。
|
||||
- **🌊 状态栏逻辑加固**: 引入 `session_finalized` 多层锁定机制,彻底解决任务完成后状态栏回弹或卡死的问题。
|
||||
- **🗂️ 环境目录持久化**: 增强 `COPILOTSDK_CONFIG_DIR` 逻辑,确保会话状态跨容器重启稳定存在。
|
||||
- **🌐 强化自主网页搜索**:`web_search` 工具现已强制对 Agent 开启(绕过 UI 网页搜索开关),充分利用 Copilot 自身具备的搜索判断能力。
|
||||
- **🛠️ 术语一致性优化**:全语种同步将“助手”更改为 **"Agent"**,并将“优化会话”统一为 **"压缩上下文"**,更准确地描述 Infinite Session 的技术本质。
|
||||
- **🌐 语言一致性**:内置指令确保 Agent 输出语言与用户输入严格对齐,提供无缝的国际化交互体验。
|
||||
- **🐛 修复 MCP 工具过滤逻辑**:解决了在管理员后端配置 `function_name_filter_list`(或在聊天界面勾选特定工具)时,因 ID 前缀(`server:mcp:`)识别逻辑错误导致工具意外失效的问题。
|
||||
- **🔍 提升过滤稳定性**:修复了工具 ID 归一化逻辑,确保点选的工具白名单在 SDK 会话中精确生效。
|
||||
|
||||
---
|
||||
|
||||
## ✨ 核心能力 (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 服务器。
|
||||
- **OpenAPI 桥接**: 将任何外部 REST API 一键转换为 Agent 可调用的工具。
|
||||
- **OpenWebUI 原生桥接**: 零配置接入现有的 OpenWebUI 工具及内置功能(网页搜索、记忆等)。
|
||||
- **🧩 OpenWebUI Skills 桥接**: 将简单的 OpenWebUI Markdown 指令转化为包含脚本、模板和数据的强大 SDK 技能文件夹。
|
||||
- **🧩 OpenWebUI Skills 桥接**: 将简单的 OpenWebUI Markdown 指令转化为包含脚本、模板 and 数据的强大 SDK 技能文件夹。
|
||||
- **♾️ 无限会话管理**: 先进的上下文窗口管理,支持自动“压缩”(摘要提取 + TODO 列表持久化)。支持长达数周的项目跟踪而不会丢失核心上下文。
|
||||
- **📊 交互式产物与发布**:
|
||||
- **实时 HTML/JS**: 瞬间渲染并交互 Agent 生成的应用程序、可视化看板或报告。
|
||||
- **持久化发布**: Agent 可将生成的产物(Excel, CSV, 文档)发布至 OpenWebUI 文件存储,并在聊天中提供永久下载链接。
|
||||
- **🌊 极致交互体验**: 完整支持深度思考过程 (Thinking Process) 流式渲染、状态指示器以及长任务实时进度条。
|
||||
- **🧠 深度数据库集成**: TOD·O 列表与会话元数据的实时持久化,确保任务执行状态在 UI 上清晰可见。
|
||||
- **🧠 深度数据库集成**: TODO 列表与会话元数据的实时持久化,确保任务执行状态在 UI 上清晰可见。
|
||||
|
||||
> [!TIP]
|
||||
> **💡 增强渲染建议**
|
||||
> 为了获得最精美的 **HTML Artifacts** 与 **RichUI** 效果,建议在对话中通过提供的 GitHub 链接直接命令 Agent 安装:
|
||||
> “请安装此技能:https://github.com/nicobailon/visual-explainer”。
|
||||
> 该技能专为生成高质量可视化组件而设计,能够与本 Pipe 完美协作。
|
||||
|
||||
---
|
||||
|
||||
@@ -46,162 +60,51 @@
|
||||
`GitHub Copilot SDK Files Filter` 是本 Pipe 的配套插件,用于阻止 OpenWebUI 默认 RAG 在 Pipe 接手前抢先处理上传文件。
|
||||
|
||||
- **作用**: 将上传文件移动到 `copilot_files`,让 Pipe 能直接读取原始二进制。
|
||||
- **必要性**: 若未安装,文件可能被提前解析/向量化,Agent 难以拿到原始文件。
|
||||
- **必要性**: 若未安装,文件可能被提前解析/向量化,Agent 拿到原始文件。
|
||||
- **v0.1.3 重点**:
|
||||
- 修复 BYOK 模型 ID 识别(支持 `github_copilot_official_sdk_pipe.xxx` 前缀匹配)。
|
||||
- 新增双通道调试日志(`show_debug_log`):后端 logger + 浏览器控制台。
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ 核心配置参数 (Valves)
|
||||
## 🚀 快速开始 (Quick Start)
|
||||
|
||||
### 1. 管理员配置 (基础设置)
|
||||
1. **安装本插件**: 在 OpenWebUI 管道管理界面添加并启用。
|
||||
2. **安装 [Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)** (必须): 以获得文件处理能力。
|
||||
3. **配置凭据**:
|
||||
- **官方模式**: 默认即可。确保环境中安装了 `github-copilot-sdk`。
|
||||
- **BYOK 模式**: 填入 OpenAI/Anthropic/DeepSeek 的 Base URL 与 Key。
|
||||
4. **选择模型**: 在聊天界面选择 `GitHub Copilot Official SDK Pipe` 系列模型。
|
||||
5. **开始对话**: 直接上传文件或发送复杂指令。
|
||||
|
||||
管理员可在函数设置中定义全局默认行为。
|
||||
---
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
## ⚙️ 配置参数 (Configuration Valves)
|
||||
|
||||
| 参数 | 默认值 | 描述 |
|
||||
| :--- | :--- | :--- |
|
||||
| `GH_TOKEN` | `""` | 全局 GitHub Token (需具备 'Copilot Requests' 权限)。 |
|
||||
| `COPILOTSDK_CONFIG_DIR` | `""` | SDK 配置与会话状态持久化目录 (例如: `/app/backend/data/.copilot`)。 |
|
||||
| `ENABLE_OPENWEBUI_TOOLS` | `True` | 启用 OpenWebUI 工具 (包括定义工具和内置工具)。 |
|
||||
| `ENABLE_OPENAPI_SERVER` | `True` | 启用 OpenAPI 工具服务器连接。 |
|
||||
| `ENABLE_MCP_SERVER` | `True` | 启用直接 MCP 客户端连接 (推荐)。 |
|
||||
| `ENABLE_OPENWEBUI_SKILLS` | `True` | 开启与 OpenWebUI **工作区 > Skills** 的双向同步桥接。 |
|
||||
| `OPENWEBUI_SKILLS_SHARED_DIR` | `/app/backend/data/cache/copilot-openwebui-skills` | OpenWebUI skills 转换后的共享缓存目录。 |
|
||||
| `GITHUB_SKILLS_SOURCE_URL` | `""` | 可选 GitHub tree 地址,用于批量导入 skills(例如 anthropic/skills)。 |
|
||||
| `DISABLED_SKILLS` | `""` | 逗号分隔的 skill 名称黑名单(如 `docs-writer,webapp-testing`)。 |
|
||||
| `REASONING_EFFORT` | `medium` | 推理强度:low, medium, high。 |
|
||||
| `SHOW_THINKING` | `True` | 显示模型推理/思考过程。 |
|
||||
| `INFINITE_SESSION` | `True` | 启用无限会话 (自动上下文压缩)。 |
|
||||
| `MAX_MULTIPLIER` | `1.0` | 最大允许的模型计费倍率 (0x 为仅限免费模型)。 |
|
||||
| `EXCLUDE_KEYWORDS` | `""` | 排除包含这些关键字的模型 (逗号分隔)。 |
|
||||
| `TIMEOUT` | `300` | 每个流数据块的超时时间 (秒)。 |
|
||||
| `BYOK_TYPE` | `openai` | BYOK 服务商类型:`openai`, `anthropic`。 |
|
||||
| `BYOK_BASE_URL` | `""` | BYOK 基础 URL (例如: <https://api.openai.com/v1)。> |
|
||||
| `BYOK_MODELS` | `""` | BYOK 模型列表 (逗号分隔)。留空则从 API 获取。 |
|
||||
| `CUSTOM_ENV_VARS` | `""` | 自定义环境变量 (JSON 格式)。 |
|
||||
| `DEBUG` | `False` | 开启此项以在前端控制台输出详细调试日志。 |
|
||||
|
||||
### 2. 用户配置 (个人覆盖)
|
||||
|
||||
普通用户可在各自的个人设置中根据需要覆盖以下参数。
|
||||
|
||||
| 参数 | 说明 |
|
||||
| :--- | :--- |
|
||||
| `GH_TOKEN` | 使用个人的 GitHub Token。 |
|
||||
| `REASONING_EFFORT` | 个人偏好的推理强度。 |
|
||||
| `SHOW_THINKING` | 显示模型推理/思考过程。 |
|
||||
| `MAX_MULTIPLIER` | 最大允许的模型计费倍率覆盖。 |
|
||||
| `EXCLUDE_KEYWORDS` | 排除包含这些关键字的模型。 |
|
||||
| `ENABLE_OPENWEBUI_SKILLS` | 启用将当前用户可读的全部已启用 OpenWebUI skills 转换并加载为 SDK `SKILL.md` 目录。 |
|
||||
| `GITHUB_SKILLS_SOURCE_URL` | 为当前用户会话设置可选 GitHub tree 地址以批量导入 skills。 |
|
||||
| `DISABLED_SKILLS` | 为当前用户会话禁用指定 skills(逗号分隔)。 |
|
||||
| `BYOK_API_KEY` | 使用个人的 OpenAI/Anthropic API Key。 |
|
||||
| `github_token` | - | GitHub Copilot 官方 Token (如果您有官方订阅且不方便本地登录时填入)。 |
|
||||
| `llm_base_url` | - | BYOK 模式的基础 URL。填入后将绕过 GitHub 官方服务。 |
|
||||
| `llm_api_key` | - | BYOK 模式的 API 密钥。 |
|
||||
| `llm_model_id` | `gpt-4o` | 使用的模型 ID (官方、BYOK 均适用)。 |
|
||||
| `workspace_root` | `./copilot_workspaces` | 所有会话沙盒的根目录。 |
|
||||
| `skills_directory` | `./copilot_skills` | 自定义 SDK 技能文件夹所在的目录。 |
|
||||
| `show_status` | `True` | 是否在 UI 显示 Agent 的实时运行状态和思考过程。 |
|
||||
| `enable_infinite_session` | `True` | 是否开启自动上下文压缩和 TODO 列表持久化。 |
|
||||
| `enable_html_artifacts` | `True` | 是否允许 Agent 生成并实时预览 HTML 应用。 |
|
||||
| `enable_rich_ui` | `True` | 是否启用进度条和增强型工具调用面板。 |
|
||||
|
||||
---
|
||||
|
||||
### 🌊 细粒度反馈与流畅体验 (Fluid UX)
|
||||
## 🤝 支持 (Support)
|
||||
|
||||
彻底告别复杂任务执行过程中的“卡顿”感:
|
||||
|
||||
- **🔄 实时状态气泡**: 将 SDK 内部事件(如 `turn_start`, `compaction`, `subagent_started`)直接映射为 OpenWebUI 的状态栏信息。
|
||||
- **🧭 分阶段状态描述增强**: 状态栏会明确显示处理阶段(处理中、技能触发、工具执行、工具完成/失败、发布中、任务完成)。
|
||||
- **⏱️ 长任务心跳提示**: 长时间处理中会周期性显示“仍在处理中(已耗时 X 秒)”,避免用户误判为卡死。
|
||||
- **📈 工具执行进度追踪**: 长耗时工具(如代码分析)会在状态栏实时显示进度百分比及当前子任务描述。
|
||||
- **⚡ 即时响应反馈**: 从响应开始第一秒即显示“助手正在处理您的请求...”,减少等待空窗感。
|
||||
如果这个插件对你有帮助,欢迎到 [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) 点个 Star,这将是我持续改进的动力,感谢支持。
|
||||
|
||||
---
|
||||
|
||||
### 🛡️ 智能版本兼容
|
||||
## ⚠️ 故障排除 (Troubleshooting)
|
||||
|
||||
插件会自动根据您的 OpenWebUI 版本调整功能集:
|
||||
|
||||
- **v0.8.0+**: 开启 Rich UI、实时状态气泡及集成 HTML 预览。
|
||||
- **旧版本**: 自动回退至标准 Markdown 代码块模式,确保最大稳定性。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 典型应用场景 (Use Cases)
|
||||
|
||||
- **📁 全自主仓库维护**: Agent 在隔离工作区内自动分析代码、运行测试并应用补丁。
|
||||
- **📊 深度财务数据审计**: 直接通过 Python 加载 Excel/CSV 原始数据(绕过 RAG),生成图表并实时预览。
|
||||
- **📝 长任务项目管理**: 自动拆解复杂任务并持久化 TOD·O 进度,跨会话跟踪执行状态。
|
||||
|
||||
---
|
||||
|
||||
## ⭐ 支持与交流 (Support)
|
||||
|
||||
如果这个插件对您有所帮助,请在 [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) 项目上点个 **Star** 💫,这是对我最大的鼓励。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 安装与配置 (Installation)
|
||||
|
||||
### 1) 导入函数
|
||||
|
||||
1. 打开 OpenWebUI,前往 **工作区** -> **函数**。
|
||||
2. 点击 **+** (创建函数),完整粘贴 `github_copilot_sdk.py` 的内容。
|
||||
3. 点击保存并确保已启用。
|
||||
|
||||
### 2) 获取 Token (Get Token)
|
||||
|
||||
1. 访问 [GitHub Token 设置](https://github.com/settings/tokens?type=beta)。
|
||||
2. 创建 **Fine-grained token**,授予 **Account permissions** -> **Copilot Requests** 访问权限。
|
||||
3. 将生成的 Token 填入插件的 `GH_TOKEN` 配置项中。
|
||||
|
||||
### 3) 认证配置要求(必填)
|
||||
|
||||
你必须至少配置以下一种凭据:
|
||||
|
||||
- `GH_TOKEN`(GitHub Copilot 官方订阅路径),或
|
||||
- `BYOK_API_KEY`(OpenAI/Anthropic 自带 Key 路径)。
|
||||
|
||||
如果两者都未配置,模型列表将不会出现。
|
||||
|
||||
### 4) 配套插件 (强烈推荐)
|
||||
|
||||
为了获得最佳的文件处理体验,请安装 [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)。
|
||||
|
||||
---
|
||||
|
||||
### 📤 增强型发布工具与交互式组件
|
||||
|
||||
`publish_file_from_workspace` 现采用更清晰、可落地的交付规范:
|
||||
|
||||
- **Artifacts 模式(`artifacts`,默认)**:返回 `[Preview]` + `[Download]`,并可附带 `html_embed`,在 ```html 代码块中直接渲染。
|
||||
- **Rich UI 模式(`richui`)**:仅返回 `[Preview]` + `[Download]`,由发射器自动触发集成式预览(消息中不输出 iframe 代码块)。
|
||||
- **📄 PDF 安全交付规则**:仅输出 Markdown 链接(可用时为 `[Preview]` + `[Download]`)。**禁止通过 iframe/html 方式嵌入 PDF。**
|
||||
- **⚡ 稳定双通道发布**:在本地与对象存储后端下,保持交互预览与持久下载链接一致可用。
|
||||
- **✅ 状态集成**:通过 OpenWebUI 状态栏实时反馈发布进度与完成状态。
|
||||
- **📘 发布工具指南(GitHub)**:[publish_file_from_workspace 工具指南(中文)](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/pipes/github-copilot-sdk/PUBLISH_FILE_FROM_WORKSPACE_CN.md)
|
||||
|
||||
---
|
||||
|
||||
### 🧩 OpenWebUI Skills 桥接与 `manage_skills` 工具
|
||||
|
||||
SDK 现在具备与 OpenWebUI **工作区 > Skills** 的双向同步能力:
|
||||
|
||||
- **🔄 自动同步**: 每次请求时,前端定义的技能会自动作为 `SKILL.md` 文件夹同步至 SDK 共享缓存,Agent 可直接调用。
|
||||
- **🛠️ `manage_skills` 工具**: 内置专业工具,赋予 Agent (或用户) 绝对的技能管理权。
|
||||
- `list`: 列出所有已安装技能及描述。
|
||||
- `install`: 从 GitHub URL (自动转换归档链接) 或直接从 `.zip`/`.tar.gz` 安装。
|
||||
- `create`: 从当前会话内容创建新技能目录,支持写入 `SKILL.md` 及辅助资源文件 (脚本、模板)。
|
||||
- `edit`: 更新现有技能文件夹。
|
||||
- `delete`: 原子化删除本地目录及关联的数据库条目,防止僵尸技能复活。
|
||||
- **📁 完整的文件夹支持**: 不同于数据库中单文件存储,SDK 会加载技能的**整个目录**。这使得技能可以携带二进制脚本、数据文件或复杂模板。
|
||||
- **🌐 持久化共享缓存**: 技能存储在 `OPENWEBUI_SKILLS_SHARED_DIR/shared/`,跨会话及容器重启持久存在。
|
||||
- **📚 技能完整文档(GitHub)**: [manage_skills 工具指南(中文)](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/pipes/github-copilot-sdk/SKILLS_MANAGER_CN.md) | [Skills Best Practices(中文)](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/pipes/github-copilot-sdk/SKILLS_BEST_PRACTICES_CN.md)
|
||||
|
||||
---
|
||||
|
||||
## 📋 常见问题与依赖 (Troubleshooting)
|
||||
|
||||
- **Agent 无法识别文件?**: 请确保已安装并启用了 Files Filter 插件,否则原始文件会被 RAG 干扰。
|
||||
- **看不到状态更新或 TODO 进度条?**: 状态气泡会覆盖处理/工具阶段;而 TODO 进度条仅在 Agent 使用 `update_todo` 工具(通常是复杂任务)时出现。
|
||||
- **依赖安装**: 本管道会自动管理 `github-copilot-sdk` (Python 包) 并优先直接使用内置的二进制 CLI,无需手动干预。
|
||||
|
||||
---
|
||||
|
||||
## 更新日志 (Changelog)
|
||||
|
||||
完整历史记录请见 GitHub: [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)
|
||||
- **工具无法使用?** 请检查是否安装了 `github-copilot-sdk`。
|
||||
- **文件找不到?** 确保已启用配套的 `Files Filter` 插件。
|
||||
- **BYOK 报错?** 确认 `llm_base_url` 包含协议前缀(如 `https://`)且模型 ID 准确无误。
|
||||
- **卡在 "Thinking..."?** 检查后端网络连接,流式传输可能受某些代理拦截。
|
||||
|
||||
@@ -4,8 +4,8 @@ author: Fu-Jie
|
||||
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
|
||||
description: A powerful Agent SDK integration for OpenWebUI. It deeply bridges GitHub Copilot SDK with OpenWebUI's ecosystem, enabling the Agent to autonomously perform intent recognition, web search, and context compaction. It seamlessly reuses your existing Tools, MCP servers, OpenAPI servers, and Skills for a professional, full-featured experience.
|
||||
version: 0.9.1
|
||||
requirements: github-copilot-sdk==0.1.25
|
||||
"""
|
||||
|
||||
@@ -425,8 +425,8 @@ class Pipe:
|
||||
"en-US": {
|
||||
"status_conn_est": "Connection established, waiting for response...",
|
||||
"status_reasoning_inj": "Reasoning Effort injected: {effort}",
|
||||
"status_assistant_start": "Assistant is starting to think...",
|
||||
"status_assistant_processing": "Assistant is processing your request...",
|
||||
"status_assistant_start": "Agent is starting to think...",
|
||||
"status_assistant_processing": "Agent is processing your request...",
|
||||
"status_still_working": "Still processing... ({seconds}s elapsed)",
|
||||
"status_skill_invoked": "Detected and using skill: {skill}",
|
||||
"status_tool_using": "Using tool: {name}...",
|
||||
@@ -434,8 +434,8 @@ class Pipe:
|
||||
"status_tool_done": "Tool completed: {name}",
|
||||
"status_tool_failed": "Tool failed: {name}",
|
||||
"status_subagent_start": "Invoking sub-agent: {name}",
|
||||
"status_compaction_start": "Optimizing session context...",
|
||||
"status_compaction_complete": "Context optimization complete.",
|
||||
"status_compaction_start": "Compacting session context...",
|
||||
"status_compaction_complete": "Context compaction complete.",
|
||||
"status_publishing_file": "Publishing artifact: {filename}",
|
||||
"status_task_completed": "Task completed.",
|
||||
"status_session_error": "Processing failed: {error}",
|
||||
@@ -450,8 +450,8 @@ class Pipe:
|
||||
"zh-CN": {
|
||||
"status_conn_est": "已建立连接,等待响应...",
|
||||
"status_reasoning_inj": "已注入推理级别:{effort}",
|
||||
"status_assistant_start": "助手开始思考...",
|
||||
"status_assistant_processing": "助手正在处理您的请求...",
|
||||
"status_assistant_start": "Agent 开始思考...",
|
||||
"status_assistant_processing": "Agent 正在处理您的请求...",
|
||||
"status_still_working": "仍在处理中...(已耗时 {seconds} 秒)",
|
||||
"status_skill_invoked": "已发现并使用技能:{skill}",
|
||||
"status_tool_using": "正在使用工具:{name}...",
|
||||
@@ -459,8 +459,8 @@ class Pipe:
|
||||
"status_tool_done": "工具已完成:{name}",
|
||||
"status_tool_failed": "工具执行失败:{name}",
|
||||
"status_subagent_start": "正在调用子代理:{name}",
|
||||
"status_compaction_start": "正在优化会话上下文...",
|
||||
"status_compaction_complete": "上下文优化完成。",
|
||||
"status_compaction_start": "正在压缩会话上下文...",
|
||||
"status_compaction_complete": "上下文压缩完成。",
|
||||
"status_publishing_file": "正在发布成果物:{filename}",
|
||||
"status_task_completed": "任务已完成。",
|
||||
"status_session_error": "处理失败:{error}",
|
||||
@@ -475,14 +475,14 @@ class Pipe:
|
||||
"zh-HK": {
|
||||
"status_conn_est": "已建立連接,等待響應...",
|
||||
"status_reasoning_inj": "已注入推理級別:{effort}",
|
||||
"status_assistant_start": "助手開始思考...",
|
||||
"status_assistant_processing": "助手正在處理您的請求...",
|
||||
"status_assistant_start": "Agent 開始思考...",
|
||||
"status_assistant_processing": "Agent 正在處理您的請求...",
|
||||
"status_skill_invoked": "已發現並使用技能:{skill}",
|
||||
"status_tool_using": "正在使用工具:{name}...",
|
||||
"status_tool_progress": "工具進度:{name} ({progress}%) {msg}",
|
||||
"status_subagent_start": "正在調用子代理:{name}",
|
||||
"status_compaction_start": "正在優化會話上下文...",
|
||||
"status_compaction_complete": "上下文優化完成。",
|
||||
"status_compaction_start": "正在壓縮會話上下文...",
|
||||
"status_compaction_complete": "上下文壓縮完成。",
|
||||
"status_publishing_file": "正在發布成果物:{filename}",
|
||||
"status_no_skill_invoked": "本輪未觸發任何技能(DEBUG)",
|
||||
"debug_agent_working_in": "Agent 工作目錄: {path}",
|
||||
@@ -495,14 +495,14 @@ class Pipe:
|
||||
"zh-TW": {
|
||||
"status_conn_est": "已建立連接,等待響應...",
|
||||
"status_reasoning_inj": "已注入推理級別:{effort}",
|
||||
"status_assistant_start": "助手開始思考...",
|
||||
"status_assistant_processing": "助手正在處理您的請求...",
|
||||
"status_assistant_start": "Agent 開始思考...",
|
||||
"status_assistant_processing": "Agent 正在處理您的請求...",
|
||||
"status_skill_invoked": "已發現並使用技能:{skill}",
|
||||
"status_tool_using": "正在使用工具:{name}...",
|
||||
"status_tool_progress": "工具進度:{name} ({progress}%) {msg}",
|
||||
"status_subagent_start": "正在調用子代理:{name}",
|
||||
"status_compaction_start": "正在優化會話上下文...",
|
||||
"status_compaction_complete": "上下文優化完成。",
|
||||
"status_compaction_start": "正在壓縮會話上下文...",
|
||||
"status_compaction_complete": "上下文壓縮完成。",
|
||||
"status_publishing_file": "正在發布成果物:{filename}",
|
||||
"status_no_skill_invoked": "本輪未觸發任何技能(DEBUG)",
|
||||
"debug_agent_working_in": "Agent 工作目錄: {path}",
|
||||
@@ -515,7 +515,11 @@ class Pipe:
|
||||
"ja-JP": {
|
||||
"status_conn_est": "接続が確立されました。応答を待っています...",
|
||||
"status_reasoning_inj": "推論レベルが注入されました:{effort}",
|
||||
"status_assistant_start": "Agent が考え始めました...",
|
||||
"status_assistant_processing": "Agent がリクエストを処理しています...",
|
||||
"status_skill_invoked": "スキルが検出され、使用されています:{skill}",
|
||||
"status_compaction_start": "セッションコンテキストを圧縮しています...",
|
||||
"status_compaction_complete": "コンテキストの圧縮が完了しました。",
|
||||
"status_publishing_file": "アーティファクトを公開中:{filename}",
|
||||
"status_no_skill_invoked": "このターンではスキルは呼び出されませんでした (DEBUG)",
|
||||
"debug_agent_working_in": "エージェントの作業ディレクトリ: {path}",
|
||||
@@ -528,7 +532,11 @@ class Pipe:
|
||||
"ko-KR": {
|
||||
"status_conn_est": "연결이 설정되었습니다. 응답을 기다리는 중...",
|
||||
"status_reasoning_inj": "추론 노력이 주입되었습니다: {effort}",
|
||||
"status_assistant_start": "Agent 가 생각을 시작했습니다...",
|
||||
"status_assistant_processing": "Agent 가 요청을 처리 중입니다...",
|
||||
"status_skill_invoked": "스킬이 감지되어 사용 중입니다: {skill}",
|
||||
"status_compaction_start": "세션 컨텍스트를 압축하는 중...",
|
||||
"status_compaction_complete": "컨텍스트 압축 완료.",
|
||||
"status_publishing_file": "아티팩트 게시 중: {filename}",
|
||||
"status_no_skill_invoked": "이 턴에는 스킬이 호출되지 않았습니다 (DEBUG)",
|
||||
"debug_agent_working_in": "에이전트 작업 디렉토리: {path}",
|
||||
@@ -541,7 +549,11 @@ class Pipe:
|
||||
"fr-FR": {
|
||||
"status_conn_est": "Connexion établie, en attente de réponse...",
|
||||
"status_reasoning_inj": "Effort de raisonnement injecté : {effort}",
|
||||
"status_assistant_start": "L'Agent commence à réfléchir...",
|
||||
"status_assistant_processing": "L'Agent traite votre demande...",
|
||||
"status_skill_invoked": "Compétence détectée et utilisée : {skill}",
|
||||
"status_compaction_start": "Compression du contexte de session...",
|
||||
"status_compaction_complete": "Compression du contexte terminée.",
|
||||
"status_publishing_file": "Publication de l'artefact : {filename}",
|
||||
"status_no_skill_invoked": "Aucune compétence invoquée pour ce tour (DEBUG)",
|
||||
"debug_agent_working_in": "Agent travaillant dans : {path}",
|
||||
@@ -554,7 +566,11 @@ class Pipe:
|
||||
"de-DE": {
|
||||
"status_conn_est": "Verbindung hergestellt, warte auf Antwort...",
|
||||
"status_reasoning_inj": "Schlussfolgerungsaufwand injiziert: {effort}",
|
||||
"status_assistant_start": "Agent beginnt zu denken...",
|
||||
"status_assistant_processing": "Agent bearbeitet Ihre Anfrage...",
|
||||
"status_skill_invoked": "Skill erkannt und verwendet: {skill}",
|
||||
"status_compaction_start": "Sitzungskontext wird komprimiert...",
|
||||
"status_compaction_complete": "Kontextkomprimierung abgeschlossen.",
|
||||
"status_publishing_file": "Artifact wird veröffentlicht: {filename}",
|
||||
"status_no_skill_invoked": "In dieser Runde wurde kein Skill aufgerufen (DEBUG)",
|
||||
"debug_agent_working_in": "Agent arbeitet in: {path}",
|
||||
@@ -567,7 +583,11 @@ class Pipe:
|
||||
"it-IT": {
|
||||
"status_conn_est": "Connessione stabilita, in attesa di risposta...",
|
||||
"status_reasoning_inj": "Sforzo di ragionamento iniettato: {effort}",
|
||||
"status_assistant_start": "L'Agent sta iniziando a pensare...",
|
||||
"status_assistant_processing": "L'Agent sta elaborando la tua richiesta...",
|
||||
"status_skill_invoked": "Skill rilevata e utilizzata: {skill}",
|
||||
"status_compaction_start": "Compattazione del contesto della sessione...",
|
||||
"status_compaction_complete": "Compattazione del contesto completata.",
|
||||
"status_publishing_file": "Pubblicazione dell'artefatto: {filename}",
|
||||
"status_no_skill_invoked": "Nessuna skill invocata in questo turno (DEBUG)",
|
||||
"debug_agent_working_in": "Agente al lavoro in: {path}",
|
||||
@@ -580,7 +600,11 @@ class Pipe:
|
||||
"es-ES": {
|
||||
"status_conn_est": "Conexión establecida, esperando respuesta...",
|
||||
"status_reasoning_inj": "Esfuerzo de razonamiento inyectado: {effort}",
|
||||
"status_assistant_start": "El Agent está empezando a pensar...",
|
||||
"status_assistant_processing": "El Agent está procesando su solicitud...",
|
||||
"status_skill_invoked": "Habilidad detectada y utilizada: {skill}",
|
||||
"status_compaction_start": "Compactando el contexto de la sesión...",
|
||||
"status_compaction_complete": "Compactación del contexto completada.",
|
||||
"status_publishing_file": "Publicando artefacto: {filename}",
|
||||
"status_no_skill_invoked": "No se invocó ninguna habilidad en este turno (DEBUG)",
|
||||
"debug_agent_working_in": "Agente trabajando en: {path}",
|
||||
@@ -593,7 +617,11 @@ class Pipe:
|
||||
"vi-VN": {
|
||||
"status_conn_est": "Đã thiết lập kết nối, đang chờ phản hồi...",
|
||||
"status_reasoning_inj": "Nỗ lực suy luận đã được đưa vào: {effort}",
|
||||
"status_assistant_start": "Agent bắt đầu suy nghĩ...",
|
||||
"status_assistant_processing": "Agent đang xử lý yêu cầu của bạn...",
|
||||
"status_skill_invoked": "Kỹ năng đã được phát hiện và sử dụng: {skill}",
|
||||
"status_compaction_start": "Đang nén ngữ cảnh phiên...",
|
||||
"status_compaction_complete": "Nén ngữ cảnh hoàn tất.",
|
||||
"status_publishing_file": "Đang xuất bản thành phẩm: {filename}",
|
||||
"status_no_skill_invoked": "Không có kỹ năng nào được gọi trong lượt này (DEBUG)",
|
||||
"debug_agent_working_in": "Agent đang làm việc tại: {path}",
|
||||
@@ -606,7 +634,11 @@ class Pipe:
|
||||
"id-ID": {
|
||||
"status_conn_est": "Koneksi terjalin, menunggu respons...",
|
||||
"status_reasoning_inj": "Upaya penalaran dimasukkan: {effort}",
|
||||
"status_assistant_start": "Agen mulai berpikir...",
|
||||
"status_assistant_processing": "Agen sedang memproses permintaan Anda...",
|
||||
"status_skill_invoked": "Keahlian terdeteksi và digunakan: {skill}",
|
||||
"status_compaction_start": "Memadatkan konteks sesi...",
|
||||
"status_compaction_complete": "Pemadatan konteks selesai.",
|
||||
"status_publishing_file": "Menerbitkan artefak: {filename}",
|
||||
"status_no_skill_invoked": "Tidak ada keahlian yang dipanggil dalam giliran ini (DEBUG)",
|
||||
"debug_agent_working_in": "Agen bekerja di: {path}",
|
||||
@@ -619,7 +651,11 @@ class Pipe:
|
||||
"ru-RU": {
|
||||
"status_conn_est": "Соединение установлено, ожидание ответа...",
|
||||
"status_reasoning_inj": "Уровень рассуждения внедрен: {effort}",
|
||||
"status_assistant_start": "Agent начинает думать...",
|
||||
"status_assistant_processing": "Agent обрабатывает ваш запрос...",
|
||||
"status_skill_invoked": "Обнаружен и используется навык: {skill}",
|
||||
"status_compaction_start": "Сжатие контекста сеанса...",
|
||||
"status_compaction_complete": "Сжатие контекста завершено.",
|
||||
"status_publishing_file": "Публикация файла: {filename}",
|
||||
"status_no_skill_invoked": "На этом шаге навыки не вызывались (DEBUG)",
|
||||
"debug_agent_working_in": "Рабочий каталог Агента: {path}",
|
||||
@@ -923,9 +959,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 +2226,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 +2321,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 +2441,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 +2478,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 +2509,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 +2554,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 +2711,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 +4560,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"]
|
||||
|
||||
14
plugins/pipes/github-copilot-sdk/v0.9.1.md
Normal file
14
plugins/pipes/github-copilot-sdk/v0.9.1.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# GitHub Copilot SDK Pipe v0.9.1 Release Notes
|
||||
|
||||
## Updated
|
||||
- Prioritize autonomous web search by always enabling `web_search` for Agent-side decision making.
|
||||
- Add explicit ecosystem injection messaging for OpenWebUI Tools, MCP servers, OpenAPI servers, and Skills.
|
||||
- Improve interactive delivery guidance for HTML Artifacts and RichUI workflows.
|
||||
- Add recommendation for installing the Visual Explainer skill for high-quality visual rendering.
|
||||
|
||||
## Fixed
|
||||
- Fix MCP tool filtering reliability when `function_name_filter_list` or UI-selected tool scopes are used.
|
||||
- Normalize tool ID handling to avoid accidental filtering failures with `server:mcp:` prefixes.
|
||||
- Standardize TODO terminology presentation to avoid malformed text artifacts.
|
||||
- Remove duplicated language-consistency statements in both English and Chinese docs while keeping the intended single statement.
|
||||
- Correct Indonesian status wording (`Agén` -> `Agen`) for better locale consistency.
|
||||
14
plugins/pipes/github-copilot-sdk/v0.9.1_CN.md
Normal file
14
plugins/pipes/github-copilot-sdk/v0.9.1_CN.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# GitHub Copilot SDK Pipe v0.9.1 发布说明
|
||||
|
||||
## 更新内容
|
||||
- 将 `web_search` 设为 Agent 侧始终可用,优先发挥自主网页搜索能力。
|
||||
- 明确强调对 OpenWebUI 工具、MCP、OpenAPI Server 与 Skills 的生态注入与复用能力。
|
||||
- 完善 HTML Artifacts 与 RichUI 的交互交付说明。
|
||||
- 增加 Visual Explainer 技能安装建议,用于提升可视化渲染质量。
|
||||
|
||||
## 修复内容
|
||||
- 修复在使用 `function_name_filter_list` 或聊天界面选择工具时的 MCP 工具过滤失效问题。
|
||||
- 修复 `server:mcp:` 前缀场景下的工具 ID 归一化逻辑,避免误过滤。
|
||||
- 统一 TODO 术语展示,消除异常字符导致的显示问题。
|
||||
- 去除中英文文档中的“语言一致性”重复条目,保留单一有效描述。
|
||||
- 修正印尼语状态文案(`Agén` -> `Agen`),提升多语言一致性。
|
||||
39
plugins/pipes/iflow-sdk-pipe/README.md
Normal file
39
plugins/pipes/iflow-sdk-pipe/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# iFlow Official SDK Pipe
|
||||
|
||||
This plugin integrates the [iFlow SDK](https://platform.iflow.cn/cli/sdk/sdk-python) into OpenWebUI as a `Pipe`.
|
||||
|
||||
## Features
|
||||
|
||||
- **Standard iFlow Integration**: Connects to the iFlow CLI process via WebSocket (ACP).
|
||||
- **Auto-Process Management**: Automatically starts the iFlow process if it's not running.
|
||||
- **Streaming Support**: Direct streaming from iFlow to the chat interface.
|
||||
- **Status Updates**: Real-time status updates in the UI (thinking, tool usage, etc.).
|
||||
- **Tool Execution Visibility**: See when iFlow is calling and completing tools.
|
||||
|
||||
## Configuration
|
||||
|
||||
Set the following `Valves`:
|
||||
|
||||
- `IFLOW_PORT`: The port for the iFlow CLI process (default: `8090`).
|
||||
- `IFLOW_URL`: The WebSocket URL (default: `ws://localhost:8090/acp`).
|
||||
- `AUTO_START`: Automatically start the process (default: `True`).
|
||||
- `TIMEOUT`: Request timeout in seconds.
|
||||
- `LOG_LEVEL`: SDK logging level (DEBUG, INFO, etc.).
|
||||
|
||||
## Installation
|
||||
|
||||
This plugin requires both the **iFlow CLI** binary and the **iflow-cli-sdk** Python package.
|
||||
|
||||
### 1. Install iFlow CLI (System level)
|
||||
|
||||
Run the following command in your terminal (Linux/macOS):
|
||||
|
||||
```bash
|
||||
bash -c "$(curl -fsSL https://platform.iflow.cn/cli/install.sh)"
|
||||
```
|
||||
|
||||
### 2. Install Python SDK (OpenWebUI environment)
|
||||
|
||||
```bash
|
||||
pip install iflow-cli-sdk
|
||||
```
|
||||
37
plugins/pipes/iflow-sdk-pipe/README_CN.md
Normal file
37
plugins/pipes/iflow-sdk-pipe/README_CN.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# iFlow 官方 SDK Pipe 插件
|
||||
|
||||
此插件将 [iFlow SDK](https://platform.iflow.cn/cli/sdk/sdk-python) 集成到 OpenWebUI 中。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- **标准 iFlow 集成**:通过 WebSocket (ACP) 连接到 iFlow CLI 进程。
|
||||
- **自动进程管理**:如果 iFlow 进程未运行,将自动启动。
|
||||
- **流式输出支持**:支持从 iFlow 到聊天界面的实时流式输出。
|
||||
- **实时状态更新**:在 UI 中实时显示助手状态(思考中、工具调用等)。
|
||||
- **工具调用可视化**:实时反馈 iFlow 调用及完成工具的过程。
|
||||
|
||||
## 配置项 (Valves)
|
||||
|
||||
- `IFLOW_PORT`:iFlow CLI 进程端口(默认:`8090`)。
|
||||
- `IFLOW_URL`:WebSocket 地址(默认:`ws://localhost:8090/acp`)。
|
||||
- `AUTO_START`:是否自动启动进程(默认:`True`)。
|
||||
- `TIMEOUT`:请求超时时间(秒)。
|
||||
- `LOG_LEVEL`:SDK 日志级别(DEBUG, INFO 等)。
|
||||
|
||||
## 安装说明
|
||||
|
||||
此插件同时依赖 **iFlow CLI** 二进制文件和 **iflow-cli-sdk** Python 包。
|
||||
|
||||
### 1. 安装 iFlow CLI (系统层级)
|
||||
|
||||
在系统中执行以下命令(适用于 Linux/macOS):
|
||||
|
||||
```bash
|
||||
bash -c "$(curl -fsSL https://gitee.com/iflow-ai/iflow-cli/raw/main/install.sh)"
|
||||
```
|
||||
|
||||
### 2. 安装 Python SDK (OpenWebUI 环境)
|
||||
|
||||
```bash
|
||||
pip install iflow-cli-sdk
|
||||
```
|
||||
544
plugins/pipes/iflow-sdk-pipe/iflow_sdk_pipe.py
Normal file
544
plugins/pipes/iflow-sdk-pipe/iflow_sdk_pipe.py
Normal file
@@ -0,0 +1,544 @@
|
||||
"""
|
||||
title: iFlow Official SDK Pipe
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie/openwebui-extensions
|
||||
funding_url: https://github.com/open-webui
|
||||
description: Integrate iFlow SDK. Supports dynamic models, multi-turn conversation, streaming, tool execution, and task planning.
|
||||
version: 0.1.2
|
||||
requirements: iflow-cli-sdk==0.1.11
|
||||
"""
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import os
|
||||
import json
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Optional, Union, AsyncGenerator, List, Any, Dict, Literal
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
# Setup logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Import iflow SDK modules with safety
|
||||
IFlowClient = None
|
||||
IFlowOptions = None
|
||||
AssistantMessage = None
|
||||
TaskFinishMessage = None
|
||||
ToolCallMessage = None
|
||||
PlanMessage = None
|
||||
TaskStatusMessage = None
|
||||
ApprovalMode = None
|
||||
StopReason = None
|
||||
|
||||
try:
|
||||
from iflow_sdk import (
|
||||
IFlowClient,
|
||||
IFlowOptions,
|
||||
AssistantMessage,
|
||||
TaskFinishMessage,
|
||||
ToolCallMessage,
|
||||
PlanMessage,
|
||||
TaskStatusMessage,
|
||||
ApprovalMode,
|
||||
StopReason,
|
||||
)
|
||||
except ImportError:
|
||||
logger.error(
|
||||
"iflow-cli-sdk not found. Please install it with 'pip install iflow-cli-sdk'."
|
||||
)
|
||||
|
||||
# Base guidelines for all users, adapted for iFlow
|
||||
BASE_GUIDELINES = (
|
||||
"\n\n[Environment & Capabilities Context]\n"
|
||||
"You are an AI assistant operating within a high-capability Linux container environment (OpenWebUI) powered by **iFlow CLI**.\n"
|
||||
"\n"
|
||||
"**System Environment & User Privileges:**\n"
|
||||
"- **Output Environment**: You are rendering in the **OpenWebUI Chat Page**. Optimize your output format to leverage Markdown for the best UI experience.\n"
|
||||
"- **Root Access**: You are running as **root**. You have **READ access to the entire container file system**. You **MUST ONLY WRITE** to your designated persistent workspace directory.\n"
|
||||
"- **STRICT FILE CREATION RULE**: You are **PROHIBITED** from creating or editing files outside of your specific workspace path. Never place files in `/root`, `/tmp`, or `/app`. All operations must use the absolute path provided in your session context.\n"
|
||||
"- **iFlow Task Planning**: You possess **Task Planning** capabilities. When faced with complex requests, you SHOULD generate a structured plan. The iFlow SDK will visualize this plan as a task list for the user.\n"
|
||||
"- **Tool Execution (ACP)**: You interact with tools via the **Agent Control Protocol (ACP)**. Depending on the `ApprovalMode`, your tool calls may be executed automatically or require user confirmation.\n"
|
||||
"- **Rich Python Environment**: You can natively import and use any installed OpenWebUI dependencies.\n"
|
||||
"\n"
|
||||
"**Formatting & Presentation Directives:**\n"
|
||||
"1. **Markdown Excellence**: Leverage headers, tables, and lists to structure your response professionally.\n"
|
||||
"2. **Advanced Visualization**: Use **Mermaid** for diagrams and **LaTeX** for math. Always wrap Mermaid in standard ```mermaid blocks.\n"
|
||||
"3. **Interactive Artifacts (HTML)**: **Premium Delivery Protocol**: For web applications, you MUST:\n"
|
||||
" - 1. **Persist**: Create the file in the workspace (e.g., `index.html`).\n"
|
||||
" - 2. **Publish**: Call `publish_file_from_workspace(filename='your_file.html')` (via provided tools if available). This triggers the premium embedded experience.\n"
|
||||
" - **CRITICAL**: Never output raw HTML source code directly in the chat. Persist and publish.\n"
|
||||
"4. **Media & Files**: ALWAYS embed generated media using ``. Never provide plain text links for images/videos.\n"
|
||||
"5. **Dual-Channel Delivery**: Always aim to provide both an instant visual Insight in the chat AND a persistent downloadable file.\n"
|
||||
"6. **Active & Autonomous**: Analyze the user's request -> Formulate a plan -> **EXECUTE** the plan immediately. Minimize user friction.\n"
|
||||
)
|
||||
|
||||
# Sensitive extensions only for Administrators
|
||||
ADMIN_EXTENSIONS = (
|
||||
"\n**[ADMINISTRATOR PRIVILEGES - CONFIDENTIAL]**\n"
|
||||
"Current user is an **ADMINISTRATOR**. Restricted access is lifted:\n"
|
||||
"- **Full OS Interaction**: You can use shell tools to analyze any container process or system configuration.\n"
|
||||
"- **Database Access**: You can connect to the **OpenWebUI Database** using credentials in environment variables.\n"
|
||||
"- **iFlow CLI Debugging**: You can inspect iFlow configuration and logs for diagnostic purposes.\n"
|
||||
"**SECURITY NOTE**: Protect sensitive internal details.\n"
|
||||
)
|
||||
|
||||
# Strict restrictions for regular Users
|
||||
USER_RESTRICTIONS = (
|
||||
"\n**[USER ACCESS RESTRICTIONS - STRICT]**\n"
|
||||
"Current user is a **REGULAR USER**. Adhere to boundaries:\n"
|
||||
"- **NO Environment Access**: FORBIDDEN from accessing environment variables (e.g., via `env` or `os.environ`).\n"
|
||||
"- **NO Database Access**: MUST NOT attempt to connect to OpenWebUI database.\n"
|
||||
"- **NO Writing Outside Workspace**: All artifacts MUST be saved strictly inside the isolated workspace path provided.\n"
|
||||
"- **Restricted Shell**: Use shell tools ONLY for operations within your isolated workspace. Do NOT explore system secrets.\n"
|
||||
)
|
||||
|
||||
|
||||
class Pipe:
|
||||
class Valves(BaseModel):
|
||||
IFLOW_PORT: int = Field(
|
||||
default=8090,
|
||||
description="Port for iFlow CLI process.",
|
||||
)
|
||||
IFLOW_URL: str = Field(
|
||||
default="ws://localhost:8090/acp",
|
||||
description="WebSocket URL for iFlow ACP.",
|
||||
)
|
||||
AUTO_START: bool = Field(
|
||||
default=True,
|
||||
description="Whether to automatically start the iFlow process.",
|
||||
)
|
||||
TIMEOUT: float = Field(
|
||||
default=300.0,
|
||||
description="Timeout for the message request (seconds).",
|
||||
)
|
||||
LOG_LEVEL: str = Field(
|
||||
default="INFO",
|
||||
description="Log level for iFlow SDK (DEBUG, INFO, WARNING, ERROR).",
|
||||
)
|
||||
CWD: str = Field(
|
||||
default="",
|
||||
description="CLI operation working directory. Empty for default.",
|
||||
)
|
||||
APPROVAL_MODE: Literal["DEFAULT", "AUTO_EDIT", "YOLO", "PLAN"] = Field(
|
||||
default="YOLO",
|
||||
description="Tool execution permission mode.",
|
||||
)
|
||||
FILE_ACCESS: bool = Field(
|
||||
default=False,
|
||||
description="Enable file system access (disabled by default for security).",
|
||||
)
|
||||
AUTO_INSTALL_CLI: bool = Field(
|
||||
default=True,
|
||||
description="Automatically install iFlow CLI if not found in PATH.",
|
||||
)
|
||||
IFLOW_BIN_DIR: str = Field(
|
||||
default="/app/backend/data/bin",
|
||||
description="Fixed path for iFlow CLI binary (recommended for persistence in Docker).",
|
||||
)
|
||||
|
||||
# Auth Config
|
||||
SELECTED_AUTH_TYPE: Literal["iflow", "openai-compatible"] = Field(
|
||||
default="iflow",
|
||||
description="Authentication type. 'iflow' for native, 'openai-compatible' for others.",
|
||||
)
|
||||
AUTH_API_KEY: str = Field(
|
||||
default="",
|
||||
description="API Key for the model provider.",
|
||||
)
|
||||
AUTH_BASE_URL: str = Field(
|
||||
default="",
|
||||
description="Base URL for the model provider.",
|
||||
)
|
||||
AUTH_MODEL: str = Field(
|
||||
default="",
|
||||
description="Model name to use.",
|
||||
)
|
||||
SYSTEM_PROMPT: str = Field(
|
||||
default="",
|
||||
description="System prompt to guide the AI's behavior.",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.type = "pipe"
|
||||
self.id = "iflow_sdk"
|
||||
self.name = "iflow"
|
||||
self.valves = self.Valves()
|
||||
|
||||
def _get_user_role(self, __user__: dict) -> str:
|
||||
"""Determine if the user is an admin."""
|
||||
return __user__.get("role", "user")
|
||||
|
||||
def _get_system_prompt(self, role: str) -> str:
|
||||
"""Construct the dynamic system prompt based on user role."""
|
||||
prompt = self.valves.SYSTEM_PROMPT if self.valves.SYSTEM_PROMPT else ""
|
||||
prompt += BASE_GUIDELINES
|
||||
if role == "admin":
|
||||
prompt += ADMIN_EXTENSIONS
|
||||
else:
|
||||
prompt += USER_RESTRICTIONS
|
||||
return prompt
|
||||
|
||||
async def _ensure_cli(self, _emit_status) -> bool:
|
||||
"""Check for iFlow CLI and attempt installation if missing."""
|
||||
|
||||
async def _check_binary(name: str) -> Optional[str]:
|
||||
# 1. Check in system PATH
|
||||
path = shutil.which(name)
|
||||
if path:
|
||||
return path
|
||||
|
||||
# 2. Compile potential search paths
|
||||
search_paths = []
|
||||
|
||||
# Try to resolve NPM global prefix
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"npm",
|
||||
"config",
|
||||
"get",
|
||||
"prefix",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, _ = await proc.communicate()
|
||||
if proc.returncode == 0:
|
||||
prefix = stdout.decode().strip()
|
||||
search_paths.extend(
|
||||
[
|
||||
os.path.join(prefix, "bin"),
|
||||
os.path.join(prefix, "node_modules", ".bin"),
|
||||
prefix,
|
||||
]
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
if self.valves.IFLOW_BIN_DIR:
|
||||
search_paths.extend(
|
||||
[
|
||||
self.valves.IFLOW_BIN_DIR,
|
||||
os.path.join(self.valves.IFLOW_BIN_DIR, "bin"),
|
||||
]
|
||||
)
|
||||
|
||||
# Common/default locations
|
||||
search_paths.extend(
|
||||
[
|
||||
os.path.expanduser("~/.iflow/bin"),
|
||||
os.path.expanduser("~/.npm-global/bin"),
|
||||
os.path.expanduser("~/.local/bin"),
|
||||
"/usr/local/bin",
|
||||
"/usr/bin",
|
||||
"/bin",
|
||||
os.path.expanduser("~/bin"),
|
||||
]
|
||||
)
|
||||
|
||||
for p in search_paths:
|
||||
full_path = os.path.join(p, name)
|
||||
if os.path.exists(full_path) and os.access(full_path, os.X_OK):
|
||||
return full_path
|
||||
return None
|
||||
|
||||
# Initial check
|
||||
binary_path = await _check_binary("iflow")
|
||||
if binary_path:
|
||||
logger.info(f"iFlow CLI found at: {binary_path}")
|
||||
bin_dir = os.path.dirname(binary_path)
|
||||
if bin_dir not in os.environ["PATH"]:
|
||||
os.environ["PATH"] = f"{bin_dir}:{os.environ['PATH']}"
|
||||
return True
|
||||
|
||||
if not self.valves.AUTO_INSTALL_CLI:
|
||||
return False
|
||||
|
||||
try:
|
||||
install_loc_msg = (
|
||||
self.valves.IFLOW_BIN_DIR
|
||||
if self.valves.IFLOW_BIN_DIR
|
||||
else "default location"
|
||||
)
|
||||
await _emit_status(
|
||||
f"iFlow CLI not found. Attempting auto-installation to {install_loc_msg}..."
|
||||
)
|
||||
|
||||
# Detection for package managers and official script
|
||||
env = os.environ.copy()
|
||||
has_npm = shutil.which("npm") is not None
|
||||
has_curl = shutil.which("curl") is not None
|
||||
|
||||
if has_npm:
|
||||
if self.valves.IFLOW_BIN_DIR:
|
||||
os.makedirs(self.valves.IFLOW_BIN_DIR, exist_ok=True)
|
||||
install_cmd = f"npm i -g --prefix {self.valves.IFLOW_BIN_DIR} @iflow-ai/iflow-cli@latest"
|
||||
else:
|
||||
install_cmd = "npm i -g @iflow-ai/iflow-cli@latest"
|
||||
elif has_curl:
|
||||
await _emit_status(
|
||||
"npm not found. Attempting to use official shell installer via curl..."
|
||||
)
|
||||
# Official installer script from gitee/github as fallback
|
||||
# We try gitee first as it's more reliable in some environments
|
||||
install_cmd = 'bash -c "$(curl -fsSL https://gitee.com/iflow-ai/iflow-cli/raw/main/install.sh)"'
|
||||
# If we have a custom bin dir, try to tell the installer (though it might not support it)
|
||||
if self.valves.IFLOW_BIN_DIR:
|
||||
env["IFLOW_BIN_DIR"] = self.valves.IFLOW_BIN_DIR
|
||||
else:
|
||||
await _emit_status(
|
||||
"Error: Neither 'npm' nor 'curl' found. Cannot proceed with auto-installation."
|
||||
)
|
||||
return False
|
||||
|
||||
process = await asyncio.create_subprocess_shell(
|
||||
install_cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
env=env,
|
||||
)
|
||||
stdout_data, stderr_data = await process.communicate()
|
||||
|
||||
# Even if the script returns non-zero (which it might if it tries to
|
||||
# start an interactive shell at the end), we check if the binary exists.
|
||||
await _emit_status(
|
||||
"Installation script finished. Finalizing verification..."
|
||||
)
|
||||
binary_path = await _check_binary("iflow")
|
||||
|
||||
if binary_path:
|
||||
try:
|
||||
os.chmod(binary_path, 0o755)
|
||||
except:
|
||||
pass
|
||||
await _emit_status(f"iFlow CLI confirmed at {binary_path}.")
|
||||
bin_dir = os.path.dirname(binary_path)
|
||||
if bin_dir not in os.environ["PATH"]:
|
||||
os.environ["PATH"] = f"{bin_dir}:{os.environ['PATH']}"
|
||||
return True
|
||||
else:
|
||||
# Script failed and no binary
|
||||
error_msg = (
|
||||
stderr_data.decode().strip() or "Binary not found in search paths"
|
||||
)
|
||||
logger.error(
|
||||
f"Installation failed with code {process.returncode}: {error_msg}"
|
||||
)
|
||||
await _emit_status(f"Installation failed: {error_msg}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Error during installation: {str(e)}")
|
||||
await _emit_status(f"Installation error: {str(e)}")
|
||||
return False
|
||||
|
||||
async def _ensure_sdk(self, _emit_status) -> bool:
|
||||
"""Check for iflow-cli-sdk Python package and attempt installation if missing."""
|
||||
global IFlowClient, IFlowOptions, AssistantMessage, TaskFinishMessage, ToolCallMessage, PlanMessage, TaskStatusMessage, ApprovalMode, StopReason
|
||||
|
||||
if IFlowClient is not None:
|
||||
return True
|
||||
|
||||
await _emit_status("iflow-cli-sdk not found. Attempting auto-installation...")
|
||||
try:
|
||||
# Use sys.executable to ensure we use the same Python environment
|
||||
import sys
|
||||
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
sys.executable,
|
||||
"-m",
|
||||
"pip",
|
||||
"install",
|
||||
"iflow-cli-sdk",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, stderr = await process.communicate()
|
||||
|
||||
if process.returncode == 0:
|
||||
await _emit_status("iflow-cli-sdk installed successfully. Loading...")
|
||||
# Try to import again
|
||||
from iflow_sdk import (
|
||||
IFlowClient as C,
|
||||
IFlowOptions as O,
|
||||
AssistantMessage as AM,
|
||||
TaskFinishMessage as TM,
|
||||
ToolCallMessage as TC,
|
||||
PlanMessage as P,
|
||||
TaskStatusMessage as TS,
|
||||
ApprovalMode as AP,
|
||||
StopReason as SR,
|
||||
)
|
||||
|
||||
# Update global pointers
|
||||
IFlowClient, IFlowOptions = C, O
|
||||
AssistantMessage, TaskFinishMessage = AM, TM
|
||||
ToolCallMessage, PlanMessage = TC, P
|
||||
TaskStatusMessage, ApprovalMode, StopReason = TS, AP, SR
|
||||
return True
|
||||
else:
|
||||
error_msg = stderr.decode().strip()
|
||||
logger.error(f"SDK installation failed: {error_msg}")
|
||||
await _emit_status(f"SDK installation failed: {error_msg}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Error during SDK installation: {str(e)}")
|
||||
await _emit_status(f"SDK installation error: {str(e)}")
|
||||
return False
|
||||
|
||||
async def pipe(
|
||||
self, body: dict, __user__: dict, __event_emitter__=None
|
||||
) -> Union[str, AsyncGenerator[str, None]]:
|
||||
"""Main entry point for the pipe."""
|
||||
|
||||
async def _emit_status(description: str, done: bool = False):
|
||||
if __event_emitter__:
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "status",
|
||||
"data": {
|
||||
"description": description,
|
||||
"done": done,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# 0. Ensure SDK and CLI are available
|
||||
if not await self._ensure_sdk(_emit_status):
|
||||
return "Error: iflow-cli-sdk (Python package) missing and auto-installation failed. Please install it with `pip install iflow-cli-sdk` manually."
|
||||
|
||||
# 1. Update PATH to include custom bin dir
|
||||
if self.valves.IFLOW_BIN_DIR not in os.environ["PATH"]:
|
||||
os.environ["PATH"] = f"{self.valves.IFLOW_BIN_DIR}:{os.environ['PATH']}"
|
||||
|
||||
# 2. Ensure CLI is installed and path is updated
|
||||
if not await self._ensure_cli(_emit_status):
|
||||
return f"Error: iFlow CLI not found and auto-installation failed. Please install it to {self.valves.IFLOW_BIN_DIR} manually."
|
||||
|
||||
messages = body.get("messages", [])
|
||||
if not messages:
|
||||
return "No messages provided."
|
||||
|
||||
# Get the last user message
|
||||
last_message = messages[-1]
|
||||
content = last_message.get("content", "")
|
||||
|
||||
# Determine user role and construct prompt
|
||||
role = self._get_user_role(__user__)
|
||||
dynamic_prompt = self._get_system_prompt(role)
|
||||
|
||||
# Prepare Auth Info
|
||||
auth_info = None
|
||||
if self.valves.AUTH_API_KEY:
|
||||
auth_info = {
|
||||
"api_key": self.valves.AUTH_API_KEY,
|
||||
"base_url": self.valves.AUTH_BASE_URL,
|
||||
"model_name": self.valves.AUTH_MODEL,
|
||||
}
|
||||
|
||||
# Prepare Session Settings
|
||||
session_settings = None
|
||||
try:
|
||||
from iflow_sdk import SessionSettings
|
||||
|
||||
session_settings = SessionSettings(system_prompt=dynamic_prompt)
|
||||
except ImportError:
|
||||
session_settings = {"system_prompt": dynamic_prompt}
|
||||
|
||||
# 2. Configure iFlow Options
|
||||
# Use local references to ensure we're using the freshly imported SDK components
|
||||
from iflow_sdk import (
|
||||
IFlowOptions as SDKOptions,
|
||||
ApprovalMode as SDKApprovalMode,
|
||||
)
|
||||
|
||||
# Get approval mode with a safe fallback
|
||||
try:
|
||||
target_mode = getattr(SDKApprovalMode, self.valves.APPROVAL_MODE)
|
||||
except (AttributeError, TypeError):
|
||||
target_mode = (
|
||||
SDKApprovalMode.YOLO if hasattr(SDKApprovalMode, "YOLO") else None
|
||||
)
|
||||
|
||||
options = SDKOptions(
|
||||
url=self.valves.IFLOW_URL,
|
||||
auto_start_process=self.valves.AUTO_START,
|
||||
process_start_port=self.valves.IFLOW_PORT,
|
||||
timeout=self.valves.TIMEOUT,
|
||||
log_level=self.valves.LOG_LEVEL,
|
||||
cwd=self.valves.CWD or None,
|
||||
approval_mode=target_mode,
|
||||
file_access=self.valves.FILE_ACCESS,
|
||||
auth_method_id=self.valves.SELECTED_AUTH_TYPE if auth_info else None,
|
||||
auth_method_info=auth_info,
|
||||
session_settings=session_settings,
|
||||
)
|
||||
|
||||
async def _emit_status(description: str, done: bool = False):
|
||||
if __event_emitter__:
|
||||
await __event_emitter__(
|
||||
{
|
||||
"type": "status",
|
||||
"data": {
|
||||
"description": description,
|
||||
"done": done,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# 3. Stream from iFlow
|
||||
async def stream_generator():
|
||||
try:
|
||||
await _emit_status("Initializing iFlow connection...")
|
||||
|
||||
async with IFlowClient(options) as client:
|
||||
await client.send_message(content)
|
||||
|
||||
await _emit_status("iFlow is processing...")
|
||||
|
||||
async for message in client.receive_messages():
|
||||
if isinstance(message, AssistantMessage):
|
||||
yield message.chunk.text
|
||||
if message.agent_info and message.agent_info.agent_id:
|
||||
logger.debug(
|
||||
f"Message from agent: {message.agent_info.agent_id}"
|
||||
)
|
||||
|
||||
elif isinstance(message, PlanMessage):
|
||||
plan_str = "\n".join(
|
||||
[
|
||||
f"{'✅' if e.status == 'completed' else '⏳'} [{e.priority}] {e.content}"
|
||||
for e in message.entries
|
||||
]
|
||||
)
|
||||
await _emit_status(f"Execution Plan updated:\n{plan_str}")
|
||||
|
||||
elif isinstance(message, TaskStatusMessage):
|
||||
await _emit_status(f"iFlow: {message.status}")
|
||||
|
||||
elif isinstance(message, ToolCallMessage):
|
||||
tool_desc = (
|
||||
f"Calling tool: {message.tool_name}"
|
||||
if message.tool_name
|
||||
else "Invoking tool"
|
||||
)
|
||||
await _emit_status(
|
||||
f"{tool_desc}... (Status: {message.status})"
|
||||
)
|
||||
|
||||
elif isinstance(message, TaskFinishMessage):
|
||||
reason_msg = "Task completed."
|
||||
if message.stop_reason == StopReason.MAX_TOKENS:
|
||||
reason_msg = "Task stopped: Max tokens reached."
|
||||
elif message.stop_reason == StopReason.END_TURN:
|
||||
reason_msg = "Task completed successfully."
|
||||
|
||||
await _emit_status(reason_msg, done=True)
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in iFlow pipe: {str(e)}", exc_info=True)
|
||||
error_msg = f"iFlow Error: {str(e)}"
|
||||
yield error_msg
|
||||
await _emit_status(error_msg, done=True)
|
||||
|
||||
return stream_generator()
|
||||
@@ -19,7 +19,7 @@ A standalone OpenWebUI Tool plugin to manage native **Workspace > Skills** for a
|
||||
## How to Use
|
||||
|
||||
1. Open OpenWebUI and go to **Workspace > Tools**.
|
||||
2. Create a new Tool and paste `openwebui_skills_manager.py`.
|
||||
2. Install **OpenWebUI Skills Manager Tool** from the official marketplace.
|
||||
3. Enable this tool for your model/chat.
|
||||
4. Ask the model to call tool operations, for example:
|
||||
- "List my skills"
|
||||
@@ -28,18 +28,22 @@ A standalone OpenWebUI Tool plugin to manage native **Workspace > Skills** for a
|
||||
- "Update skill ..."
|
||||
- "Delete skill ..."
|
||||
|
||||
### Manual Installation (Alternative)
|
||||
|
||||
- Create a new Tool and paste `openwebui_skills_manager.py`.
|
||||
|
||||
## Example: Install Skills
|
||||
|
||||
This tool can fetch and install skills directly from URLs (supporting GitHub tree/blob, raw markdown, and .zip/.tar archives).
|
||||
|
||||
### Install a single skill from GitHub
|
||||
|
||||
- "Install skill from <https://github.com/anthropics/skills/tree/main/skills/search_manager>"
|
||||
- "Install skill from <https://github.com/anthropics/skills/tree/main/skills/xlsx>"
|
||||
- "Install skill from <https://github.com/Fu-Jie/openwebui-extensions/blob/main/.agent/skills/test-copilot-pipe/SKILL.md>"
|
||||
|
||||
### Batch install multiple skills
|
||||
|
||||
- "Install these skills: ['https://github.com/anthropics/skills/tree/main/skills/search_manager', 'https://github.com/anthropics/skills/tree/main/skills/guide_writer']"
|
||||
- "Install these skills: ['https://github.com/anthropics/skills/tree/main/skills/xlsx', 'https://github.com/anthropics/skills/tree/main/skills/docx']"
|
||||
|
||||
> **Tip**: For GitHub, the tool automatically resolves directory (tree) URLs by looking for `SKILL.md` or `README.md`.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
## 使用方法
|
||||
|
||||
1. 打开 OpenWebUI,进入 **Workspace > Tools**。
|
||||
2. 新建 Tool,粘贴 `openwebui_skills_manager.py`。
|
||||
2. 在官方市场安装 **OpenWebUI Skills 管理工具**。
|
||||
3. 为当前模型/聊天启用该工具。
|
||||
4. 在对话中让模型调用,例如:
|
||||
- “列出我的 skills”
|
||||
@@ -28,18 +28,22 @@
|
||||
- “更新某个 skill ...”
|
||||
- “删除某个 skill ...”
|
||||
|
||||
### 手动安装(备选)
|
||||
|
||||
- 新建 Tool,粘贴 `openwebui_skills_manager.py`。
|
||||
|
||||
## 示例:安装技能 (Install Skills)
|
||||
|
||||
该工具支持从 URL 直接抓取并安装技能(支持 GitHub tree/blob 链接、原始 Markdown 链接以及 .zip/.tar 压缩包)。
|
||||
|
||||
### 从 GitHub 安装单个技能
|
||||
|
||||
- “从 <https://github.com/anthropics/skills/tree/main/skills/search_manager> 安装技能”
|
||||
- “从 <https://github.com/anthropics/skills/tree/main/skills/xlsx> 安装技能”
|
||||
- “安装技能 <https://github.com/Fu-Jie/openwebui-extensions/blob/main/.agent/skills/test-copilot-pipe/SKILL.md”>
|
||||
|
||||
### 批量安装多个技能
|
||||
|
||||
- “安装这些技能:['https://github.com/anthropics/skills/tree/main/skills/search_manager', 'https://github.com/anthropics/skills/tree/main/skills/guide_writer']”
|
||||
- “安装这些技能:['https://github.com/anthropics/skills/tree/main/skills/xlsx', 'https://github.com/anthropics/skills/tree/main/skills/docx']”
|
||||
|
||||
> **提示**:对于 GitHub 链接,工具会自动处理目录(tree)地址,并尝试查找目录下的 `SKILL.md` 或 `README.md` 文件。
|
||||
|
||||
|
||||
241
plugins/tools/smart-mind-map-tool/smart_mind_map_tool.py
Normal file
241
plugins/tools/smart-mind-map-tool/smart_mind_map_tool.py
Normal file
@@ -0,0 +1,241 @@
|
||||
"""
|
||||
title: Smart Mind Map Tool
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie/openwebui-extensions
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 1.1.0
|
||||
description: Intelligently analyzes text content and generates interactive mind maps inline to help users structure and visualize knowledge.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Callable, Awaitable, Dict, Optional
|
||||
|
||||
from fastapi import Request
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from open_webui.utils.chat import generate_chat_completion
|
||||
from open_webui.models.users import Users
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Tools:
|
||||
class Valves(BaseModel):
|
||||
MODEL_ID: str = Field(default="", description="The model ID to use for mind map generation. If empty, uses the current conversation model.")
|
||||
MIN_TEXT_LENGTH: int = Field(default=50, description="Minimum text length required for analysis.")
|
||||
SHOW_STATUS: bool = Field(default=True, description="Whether to show status messages.")
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
self.__translations = {
|
||||
"en-US": {
|
||||
"status_analyzing": "Smart Mind Map: Analyzing text structure...",
|
||||
"status_drawing": "Smart Mind Map: Drawing completed!",
|
||||
"notification_success": "Mind map has been generated, {user_name}!",
|
||||
"error_text_too_short": "Text content is too short ({len} characters). Min: {min_len}.",
|
||||
"error_user_facing": "Sorry, Smart Mind Map encountered an error: {error}",
|
||||
"status_failed": "Smart Mind Map: Failed.",
|
||||
"ui_title": "🧠 Smart Mind Map",
|
||||
"ui_download_png": "PNG",
|
||||
"ui_download_svg": "SVG",
|
||||
"ui_download_md": "Markdown",
|
||||
"ui_zoom_out": "Zoom Out",
|
||||
"ui_zoom_reset": "Reset",
|
||||
"ui_zoom_in": "Zoom In",
|
||||
"ui_depth_select": "Expand Level",
|
||||
"ui_depth_all": "All",
|
||||
"ui_depth_2": "L2",
|
||||
"ui_depth_3": "L3",
|
||||
"ui_fullscreen": "Fullscreen",
|
||||
"ui_theme": "Theme",
|
||||
"ui_footer": "<b>Powered by</b> <a href='https://markmap.js.org/' target='_blank' rel='noopener noreferrer'>Markmap</a>",
|
||||
"html_error_missing_content": "⚠️ Missing content.",
|
||||
"html_error_load_failed": "⚠️ Resource load failed.",
|
||||
"js_done": "Done",
|
||||
},
|
||||
"zh-CN": {
|
||||
"status_analyzing": "思维导图:深入分析文本结构...",
|
||||
"status_drawing": "思维导图:绘制完成!",
|
||||
"notification_success": "思维导图已生成,{user_name}!",
|
||||
"error_text_too_short": "文本内容过短({len}字符),请提供至少{min_len}字符。",
|
||||
"error_user_facing": "抱歉,思维导图处理出错:{error}",
|
||||
"status_failed": "思维导图:处理失败。",
|
||||
"ui_title": "🧠 智能思维导图",
|
||||
"ui_download_png": "PNG",
|
||||
"ui_download_svg": "SVG",
|
||||
"ui_download_md": "Markdown",
|
||||
"ui_zoom_out": "缩小",
|
||||
"ui_zoom_reset": "重置",
|
||||
"ui_zoom_in": "放大",
|
||||
"ui_depth_select": "展开层级",
|
||||
"ui_depth_all": "全部",
|
||||
"ui_depth_2": "2级",
|
||||
"ui_depth_3": "3级",
|
||||
"ui_fullscreen": "全屏",
|
||||
"ui_theme": "主题",
|
||||
"ui_footer": "<b>Powered by</b> <a href='https://markmap.js.org/' target='_blank' rel='noopener noreferrer'>Markmap</a>",
|
||||
"html_error_missing_content": "⚠️ 缺少有效内容。",
|
||||
"html_error_load_failed": "⚠️ 资源加载失败。",
|
||||
"js_done": "完成",
|
||||
}
|
||||
}
|
||||
self.__system_prompt = """You are a professional mind map assistant. Analyze text and output Markdown list syntax for Markmap.js.
|
||||
Guidelines:
|
||||
- Root node (#) must be ultra-compact (max 10 chars for CJK, 5 words for Latin).
|
||||
- Use '-' with 2-space indentation.
|
||||
- Output ONLY Markdown wrapped in ```markdown.
|
||||
- Match the language of the input text."""
|
||||
|
||||
self.__css_template = """
|
||||
:root {
|
||||
--primary-color: #1e88e5; --secondary-color: #43a047; --background-color: #f4f6f8;
|
||||
--card-bg-color: #ffffff; --text-color: #000000; --link-color: #546e7a;
|
||||
--node-stroke-color: #90a4ae; --muted-text-color: #546e7a; --border-color: #e0e0e0;
|
||||
--shadow: 0 4px 12px rgba(0, 0, 0, 0.05); --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
.theme-dark {
|
||||
--primary-color: #3b82f6; --secondary-color: #22c55e; --background-color: #0d1117;
|
||||
--card-bg-color: #161b22; --text-color: #ffffff; --link-color: #58a6ff;
|
||||
--node-stroke-color: #8b949e; --muted-text-color: #7d8590; --border-color: #30363d;
|
||||
}
|
||||
html, body { margin: 0; padding: 0; width: 100%; height: 600px; background: transparent; overflow: hidden; font-family: var(--font-family); }
|
||||
.mindmap-wrapper { display: flex; flex-direction: column; width: 100%; height: 100%; background: var(--card-bg-color); border: 1px solid var(--border-color); border-radius: 12px; overflow: hidden; box-shadow: var(--shadow); }
|
||||
.header { display: flex; align-items: center; padding: 8px 16px; border-bottom: 1px solid var(--border-color); background: var(--card-bg-color); flex-shrink: 0; gap: 12px; }
|
||||
.header h1 { margin: 0; font-size: 1rem; flex-grow: 1; color: var(--text-color); }
|
||||
.btn-group { display: flex; gap: 2px; background: var(--background-color); padding: 2px; border-radius: 6px; }
|
||||
.control-btn { border: none; background: transparent; color: var(--text-color); padding: 4px 8px; cursor: pointer; border-radius: 4px; font-size: 0.8rem; opacity: 0.7; }
|
||||
.control-btn:hover { background: var(--card-bg-color); opacity: 1; }
|
||||
.content { flex-grow: 1; position: relative; }
|
||||
.markmap-container { position: absolute; top:0; left:0; right:0; bottom:0; }
|
||||
svg text { fill: var(--text-color) !important; }
|
||||
svg .markmap-link { stroke: var(--link-color) !important; }
|
||||
"""
|
||||
|
||||
self.__content_template = """
|
||||
<div class="mindmap-wrapper">
|
||||
<div class="header">
|
||||
<h1>{t_ui_title}</h1>
|
||||
<div class="btn-group">
|
||||
<button id="z-in-{uid}" class="control-btn">+</button>
|
||||
<button id="z-out-{uid}" class="control-btn">-</button>
|
||||
<button id="z-res-{uid}" class="control-btn">↺</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<select id="d-sel-{uid}" class="control-btn">
|
||||
<option value="0">{t_ui_depth_all}</option>
|
||||
<option value="2">{t_ui_depth_2}</option>
|
||||
<option value="3" selected>{t_ui_depth_3}</option>
|
||||
</select>
|
||||
</div>
|
||||
<button id="t-tog-{uid}" class="control-btn">◐</button>
|
||||
</div>
|
||||
<div class="content"><div class="markmap-container" id="mm-{uid}"></div></div>
|
||||
</div>
|
||||
<script type="text/template" id="src-{uid}">{md}</script>
|
||||
"""
|
||||
|
||||
async def generate_mind_map(
|
||||
self,
|
||||
text: str,
|
||||
__user__: Optional[Dict[str, Any]] = None,
|
||||
__metadata__: Optional[Dict[str, Any]] = None,
|
||||
__event_emitter__: Optional[Callable[[Any], Awaitable[None]]] = None,
|
||||
__request__: Optional[Request] = None,
|
||||
) -> Any:
|
||||
user_ctx = await self.__get_user_context(__user__, __request__)
|
||||
lang = user_ctx["lang"]
|
||||
name = user_ctx["name"]
|
||||
|
||||
if len(text) < self.valves.MIN_TEXT_LENGTH:
|
||||
return f"⚠️ {self.__get_t(lang, 'error_text_too_short', len=len(text), min_len=self.valves.MIN_TEXT_LENGTH)}"
|
||||
|
||||
await self.__emit_status(__event_emitter__, self.__get_t(lang, "status_analyzing"), False)
|
||||
|
||||
try:
|
||||
target_model = self.valves.MODEL_ID or (__metadata__.get("model_id") if __metadata__ else "")
|
||||
llm_payload = {
|
||||
"model": target_model,
|
||||
"messages": [
|
||||
{"role": "system", "content": self.__system_prompt},
|
||||
{"role": "user", "content": f"Language: {lang}\nText: {text}"},
|
||||
],
|
||||
"temperature": 0.5,
|
||||
}
|
||||
|
||||
user_obj = Users.get_user_by_id(user_ctx["id"])
|
||||
response = await generate_chat_completion(__request__, llm_payload, user_obj)
|
||||
md_content = self.__extract_md(response["choices"][0]["message"]["content"])
|
||||
|
||||
uid = str(int(time.time() * 1000))
|
||||
ui_t = {f"t_{k}": self.__get_t(lang, k) for k in self.__translations["en-US"] if k.startswith("ui_")}
|
||||
|
||||
html_body = self.__content_template.format(uid=uid, md=md_content, **ui_t)
|
||||
|
||||
script = f"""
|
||||
<script>
|
||||
(function() {{
|
||||
const uid = "{uid}";
|
||||
const load = (s, c) => c() ? Promise.resolve() : new Promise((r,e) => {{
|
||||
const t = document.createElement('script'); t.src = s; t.onload = r; t.onerror = e; document.head.appendChild(t);
|
||||
}});
|
||||
const init = () => load('https://cdn.jsdelivr.net/npm/d3@7', () => window.d3)
|
||||
.then(() => load('https://cdn.jsdelivr.net/npm/markmap-lib@0.17', () => window.markmap?.Transformer))
|
||||
.then(() => load('https://cdn.jsdelivr.net/npm/markmap-view@0.17', () => window.markmap?.Markmap))
|
||||
.then(() => {{
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.style.width = svg.style.height = '100%';
|
||||
const cnt = document.getElementById('mm-'+uid); cnt.appendChild(svg);
|
||||
const {{ Transformer, Markmap }} = window.markmap;
|
||||
const {{ root }} = new Transformer().transform(document.getElementById('src-'+uid).textContent);
|
||||
const mm = Markmap.create(svg, {{ autoFit: true, initialExpandLevel: 3 }}, root);
|
||||
document.getElementById('z-in-'+uid).onclick = () => mm.rescale(1.25);
|
||||
document.getElementById('z-out-'+uid).onclick = () => mm.rescale(0.8);
|
||||
document.getElementById('z-res-'+uid).onclick = () => mm.fit();
|
||||
document.getElementById('t-tog-'+uid).onclick = () => document.body.classList.toggle('theme-dark');
|
||||
document.getElementById('d-sel-'+uid).onchange = (e) => {{
|
||||
mm.setOptions({{ initialExpandLevel: parseInt(e.target.value) || 99 }}); mm.setData(root); mm.fit();
|
||||
}};
|
||||
window.addEventListener('resize', () => mm.fit());
|
||||
}});
|
||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init();
|
||||
}})();
|
||||
</script>
|
||||
"""
|
||||
|
||||
final_html = f"<!DOCTYPE html><html lang='{lang}'><head><style>{self.__css_template}</style></head><body>{html_body}{script}</body></html>"
|
||||
|
||||
await self.__emit_status(__event_emitter__, self.__get_t(lang, "status_drawing"), True)
|
||||
await self.__emit_notification(__event_emitter__, self.__get_t(lang, "notification_success", user_name=name), "success")
|
||||
|
||||
return (final_html.strip(), {"Content-Disposition": "inline", "Content-Type": "text/html"})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Mind Map Error: {e}", exc_info=True)
|
||||
await self.__emit_status(__event_emitter__, self.__get_t(lang, "status_failed"), True)
|
||||
return f"❌ {self.__get_t(lang, 'error_user_facing', error=str(e))}"
|
||||
|
||||
async def __get_user_context(self, __user__, __request__) -> Dict[str, str]:
|
||||
u = __user__ or {}
|
||||
lang = u.get("language") or (__request__.headers.get("accept-language") or "en-US").split(",")[0].split(";")[0]
|
||||
return {"id": u.get("id", "unknown"), "name": u.get("name", "User"), "lang": lang}
|
||||
|
||||
def __get_t(self, lang: str, key: str, **kwargs) -> str:
|
||||
base = lang.split("-")[0]
|
||||
t = self.__translations.get(lang, self.__translations.get(base, self.__translations["en-US"])).get(key, key)
|
||||
return t.format(**kwargs) if kwargs else t
|
||||
|
||||
def __extract_md(self, content: str) -> str:
|
||||
match = re.search(r"```markdown\s*(.*?)\s*```", content, re.DOTALL)
|
||||
return (match.group(1).strip() if match else content.strip()).replace("</script>", "<\\/script>")
|
||||
|
||||
async def __emit_status(self, emitter, description: str, done: bool):
|
||||
if self.valves.SHOW_STATUS and emitter:
|
||||
await emitter({"type": "status", "data": {"description": description, "done": done}})
|
||||
|
||||
async def __emit_notification(self, emitter, content: str, ntype: str):
|
||||
if emitter:
|
||||
await emitter({"type": "notification", "data": {"type": ntype, "content": content}})
|
||||
@@ -1,20 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
OpenWebUI 社区统计工具
|
||||
OpenWebUI community stats utility.
|
||||
|
||||
获取并统计你在 openwebui.com 上发布的插件/帖子数据。
|
||||
Collect and summarize your published posts/plugins on openwebui.com.
|
||||
|
||||
使用方法:
|
||||
1. 设置环境变量:
|
||||
- OPENWEBUI_API_KEY: 你的 API Key
|
||||
- OPENWEBUI_USER_ID: 你的用户 ID
|
||||
2. 运行: python scripts/openwebui_stats.py
|
||||
Usage:
|
||||
1. Set environment variables:
|
||||
- OPENWEBUI_API_KEY: required
|
||||
- OPENWEBUI_USER_ID: optional (auto-resolved from /api/v1/auths/ when missing)
|
||||
2. Run: python scripts/openwebui_stats.py
|
||||
|
||||
获取 API Key:
|
||||
访问 https://openwebui.com/settings/api 创建 API Key (sk-开头)
|
||||
How to get API key:
|
||||
Visit https://openwebui.com/settings/api and create a key (starts with sk-).
|
||||
|
||||
获取 User ID:
|
||||
从个人主页的 API 请求中获取,格式如: b15d1348-4347-42b4-b815-e053342d6cb0
|
||||
How to get user ID (optional):
|
||||
Read the `id` field from /api/v1/auths/, format example:
|
||||
b15d1348-4347-42b4-b815-e053342d6cb0
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -28,16 +29,16 @@ from datetime import datetime, timezone, timedelta
|
||||
from typing import Optional
|
||||
from pathlib import Path
|
||||
|
||||
# 北京时区 (UTC+8)
|
||||
# Beijing timezone (UTC+8)
|
||||
BEIJING_TZ = timezone(timedelta(hours=8))
|
||||
|
||||
|
||||
def get_beijing_time() -> datetime:
|
||||
"""获取当前北京时间"""
|
||||
"""Get current time in Beijing timezone."""
|
||||
return datetime.now(BEIJING_TZ)
|
||||
|
||||
|
||||
# 尝试加载 .env 文件
|
||||
# Try loading local .env file (if python-dotenv is installed)
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
|
||||
@@ -47,7 +48,7 @@ except ImportError:
|
||||
|
||||
|
||||
class OpenWebUIStats:
|
||||
"""OpenWebUI 社区统计工具"""
|
||||
"""OpenWebUI community stats utility."""
|
||||
|
||||
BASE_URL = "https://api.openwebui.com/api/v1"
|
||||
|
||||
@@ -59,18 +60,18 @@ class OpenWebUIStats:
|
||||
gist_id: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
初始化统计工具
|
||||
Initialize the stats utility
|
||||
|
||||
Args:
|
||||
api_key: OpenWebUI API Key (JWT Token)
|
||||
user_id: 用户 ID,如果为 None 则从 token 中解析
|
||||
gist_token: GitHub Personal Access Token (用于读写 Gist)
|
||||
user_id: User ID; if None, will be parsed from token
|
||||
gist_token: GitHub Personal Access Token (for reading/writing Gist)
|
||||
gist_id: GitHub Gist ID
|
||||
"""
|
||||
self.api_key = api_key
|
||||
self.user_id = user_id or self._parse_user_id_from_token(api_key)
|
||||
self.gist_token = gist_token
|
||||
self.gist_id = gist_id
|
||||
self.gist_id = gist_id or "db3d95687075a880af6f1fba76d679c6"
|
||||
self.history_filename = "community-stats-history.json"
|
||||
|
||||
self.session = requests.Session()
|
||||
@@ -83,23 +84,32 @@ class OpenWebUIStats:
|
||||
)
|
||||
self.history_file = Path("docs/stats-history.json")
|
||||
|
||||
# 定义下载类别的判定(这些类别会计入总浏览量/下载量统计)
|
||||
# Types considered downloadable (included in total view/download stats)
|
||||
DOWNLOADABLE_TYPES = [
|
||||
"action",
|
||||
"filter",
|
||||
"pipe",
|
||||
"toolkit",
|
||||
"tool",
|
||||
"function",
|
||||
"prompt",
|
||||
"model",
|
||||
]
|
||||
|
||||
TYPE_ALIASES = {
|
||||
"tools": "tool",
|
||||
}
|
||||
|
||||
def _normalize_post_type(self, post_type: str) -> str:
|
||||
"""Normalize post type to avoid synonym type splitting in statistics."""
|
||||
normalized = str(post_type or "").strip().lower()
|
||||
return self.TYPE_ALIASES.get(normalized, normalized)
|
||||
|
||||
def load_history(self) -> list:
|
||||
"""加载历史记录 (合并 Gist + 本地文件, 取记录更多的)"""
|
||||
"""Load history records (merge Gist + local file, keep the one with more records)"""
|
||||
gist_history = []
|
||||
local_history = []
|
||||
|
||||
# 1. 尝试从 Gist 加载
|
||||
# 1. Try loading from Gist
|
||||
if self.gist_token and self.gist_id:
|
||||
try:
|
||||
url = f"https://api.github.com/gists/{self.gist_id}"
|
||||
@@ -111,30 +121,36 @@ class OpenWebUIStats:
|
||||
if file_info:
|
||||
content = file_info.get("content")
|
||||
gist_history = json.loads(content)
|
||||
print(f"✅ 已从 Gist 加载历史记录 ({len(gist_history)} 条)")
|
||||
print(
|
||||
f"✅ Loaded history from Gist ({len(gist_history)} records)"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"⚠️ 无法从 Gist 加载历史: {e}")
|
||||
print(f"⚠️ Failed to load history from Gist: {e}")
|
||||
|
||||
# 2. 同时从本地文件加载
|
||||
# 2. Also load from local file
|
||||
if self.history_file.exists():
|
||||
try:
|
||||
with open(self.history_file, "r", encoding="utf-8") as f:
|
||||
local_history = json.load(f)
|
||||
print(f"✅ 已从本地加载历史记录 ({len(local_history)} 条)")
|
||||
print(
|
||||
f"✅ Loaded history from local file ({len(local_history)} records)"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"⚠️ 无法加载本地历史记录: {e}")
|
||||
print(f"⚠️ Failed to load local history: {e}")
|
||||
|
||||
# 3. 合并两个来源 (以日期为 key, 有冲突时保留更新的)
|
||||
# 3. Merge two sources (by date as key, keep newer when conflicts)
|
||||
hist_dict = {}
|
||||
for item in gist_history:
|
||||
hist_dict[item["date"]] = item
|
||||
for item in local_history:
|
||||
hist_dict[item["date"]] = item # 本地数据覆盖 Gist (更可能是最新的)
|
||||
hist_dict[item["date"]] = (
|
||||
item # Local data overrides Gist (more likely to be latest)
|
||||
)
|
||||
|
||||
history = sorted(hist_dict.values(), key=lambda x: x["date"])
|
||||
print(f"📊 合并后历史记录: {len(history)} 条")
|
||||
print(f"📊 Merged history records: {len(history)}")
|
||||
|
||||
# 4. 如果合并后仍然太少, 尝试从 Git 历史重建
|
||||
# 4. If merged data is still too short, try rebuilding from Git
|
||||
if len(history) < 5 and os.path.isdir(".git"):
|
||||
print("📉 History too short, attempting Git rebuild...")
|
||||
git_history = self.rebuild_history_from_git()
|
||||
@@ -146,7 +162,7 @@ class OpenWebUIStats:
|
||||
hist_dict[item["date"]] = item
|
||||
history = sorted(hist_dict.values(), key=lambda x: x["date"])
|
||||
|
||||
# 5. 如果有新数据, 同步回 Gist
|
||||
# 5. If there is new data, sync back to Gist
|
||||
if len(history) > len(gist_history) and self.gist_token and self.gist_id:
|
||||
try:
|
||||
url = f"https://api.github.com/gists/{self.gist_id}"
|
||||
@@ -160,7 +176,7 @@ class OpenWebUIStats:
|
||||
}
|
||||
resp = requests.patch(url, headers=headers, json=payload)
|
||||
if resp.status_code == 200:
|
||||
print(f"✅ 历史记录已同步至 Gist ({len(history)} 条)")
|
||||
print(f"✅ History synced to Gist ({len(history)} records)")
|
||||
else:
|
||||
print(f"⚠️ Gist sync failed: {resp.status_code}")
|
||||
except Exception as e:
|
||||
@@ -169,11 +185,11 @@ class OpenWebUIStats:
|
||||
return history
|
||||
|
||||
def save_history(self, stats: dict):
|
||||
"""保存当前快照到历史记录 (优先保存到 Gist, 其次本地)"""
|
||||
"""Save current snapshot to history (prioritize Gist, fallback to local)"""
|
||||
history = self.load_history()
|
||||
today = get_beijing_time().strftime("%Y-%m-%d")
|
||||
|
||||
# 构造详细快照 (包含每个插件的下载量)
|
||||
# Build detailed snapshot (including each plugin's download count)
|
||||
snapshot = {
|
||||
"date": today,
|
||||
"total_posts": stats["total_posts"],
|
||||
@@ -187,7 +203,7 @@ class OpenWebUIStats:
|
||||
"posts": {p["slug"]: p["downloads"] for p in stats.get("posts", [])},
|
||||
}
|
||||
|
||||
# 更新或追加数据点
|
||||
# Update or append data point
|
||||
updated = False
|
||||
for i, item in enumerate(history):
|
||||
if item.get("date") == today:
|
||||
@@ -197,10 +213,10 @@ class OpenWebUIStats:
|
||||
if not updated:
|
||||
history.append(snapshot)
|
||||
|
||||
# 限制长度 (90天)
|
||||
# Limit length (90 days)
|
||||
history = history[-90:]
|
||||
|
||||
# 尝试保存到 Gist
|
||||
# Try saving to Gist
|
||||
if self.gist_token and self.gist_id:
|
||||
try:
|
||||
url = f"https://api.github.com/gists/{self.gist_id}"
|
||||
@@ -214,19 +230,19 @@ class OpenWebUIStats:
|
||||
}
|
||||
resp = requests.patch(url, headers=headers, json=payload)
|
||||
if resp.status_code == 200:
|
||||
print(f"✅ 历史记录已同步至 Gist ({self.gist_id})")
|
||||
# 如果同步成功,不再保存到本地,减少 commit 压力
|
||||
print(f"✅ History synced to Gist ({self.gist_id})")
|
||||
# If sync succeeds, do not save to local to reduce commit pressure
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"⚠️ 同步至 Gist 失败: {e}")
|
||||
print(f"⚠️ Failed to sync to Gist: {e}")
|
||||
|
||||
# 降级:保存到本地
|
||||
# Fallback: save to local
|
||||
with open(self.history_file, "w", encoding="utf-8") as f:
|
||||
json.dump(history, f, ensure_ascii=False, indent=2)
|
||||
print(f"✅ 历史记录已更新至本地 ({today})")
|
||||
print(f"✅ History updated to local ({today})")
|
||||
|
||||
def get_stat_delta(self, stats: dict) -> dict:
|
||||
"""计算相对于上次记录的增长 (24h)"""
|
||||
"""Calculate growth relative to last recorded snapshot (24h delta)"""
|
||||
history = self.load_history()
|
||||
if not history:
|
||||
return {}
|
||||
@@ -234,7 +250,7 @@ class OpenWebUIStats:
|
||||
today = get_beijing_time().strftime("%Y-%m-%d")
|
||||
prev = None
|
||||
|
||||
# 查找非今天的最后一笔数据作为基准
|
||||
# Find last data point from a different day as baseline
|
||||
for item in reversed(history):
|
||||
if item.get("date") != today:
|
||||
prev = item
|
||||
@@ -262,14 +278,14 @@ class OpenWebUIStats:
|
||||
}
|
||||
|
||||
def _resolve_post_type(self, post: dict) -> str:
|
||||
"""解析帖子类别"""
|
||||
"""Resolve the post category type"""
|
||||
top_type = post.get("type")
|
||||
function_data = post.get("data", {}) or {}
|
||||
function_obj = function_data.get("function", {}) or {}
|
||||
meta = function_obj.get("meta", {}) or {}
|
||||
manifest = meta.get("manifest", {}) or {}
|
||||
|
||||
# 类别识别优先级:
|
||||
# Category identification priority:
|
||||
if top_type == "review":
|
||||
return "review"
|
||||
|
||||
@@ -283,7 +299,9 @@ class OpenWebUIStats:
|
||||
elif not meta and not function_obj:
|
||||
post_type = "post"
|
||||
|
||||
# 统一和启发式识别逻辑
|
||||
post_type = self._normalize_post_type(post_type)
|
||||
|
||||
# Unified and heuristic identification logic
|
||||
if post_type == "unknown" and function_obj:
|
||||
post_type = "action"
|
||||
|
||||
@@ -298,17 +316,17 @@ class OpenWebUIStats:
|
||||
post_type = "filter"
|
||||
elif "pipe" in all_metadata:
|
||||
post_type = "pipe"
|
||||
elif "toolkit" in all_metadata:
|
||||
post_type = "toolkit"
|
||||
elif "tool" in all_metadata:
|
||||
post_type = "tool"
|
||||
|
||||
return post_type
|
||||
return self._normalize_post_type(post_type)
|
||||
|
||||
def rebuild_history_from_git(self) -> list:
|
||||
"""从 Git 历史提交中重建统计数据"""
|
||||
"""Rebuild statistics from Git commit history"""
|
||||
history = []
|
||||
try:
|
||||
# 从 docs/community-stats.json 的 Git 历史重建 (该文件历史最丰富)
|
||||
# 格式: hash date
|
||||
# Rebuild from Git history of docs/community-stats.json (has richest history)
|
||||
# Format: hash date
|
||||
target = "docs/community-stats.json"
|
||||
cmd = [
|
||||
"git",
|
||||
@@ -324,8 +342,8 @@ class OpenWebUIStats:
|
||||
|
||||
seen_dates = set()
|
||||
|
||||
# 从旧到新处理(git log 默认是从新到旧,所以我们要反转或者用 reverse)
|
||||
# 其实顺序无所谓,只要最后 sort 一下就行
|
||||
# Process from oldest to newest (git log defaults to newest first, so reverse)
|
||||
# The order doesn't really matter as long as we sort at the end
|
||||
for line in reversed(commits): # Process from oldest to newest
|
||||
parts = line.split()
|
||||
if len(parts) < 2:
|
||||
@@ -338,7 +356,7 @@ class OpenWebUIStats:
|
||||
continue
|
||||
seen_dates.add(commit_date)
|
||||
|
||||
# 读取该 commit 时的文件内容
|
||||
# Read file content at this commit
|
||||
# Note: The file name in git show needs to be relative to the repo root
|
||||
show_cmd = ["git", "show", f"{commit_hash}:{target}"]
|
||||
show_res = subprocess.run(
|
||||
@@ -405,13 +423,16 @@ class OpenWebUIStats:
|
||||
return []
|
||||
|
||||
def _parse_user_id_from_token(self, token: str) -> str:
|
||||
"""从 JWT Token 中解析用户 ID"""
|
||||
"""Parse user ID from JWT Token"""
|
||||
import base64
|
||||
|
||||
if not token or token.startswith("sk-"):
|
||||
return ""
|
||||
|
||||
try:
|
||||
# JWT 格式: header.payload.signature
|
||||
# JWT format: header.payload.signature
|
||||
payload = token.split(".")[1]
|
||||
# 添加 padding
|
||||
# Add padding
|
||||
padding = 4 - len(payload) % 4
|
||||
if padding != 4:
|
||||
payload += "=" * padding
|
||||
@@ -419,16 +440,36 @@ class OpenWebUIStats:
|
||||
data = json.loads(decoded)
|
||||
return data.get("id", "")
|
||||
except Exception as e:
|
||||
print(f"⚠️ 无法从 Token 解析用户 ID: {e}")
|
||||
print(f"⚠️ Failed to parse user ID from token: {e}")
|
||||
return ""
|
||||
|
||||
def resolve_user_id(self) -> str:
|
||||
"""Auto-resolve current user ID via community API (for sk- type API keys)"""
|
||||
if self.user_id:
|
||||
return self.user_id
|
||||
|
||||
try:
|
||||
resp = self.session.get(f"{self.BASE_URL}/auths/", timeout=20)
|
||||
if resp.status_code == 200:
|
||||
data = resp.json() if resp.text else {}
|
||||
resolved = str(data.get("id", "")).strip()
|
||||
if resolved:
|
||||
self.user_id = resolved
|
||||
return resolved
|
||||
else:
|
||||
print(f"⚠️ Failed to auto-resolve user ID: HTTP {resp.status_code}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Exception while auto-resolving user ID: {e}")
|
||||
|
||||
return ""
|
||||
|
||||
def generate_mermaid_chart(self, stats: dict = None, lang: str = "zh") -> str:
|
||||
"""生成支持 Kroki 服务端渲染的动态 Mermaid 图表链接 (零 Commit)"""
|
||||
"""Generate dynamic Mermaid chart links with Kroki server-side rendering (zero commit)"""
|
||||
history = self.load_history()
|
||||
if not history:
|
||||
return ""
|
||||
|
||||
# 多语言标签
|
||||
# Multi-language labels
|
||||
labels = {
|
||||
"zh": {
|
||||
"trend_title": "增长与趋势 (Last 14 Days)",
|
||||
@@ -495,14 +536,14 @@ class OpenWebUIStats:
|
||||
|
||||
def get_user_posts(self, sort: str = "new", page: int = 1) -> list:
|
||||
"""
|
||||
获取用户发布的帖子列表
|
||||
Fetch list of posts published by the user
|
||||
|
||||
Args:
|
||||
sort: 排序方式 (new/top/hot)
|
||||
page: 页码
|
||||
sort: Sort order (new/top/hot)
|
||||
page: Page number
|
||||
|
||||
Returns:
|
||||
帖子列表
|
||||
List of posts
|
||||
"""
|
||||
url = f"{self.BASE_URL}/posts/users/{self.user_id}"
|
||||
params = {"sort": sort, "page": page}
|
||||
@@ -512,7 +553,7 @@ class OpenWebUIStats:
|
||||
return response.json()
|
||||
|
||||
def get_all_posts(self, sort: str = "new") -> list:
|
||||
"""获取所有帖子(自动分页)"""
|
||||
"""Fetch all posts (automatic pagination)"""
|
||||
all_posts = []
|
||||
page = 1
|
||||
|
||||
@@ -526,7 +567,7 @@ class OpenWebUIStats:
|
||||
return all_posts
|
||||
|
||||
def generate_stats(self, posts: list) -> dict:
|
||||
"""生成统计数据"""
|
||||
"""Generate statistics"""
|
||||
stats = {
|
||||
"total_posts": len(posts),
|
||||
"total_downloads": 0,
|
||||
@@ -537,10 +578,10 @@ class OpenWebUIStats:
|
||||
"total_comments": 0,
|
||||
"by_type": {},
|
||||
"posts": [],
|
||||
"user": {}, # 用户信息
|
||||
"user": {}, # User info
|
||||
}
|
||||
|
||||
# 从第一个帖子中提取用户信息
|
||||
# Extract user info from first post
|
||||
if posts and "user" in posts[0]:
|
||||
user = posts[0]["user"]
|
||||
stats["user"] = {
|
||||
@@ -564,7 +605,7 @@ class OpenWebUIStats:
|
||||
meta = function_obj.get("meta", {}) or {}
|
||||
manifest = meta.get("manifest", {}) or {}
|
||||
|
||||
# 累计统计
|
||||
# Accumulate statistics
|
||||
post_downloads = post.get("downloads", 0)
|
||||
post_views = post.get("views", 0)
|
||||
|
||||
@@ -574,7 +615,7 @@ class OpenWebUIStats:
|
||||
stats["total_saves"] += post.get("saveCount", 0)
|
||||
stats["total_comments"] += post.get("commentCount", 0)
|
||||
|
||||
# 关键:总浏览量不包括不可以下载的类型 (如 post, review)
|
||||
# Key: total views do not include non-downloadable types (e.g., post, review)
|
||||
if post_type in self.DOWNLOADABLE_TYPES or post_downloads > 0:
|
||||
stats["total_views"] += post_views
|
||||
|
||||
@@ -582,7 +623,7 @@ class OpenWebUIStats:
|
||||
stats["by_type"][post_type] = 0
|
||||
stats["by_type"][post_type] += 1
|
||||
|
||||
# 单个帖子信息
|
||||
# Individual post information
|
||||
created_at = datetime.fromtimestamp(post.get("createdAt", 0))
|
||||
updated_at = datetime.fromtimestamp(post.get("updatedAt", 0))
|
||||
|
||||
@@ -605,43 +646,45 @@ class OpenWebUIStats:
|
||||
}
|
||||
)
|
||||
|
||||
# 按下载量排序
|
||||
# Sort by download count
|
||||
stats["posts"].sort(key=lambda x: x["downloads"], reverse=True)
|
||||
|
||||
return stats
|
||||
|
||||
def print_stats(self, stats: dict):
|
||||
"""打印统计报告到终端"""
|
||||
"""Print statistics report to terminal"""
|
||||
print("\n" + "=" * 60)
|
||||
print("📊 OpenWebUI 社区统计报告")
|
||||
print("📊 OpenWebUI Community Statistics Report")
|
||||
print("=" * 60)
|
||||
print(f"📅 生成时间 (北京): {get_beijing_time().strftime('%Y-%m-%d %H:%M')}")
|
||||
print(
|
||||
f"📅 Generated (Beijing time): {get_beijing_time().strftime('%Y-%m-%d %H:%M')}"
|
||||
)
|
||||
print()
|
||||
|
||||
# 总览
|
||||
print("📈 总览")
|
||||
# Overview
|
||||
print("📈 Overview")
|
||||
print("-" * 40)
|
||||
print(f" 📝 发布数量: {stats['total_posts']}")
|
||||
print(f" ⬇️ 总下载量: {stats['total_downloads']}")
|
||||
print(f" 👁️ 总浏览量: {stats['total_views']}")
|
||||
print(f" 👍 总点赞数: {stats['total_upvotes']}")
|
||||
print(f" 💾 总收藏数: {stats['total_saves']}")
|
||||
print(f" 💬 总评论数: {stats['total_comments']}")
|
||||
print(f" 📝 Posts: {stats['total_posts']}")
|
||||
print(f" ⬇️ Total Downloads: {stats['total_downloads']}")
|
||||
print(f" 👁️ Total Views: {stats['total_views']}")
|
||||
print(f" 👍 Total Upvotes: {stats['total_upvotes']}")
|
||||
print(f" 💾 Total Saves: {stats['total_saves']}")
|
||||
print(f" 💬 Total Comments: {stats['total_comments']}")
|
||||
print()
|
||||
|
||||
# 按类型分类
|
||||
print("📂 按类型分类")
|
||||
# By type
|
||||
print("📂 By Type")
|
||||
print("-" * 40)
|
||||
for post_type, count in stats["by_type"].items():
|
||||
print(f" • {post_type}: {count}")
|
||||
print()
|
||||
|
||||
# 详细列表
|
||||
print("📋 发布列表 (按下载量排序)")
|
||||
# Detailed list
|
||||
print("📋 Posts List (sorted by downloads)")
|
||||
print("-" * 60)
|
||||
|
||||
# 表头
|
||||
print(f"{'排名':<4} {'标题':<30} {'下载':<8} {'浏览':<8} {'点赞':<6}")
|
||||
# Header
|
||||
print(f"{'Rank':<4} {'Title':<30} {'Downloads':<8} {'Views':<8} {'Upvotes':<6}")
|
||||
print("-" * 60)
|
||||
|
||||
for i, post in enumerate(stats["posts"], 1):
|
||||
@@ -655,23 +698,23 @@ class OpenWebUIStats:
|
||||
print("=" * 60)
|
||||
|
||||
def _safe_key(self, key: str) -> str:
|
||||
"""生成安全的文件名 Key (MD5 hash) 以避免中文字符问题"""
|
||||
"""Generate safe filename key (MD5 hash) to avoid Chinese character issues"""
|
||||
import hashlib
|
||||
|
||||
return hashlib.md5(key.encode("utf-8")).hexdigest()
|
||||
|
||||
def generate_markdown(self, stats: dict, lang: str = "zh") -> str:
|
||||
"""
|
||||
生成 Markdown 格式报告 (全动态徽章与 Kroki 图表)
|
||||
Generate Markdown format report (fully dynamic badges and Kroki charts)
|
||||
|
||||
Args:
|
||||
stats: 统计数据
|
||||
lang: 语言 ("zh" 中文, "en" 英文)
|
||||
stats: Statistics data
|
||||
lang: Language ("zh" Chinese, "en" English)
|
||||
"""
|
||||
# 获取增量数据
|
||||
# Get delta data
|
||||
delta = self.get_stat_delta(stats)
|
||||
|
||||
# 中英文文本
|
||||
# Bilingual text
|
||||
texts = {
|
||||
"zh": {
|
||||
"title": "# 📊 OpenWebUI 社区统计报告",
|
||||
@@ -761,6 +804,7 @@ class OpenWebUIStats:
|
||||
"filter": "brightgreen",
|
||||
"action": "orange",
|
||||
"pipe": "blueviolet",
|
||||
"tool": "teal",
|
||||
"pipeline": "purple",
|
||||
"review": "yellow",
|
||||
"prompt": "lightgrey",
|
||||
@@ -815,30 +859,30 @@ class OpenWebUIStats:
|
||||
return "\n".join(md)
|
||||
|
||||
def save_json(self, stats: dict, filepath: str):
|
||||
"""保存 JSON 格式数据"""
|
||||
"""Save data in JSON format"""
|
||||
with open(filepath, "w", encoding="utf-8") as f:
|
||||
json.dump(stats, f, ensure_ascii=False, indent=2)
|
||||
print(f"✅ JSON 数据已保存到: {filepath}")
|
||||
print(f"✅ JSON data saved to: {filepath}")
|
||||
|
||||
def generate_shields_endpoints(self, stats: dict, output_dir: str = "docs/badges"):
|
||||
"""
|
||||
生成 Shields.io endpoint JSON 文件
|
||||
Generate Shields.io endpoint JSON files
|
||||
|
||||
Args:
|
||||
stats: 统计数据
|
||||
output_dir: 输出目录
|
||||
stats: Statistics data
|
||||
output_dir: Output directory
|
||||
"""
|
||||
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def format_number(n: int) -> str:
|
||||
"""格式化数字为易读格式"""
|
||||
"""Format number to readable format"""
|
||||
if n >= 1000000:
|
||||
return f"{n/1000000:.1f}M"
|
||||
elif n >= 1000:
|
||||
return f"{n/1000:.1f}k"
|
||||
return str(n)
|
||||
|
||||
# 各种徽章数据
|
||||
# Badge data
|
||||
badges = {
|
||||
"downloads": {
|
||||
"schemaVersion": 1,
|
||||
@@ -884,18 +928,18 @@ class OpenWebUIStats:
|
||||
# 构造并上传 Shields.io 徽章数据
|
||||
self.upload_gist_badges(stats)
|
||||
except Exception as e:
|
||||
print(f"⚠️ 徽章生成失败: {e}")
|
||||
print(f"⚠️ Badge generation failed: {e}")
|
||||
|
||||
print(f"✅ Shields.io endpoints saved to: {output_dir}/")
|
||||
|
||||
def upload_gist_badges(self, stats: dict):
|
||||
"""生成并上传 Gist 徽章数据 (用于 Shields.io Endpoint)"""
|
||||
"""Generate and upload Gist badge data (for Shields.io Endpoint)"""
|
||||
if not (self.gist_token and self.gist_id):
|
||||
return
|
||||
|
||||
delta = self.get_stat_delta(stats)
|
||||
|
||||
# 定义徽章配置 {key: (label, value, color)}
|
||||
# Define badge config {key: (label, value, color)}
|
||||
badges_config = {
|
||||
"downloads": ("Downloads", stats["total_downloads"], "brightgreen"),
|
||||
"views": ("Views", stats["total_views"], "blue"),
|
||||
@@ -923,7 +967,7 @@ class OpenWebUIStats:
|
||||
for key, (label, val, color) in badges_config.items():
|
||||
diff = delta.get(key, 0)
|
||||
if isinstance(diff, dict):
|
||||
diff = 0 # 避免 'posts' key 导致的 dict vs int 比较错误
|
||||
diff = 0 # Avoid dict vs int comparison error with 'posts' key
|
||||
|
||||
message = f"{val}"
|
||||
if diff > 0:
|
||||
@@ -931,7 +975,7 @@ class OpenWebUIStats:
|
||||
elif diff < 0:
|
||||
message += f" ({diff})"
|
||||
|
||||
# 构造 Shields.io endpoint JSON
|
||||
# Build Shields.io endpoint JSON
|
||||
# 参考: https://shields.io/badges/endpoint-badge
|
||||
badge_data = {
|
||||
"schemaVersion": 1,
|
||||
@@ -945,13 +989,13 @@ class OpenWebUIStats:
|
||||
"content": json.dumps(badge_data, ensure_ascii=False)
|
||||
}
|
||||
|
||||
# 生成 Top 6 插件徽章 (基于槽位 p1, p2...)
|
||||
# Generate top 6 plugins badges (based on slots p1, p2...)
|
||||
post_deltas = delta.get("posts", {})
|
||||
for i, post in enumerate(stats.get("posts", [])[:6]):
|
||||
idx = i + 1
|
||||
diff = post_deltas.get(post["slug"], 0)
|
||||
|
||||
# 下载量徽章
|
||||
# Downloads badge
|
||||
dl_msg = f"{post['downloads']}"
|
||||
if diff > 0:
|
||||
dl_msg += f" (+{diff}🚀)"
|
||||
@@ -1103,6 +1147,8 @@ class OpenWebUIStats:
|
||||
|
||||
def _fmt_delta(k: str) -> str:
|
||||
val = delta.get(k, 0)
|
||||
if isinstance(val, dict):
|
||||
return ""
|
||||
if val > 0:
|
||||
return f" <br><sub>(+{val}🚀)</sub>"
|
||||
return ""
|
||||
@@ -1462,86 +1508,93 @@ class OpenWebUIStats:
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
# 获取配置
|
||||
"""CLI entry point."""
|
||||
# Load runtime config
|
||||
api_key = os.getenv("OPENWEBUI_API_KEY")
|
||||
user_id = os.getenv("OPENWEBUI_USER_ID")
|
||||
|
||||
if not api_key:
|
||||
print("❌ 错误: 未设置 OPENWEBUI_API_KEY 环境变量")
|
||||
print("请设置环境变量:")
|
||||
print("❌ Error: OPENWEBUI_API_KEY is not set")
|
||||
print("Please set environment variable:")
|
||||
print(" export OPENWEBUI_API_KEY='your_api_key_here'")
|
||||
return 1
|
||||
|
||||
if not user_id:
|
||||
print("❌ 错误: 未设置 OPENWEBUI_USER_ID 环境变量")
|
||||
print("请设置环境变量:")
|
||||
print(" export OPENWEBUI_USER_ID='your_user_id_here'")
|
||||
print("\n提示: 用户 ID 可以从之前的 curl 请求中获取")
|
||||
print(" 例如: b15d1348-4347-42b4-b815-e053342d6cb0")
|
||||
return 1
|
||||
print("ℹ️ OPENWEBUI_USER_ID not set, attempting auto-resolve via API key...")
|
||||
|
||||
# 获取 Gist 配置 (用于存储历史记录)
|
||||
# Gist config (optional, for badges/history sync)
|
||||
gist_token = os.getenv("GIST_TOKEN")
|
||||
gist_id = os.getenv("GIST_ID")
|
||||
|
||||
# 初始化
|
||||
# Initialize client
|
||||
stats_client = OpenWebUIStats(api_key, user_id, gist_token, gist_id)
|
||||
print(f"🔍 用户 ID: {stats_client.user_id}")
|
||||
|
||||
if not stats_client.user_id:
|
||||
stats_client.resolve_user_id()
|
||||
|
||||
if not stats_client.user_id:
|
||||
print("❌ Error: failed to auto-resolve OPENWEBUI_USER_ID")
|
||||
print("Please set environment variable:")
|
||||
print(" export OPENWEBUI_USER_ID='your_user_id_here'")
|
||||
print("\nTip: user id is the 'id' field returned by /api/v1/auths/")
|
||||
print(" e.g. b15d1348-4347-42b4-b815-e053342d6cb0")
|
||||
return 1
|
||||
|
||||
print(f"🔍 User ID: {stats_client.user_id}")
|
||||
if gist_id:
|
||||
print(f"📦 Gist 存储已启用: {gist_id}")
|
||||
print(f"📦 Gist storage enabled: {gist_id}")
|
||||
|
||||
# 获取所有帖子
|
||||
print("📥 正在获取帖子数据...")
|
||||
# Fetch posts
|
||||
print("📥 Fetching posts...")
|
||||
posts = stats_client.get_all_posts()
|
||||
print(f"✅ 获取到 {len(posts)} 个帖子")
|
||||
print(f"✅ Retrieved {len(posts)} posts")
|
||||
|
||||
# 生成统计
|
||||
# Build stats
|
||||
stats = stats_client.generate_stats(posts)
|
||||
|
||||
# 保存历史快照
|
||||
# Save history snapshot
|
||||
stats_client.save_history(stats)
|
||||
|
||||
# 打印到终端
|
||||
# Print terminal report
|
||||
stats_client.print_stats(stats)
|
||||
|
||||
# 保存 Markdown 报告 (中英文双版本)
|
||||
# Save markdown reports (zh/en)
|
||||
script_dir = Path(__file__).parent.parent
|
||||
|
||||
# 中文报告
|
||||
# Chinese report
|
||||
md_zh_path = script_dir / "docs" / "community-stats.zh.md"
|
||||
md_zh_content = stats_client.generate_markdown(stats, lang="zh")
|
||||
with open(md_zh_path, "w", encoding="utf-8") as f:
|
||||
f.write(md_zh_content)
|
||||
print(f"\n✅ 中文报告已保存到: {md_zh_path}")
|
||||
print(f"\n✅ Chinese report saved to: {md_zh_path}")
|
||||
|
||||
# 英文报告
|
||||
# English report
|
||||
md_en_path = script_dir / "docs" / "community-stats.md"
|
||||
md_en_content = stats_client.generate_markdown(stats, lang="en")
|
||||
with open(md_en_path, "w", encoding="utf-8") as f:
|
||||
f.write(md_en_content)
|
||||
print(f"✅ 英文报告已保存到: {md_en_path}")
|
||||
print(f"✅ English report saved to: {md_en_path}")
|
||||
|
||||
# 保存 JSON 数据
|
||||
# Save JSON snapshot
|
||||
json_path = script_dir / "docs" / "community-stats.json"
|
||||
stats_client.save_json(stats, str(json_path))
|
||||
|
||||
# 生成 Shields.io endpoint JSON (用于动态徽章)
|
||||
# Generate Shields.io endpoint JSON (dynamic badges)
|
||||
badges_dir = script_dir / "docs" / "badges"
|
||||
|
||||
# 生成徽章
|
||||
# Generate badges
|
||||
stats_client.generate_shields_endpoints(stats, str(badges_dir))
|
||||
|
||||
# 生成并上传 SVG 图表 (每日更新 Gist, README URL 保持不变)
|
||||
# Generate and upload SVG chart (if Gist is configured)
|
||||
stats_client.upload_chart_svg()
|
||||
|
||||
# 更新 README 文件
|
||||
# Update README files
|
||||
readme_path = script_dir / "README.md"
|
||||
readme_cn_path = script_dir / "README_CN.md"
|
||||
stats_client.update_readme(stats, str(readme_path), lang="en")
|
||||
stats_client.update_readme(stats, str(readme_cn_path), lang="zh")
|
||||
|
||||
# 更新 docs 中的图表
|
||||
# Update charts in docs pages
|
||||
stats_client.update_docs_chart(str(md_en_path), lang="en")
|
||||
stats_client.update_docs_chart(str(md_zh_path), lang="zh")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user