Compare commits

..

21 Commits

Author SHA1 Message Date
fujie
e17818bf6c chore: re-trigger release for v0.6.0 2026-02-09 19:54:49 +08:00
fujie
c5c9fd9d57 fix(ci): fix shell script error when commit messages contain backticks 2026-02-09 19:53:23 +08:00
fujie
5c9875d390 feat: add openwebui_id to the GitHub Copilot SDK files filter plugin metadata. 2026-02-09 19:48:41 +08:00
fujie
9185f88d40 feat: release github-copilot-sdk v0.6.0 and files-filter v0.1.2 2026-02-09 19:45:23 +08:00
github-actions[bot]
1fcad993ea chore: update community stats - followers increased (208 -> 210) 2026-02-09 03:10:29 +00:00
github-actions[bot]
1594ea3a20 chore: update community stats - followers increased (207 -> 208) 2026-02-09 00:44:14 +00:00
github-actions[bot]
ee681ddad9 chore: update community stats - followers increased (206 -> 207) 2026-02-08 21:11:27 +00:00
github-actions[bot]
881597fd51 chore: update community stats - followers increased (205 -> 206) 2026-02-08 07:21:06 +00:00
github-actions[bot]
5772b1c65f chore: update community stats - plugin version updated, points increased (258 -> 259) 2026-02-08 00:53:22 +00:00
fujie
3d4b4b96e8 fix(docs): Repair broken valves table in README 2026-02-08 08:33:02 +08:00
fujie
409d2f663f feat(copilot): Release v0.5.1 - Smarter BYOK, Tool Caching & Refined Docs 2026-02-08 08:21:32 +08:00
github-actions[bot]
de9948a5b0 chore: update community stats - followers increased (204 -> 205) 2026-02-07 23:12:20 +00:00
github-actions[bot]
0a51b4cfca chore: update community stats - points increased (257 -> 258) 2026-02-07 18:13:00 +00:00
github-actions[bot]
f8eb096300 chore: update community stats - plugin version updated 2026-02-07 11:08:46 +00:00
fujie
f6369a1591 feat: release export-to-docx v0.4.4 w/ formatting & font fixes 2026-02-07 18:14:11 +08:00
github-actions[bot]
81634f57fa chore: update community stats - points increased (254 -> 257) 2026-02-07 09:12:46 +00:00
github-actions[bot]
70dc62aaab chore: update community stats - points increased (246 -> 254), followers increased (203 -> 204) 2026-02-07 06:19:35 +00:00
fujie
8762c05e04 docs: add explicit Copilot subscription requirement to README 2026-02-07 13:41:23 +08:00
github-actions[bot]
db52ac1041 chore: update community stats - plugin version updated 2026-02-07 05:23:23 +00:00
fujie
795ac34cd9 chore: archive development research artifacts and debug scripts 2026-02-07 12:49:42 +08:00
fujie
89a12a4fe8 docs: enforce English-only for commit messages and release notes 2026-02-07 12:38:46 +08:00
69 changed files with 8657 additions and 3015 deletions

View File

@@ -11,6 +11,7 @@ This workflow outlines the standard process for developing, documenting, and rel
Reference: `.github/copilot-instructions.md`
### Bilingual Requirement
Every plugin **MUST** have bilingual versions for both code and documentation:
- **Code**:
@@ -21,6 +22,7 @@ Every plugin **MUST** have bilingual versions for both code and documentation:
- Chinese: `plugins/{type}/{name}/README_CN.md`
### Code Structure
- **Docstring**: Must include `title`, `author`, `version`, `description`, etc.
- **Valves**: Use `pydantic` for configuration.
- **Database**: Re-use `open_webui.internal.db` shared connection.
@@ -32,8 +34,9 @@ Every plugin **MUST** have bilingual versions for both code and documentation:
- Use Chat Persistence API for database storage
- Always update both `messages[]` and `history.messages`
### Commit Messages
- **Language**: **English ONLY**. Do not use Chinese in commit messages.
### Commit Messages & Release Notes
- **Language**: **English ONLY**. Do not use Chinese in commit messages or release notes.
- **Format**: Conventional Commits (e.g., `feat:`, `fix:`, `docs:`).
## 2. Documentation Updates
@@ -41,10 +44,16 @@ Every plugin **MUST** have bilingual versions for both code and documentation:
When adding or updating a plugin, you **MUST** update the following documentation files to maintain consistency:
### Plugin Directory
- `README.md`: Update version, description, and usage. **Explicitly describe new features in a prominent position at the beginning.**
- `README_CN.md`: Update version, description, and usage. **Explicitly describe new features in a prominent position at the beginning.**
- `README.md`: Update version, description, and usage.
- **Key Capabilities**: **MUST** include ALL core functionalities and features. Do not only list new features in "What's New".
- **What's New**: Explicitly describe only the latest changes/updates in a prominent position at the beginning. This section is dynamic and changes with versions.
- `README_CN.md`: Update version, description, and usage.
- **核心功能 (Key Capabilities)**: **必须**包含所有核心功能和特性,不能只在 "What's New" 中列出。
- **最新更新 (What's New)**: 在开头显眼位置明确描述最新的更改/更新。此部分是动态的,随版本变化。
### Global Documentation (`docs/`)
- **Index Pages**:
- `docs/plugins/{type}/index.md`: Add/Update list item with **correct version**.
- `docs/plugins/{type}/index.zh.md`: Add/Update list item with **correct version**.
@@ -53,6 +62,7 @@ When adding or updating a plugin, you **MUST** update the following documentatio
- `docs/plugins/{type}/{name}.zh.md`: Ensure content matches README_CN.
### Root README
- `README.md`: Add to "Featured Plugins" if applicable.
- `README_CN.md`: Add to "Featured Plugins" if applicable.
@@ -61,6 +71,7 @@ When adding or updating a plugin, you **MUST** update the following documentatio
Reference: `.github/workflows/release.yml`
### Version Bumping
- **Rule**: Version bump is required **ONLY when the user explicitly requests a release**. Regular code changes do NOT require version bumps.
- **Format**: Semantic Versioning (e.g., `1.0.0` -> `1.0.1`).
- **When to Bump**: Only update the version when:
@@ -78,13 +89,14 @@ Reference: `.github/workflows/release.yml`
8. Docs Detail CN (`docs/.../{name}.zh.md`)
### Automated Release Process
1. **Trigger**: Push to `main` branch with changes in `plugins/**/*.py`.
2. **Detection**: `scripts/extract_plugin_versions.py` detects changed plugins and compares versions.
3. **Release**:
1. **Trigger**: Push to `main` branch with changes in `plugins/**/*.py`.
2. **Detection**: `scripts/extract_plugin_versions.py` detects changed plugins and compares versions.
3. **Release**:
- Generates release notes based on changes.
- Creates a GitHub Release tag (e.g., `v2024.01.01-1`).
- Uploads individual `.py` files of **changed plugins only** as assets.
4. **Market Publishing**:
4. **Market Publishing**:
- Workflow: `.github/workflows/publish_plugin.yml`
- Trigger: Release published.
- Action: Automatically updates the plugin code and metadata on OpenWebUI.com using `scripts/publish_plugin.py`.
@@ -95,6 +107,7 @@ Reference: `.github/workflows/release.yml`
- Example: `https://github.com/Fu-Jie/awesome-openwebui/blob/main/plugins/filters/folder-memory/README.md`
### Pull Request Check
- Workflow: `.github/workflows/plugin-version-check.yml`
- Checks if plugin files are modified.
- **Fails** if version number is not updated.
@@ -114,5 +127,3 @@ Before committing:
## 5. Git Operations (Agent Rules)
Strictly follow the rules defined in `.github/copilot-instructions.md`**Git Operations (Agent Rules)** section.

View File

@@ -889,6 +889,9 @@ Filter 实例是**单例 (Singleton)**。
- [ ] 使用 logging 而非 print
- [ ] 测试双语界面
- [ ] **一致性检查**: 确保文档、代码、README 同步
- [ ] **README 结构**:
- **Key Capabilities** (英文) / **核心功能** (中文): 必须包含所有核心功能
- **What's New** (英文) / **最新更新** (中文): 仅包含最新版本的变更信息
### 2. 🔄 一致性维护 (Consistency Maintenance)

View File

@@ -324,13 +324,13 @@ jobs:
- name: Generate release notes
id: notes
env:
VERSION: ${{ steps.version.outputs.version }}
TITLE: ${{ github.event.inputs.release_title }}
NOTES: ${{ github.event.inputs.release_notes }}
DETECTED_CHANGES: ${{ needs.check-changes.outputs.release_notes }}
COMMITS: ${{ steps.commits.outputs.commits }}
run: |
VERSION="${{ steps.version.outputs.version }}"
TITLE="${{ github.event.inputs.release_title }}"
NOTES="${{ github.event.inputs.release_notes }}"
DETECTED_CHANGES="${{ needs.check-changes.outputs.release_notes }}"
COMMITS="${{ steps.commits.outputs.commits }}"
echo "# ${VERSION} Release / 发布" > release_notes.md
echo "" >> release_notes.md

View File

@@ -10,28 +10,28 @@ A collection of enhancements, plugins, and prompts for [OpenWebUI](https://githu
<!-- STATS_START -->
## 📊 Community Stats
> 🕐 Auto-updated: 2026-02-07 09:38
> 🕐 Auto-updated: 2026-02-09 11:10
| 👤 Author | 👥 Followers | ⭐ Points | 🏆 Contributions |
|:---:|:---:|:---:|:---:|
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **203** | **246** | **40** |
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **210** | **259** | **43** |
| 📝 Posts | ⬇️ Downloads | 👁️ Views | 👍 Upvotes | 💾 Saves |
|:---:|:---:|:---:|:---:|:---:|
| **20** | **3675** | **43326** | **213** | **253** |
| **20** | **3909** | **45507** | **221** | **264** |
### 🔥 Top 6 Popular Plugins
> 🕐 Auto-updated: 2026-02-07 09:38
> 🕐 Auto-updated: 2026-02-09 11:10
| Rank | Plugin | Version | Downloads | Views | Updated |
|:---:|------|:---:|:---:|:---:|:---:|
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.2 | 877 | 7765 | 2026-01-28 |
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.5.0 | 631 | 5825 | 2026-01-30 |
| 🥉 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.3 | 343 | 2659 | 2026-01-28 |
| 4⃣ | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 327 | 1519 | 2026-01-29 |
| 5⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.2.2 | 327 | 3412 | 2026-01-28 |
| 6⃣ | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | 1.2.4 | 293 | 4243 | 2026-01-29 |
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.2 | 916 | 8070 | 2026-01-28 |
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.5.0 | 665 | 6205 | 2026-01-30 |
| 🥉 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.4 | 365 | 2841 | 2026-02-07 |
| 4⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.2.2 | 350 | 3580 | 2026-01-28 |
| 5⃣ | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 334 | 1569 | 2026-01-29 |
| 6⃣ | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | 1.2.4 | 310 | 4433 | 2026-01-29 |
*See full stats in [Community Stats Report](./docs/community-stats.md)*
<!-- STATS_END -->

View File

@@ -7,28 +7,28 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
<!-- STATS_START -->
## 📊 社区统计
> 🕐 自动更新于 2026-02-07 09:38
> 🕐 自动更新于 2026-02-09 11:10
| 👤 作者 | 👥 粉丝 | ⭐ 积分 | 🏆 贡献 |
|:---:|:---:|:---:|:---:|
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **203** | **246** | **40** |
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **210** | **259** | **43** |
| 📝 发布 | ⬇️ 下载 | 👁️ 浏览 | 👍 点赞 | 💾 收藏 |
|:---:|:---:|:---:|:---:|:---:|
| **20** | **3675** | **43326** | **213** | **253** |
| **20** | **3909** | **45507** | **221** | **264** |
### 🔥 热门插件 Top 6
> 🕐 自动更新于 2026-02-07 09:38
> 🕐 自动更新于 2026-02-09 11:10
| 排名 | 插件 | 版本 | 下载 | 浏览 | 更新日期 |
|:---:|------|:---:|:---:|:---:|:---:|
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.2 | 877 | 7765 | 2026-01-28 |
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.5.0 | 631 | 5825 | 2026-01-30 |
| 🥉 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.3 | 343 | 2659 | 2026-01-28 |
| 4⃣ | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 327 | 1519 | 2026-01-29 |
| 5⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.2.2 | 327 | 3412 | 2026-01-28 |
| 6⃣ | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | 1.2.4 | 293 | 4243 | 2026-01-29 |
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | 0.9.2 | 916 | 8070 | 2026-01-28 |
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.5.0 | 665 | 6205 | 2026-01-30 |
| 🥉 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.4 | 365 | 2841 | 2026-02-07 |
| 4⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.2.2 | 350 | 3580 | 2026-01-28 |
| 5⃣ | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 334 | 1569 | 2026-01-29 |
| 6⃣ | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | 1.2.4 | 310 | 4433 | 2026-01-29 |
*完整统计请查看 [社区统计报告](./docs/community-stats.zh.md)*
<!-- STATS_END -->

View File

@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
"label": "downloads",
"message": "3.7k",
"message": "3.9k",
"color": "blue",
"namedLogo": "openwebui"
}

View File

@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"label": "followers",
"message": "203",
"message": "210",
"color": "blue"
}

View File

@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"label": "points",
"message": "246",
"message": "259",
"color": "orange"
}

View File

@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"label": "upvotes",
"message": "213",
"message": "221",
"color": "brightgreen"
}

View File

@@ -1,13 +1,14 @@
{
"total_posts": 20,
"total_downloads": 3675,
"total_views": 43326,
"total_upvotes": 213,
"total_downloads": 3909,
"total_views": 45507,
"total_upvotes": 221,
"total_downvotes": 2,
"total_saves": 253,
"total_comments": 46,
"total_saves": 264,
"total_comments": 51,
"by_type": {
"action": 15,
"pipe": 1,
"action": 14,
"unknown": 4,
"filter": 1
},
@@ -19,10 +20,10 @@
"version": "0.9.2",
"author": "Fu-Jie",
"description": "Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.",
"downloads": 877,
"views": 7765,
"upvotes": 21,
"saves": 47,
"downloads": 916,
"views": 8070,
"upvotes": 22,
"saves": 50,
"comments": 13,
"created_at": "2025-12-30",
"updated_at": "2026-01-28",
@@ -35,47 +36,31 @@
"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": 631,
"views": 5825,
"upvotes": 23,
"saves": 33,
"downloads": 665,
"views": 6205,
"upvotes": 24,
"saves": 34,
"comments": 10,
"created_at": "2025-12-28",
"updated_at": "2026-01-30",
"url": "https://openwebui.com/posts/smart_infographic_ad6f0c7f"
},
{
"title": "Export to Word (Enhanced)",
"title": "Export to Word Enhanced",
"slug": "export_to_word_enhanced_formatting_fca6a315",
"type": "action",
"version": "0.4.3",
"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": 343,
"views": 2659,
"upvotes": 12,
"downloads": 365,
"views": 2841,
"upvotes": 14,
"saves": 26,
"comments": 2,
"comments": 3,
"created_at": "2026-01-03",
"updated_at": "2026-01-28",
"updated_at": "2026-02-07",
"url": "https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315"
},
{
"title": "Export to Excel",
"slug": "export_mulit_table_to_excel_244b8f9d",
"type": "action",
"version": "0.3.7",
"author": "Fu-Jie",
"description": "Extracts tables from chat messages and exports them to Excel (.xlsx) files with smart formatting.",
"downloads": 327,
"views": 1519,
"upvotes": 7,
"saves": 6,
"comments": 0,
"created_at": "2025-05-30",
"updated_at": "2026-01-29",
"url": "https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d"
},
{
"title": "Async Context Compression",
"slug": "async_context_compression_b1655bc8",
@@ -83,15 +68,31 @@
"version": "1.2.2",
"author": "Fu-Jie",
"description": "Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.",
"downloads": 327,
"views": 3412,
"upvotes": 13,
"downloads": 350,
"views": 3580,
"upvotes": 14,
"saves": 33,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-01-28",
"url": "https://openwebui.com/posts/async_context_compression_b1655bc8"
},
{
"title": "Export to Excel",
"slug": "export_mulit_table_to_excel_244b8f9d",
"type": "action",
"version": "0.3.7",
"author": "Fu-Jie",
"description": "Extracts tables from chat messages and exports them to Excel (.xlsx) files with smart formatting.",
"downloads": 334,
"views": 1569,
"upvotes": 7,
"saves": 6,
"comments": 0,
"created_at": "2025-05-30",
"updated_at": "2026-01-29",
"url": "https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d"
},
{
"title": "Markdown Normalizer",
"slug": "markdown_normalizer_baaa8732",
@@ -99,10 +100,10 @@
"version": "1.2.4",
"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.",
"downloads": 293,
"views": 4243,
"downloads": 310,
"views": 4433,
"upvotes": 17,
"saves": 27,
"saves": 28,
"comments": 5,
"created_at": "2026-01-12",
"updated_at": "2026-01-29",
@@ -115,8 +116,8 @@
"version": "0.2.4",
"author": "Fu-Jie",
"description": "Quickly generates beautiful flashcards from text, extracting key points and categories.",
"downloads": 213,
"views": 3239,
"downloads": 217,
"views": 3302,
"upvotes": 13,
"saves": 14,
"comments": 2,
@@ -131,10 +132,10 @@
"version": "",
"author": "",
"description": "",
"downloads": 141,
"views": 2180,
"downloads": 178,
"views": 2475,
"upvotes": 8,
"saves": 3,
"saves": 4,
"comments": 0,
"created_at": "2026-01-28",
"updated_at": "2026-01-28",
@@ -147,8 +148,8 @@
"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": 132,
"views": 1155,
"downloads": 144,
"views": 1218,
"upvotes": 6,
"saves": 11,
"comments": 0,
@@ -157,21 +158,37 @@
"url": "https://openwebui.com/posts/deep_dive_c0b846e4"
},
{
"title": "导出为 Word (增强版)",
"title": "导出为Word增强版",
"slug": "导出为_word_支持公式流程图表格和代码块_8a6306c0",
"type": "action",
"version": "0.4.3",
"version": "0.4.4",
"author": "Fu-Jie",
"description": "将对话导出为 Word (.docx),支持 Mermaid 图表 (客户端渲染 SVG+PNG)、LaTeX 数学公式、真实超链接、增强表格格式、代码高亮和引用块。",
"downloads": 119,
"views": 2104,
"downloads": 122,
"views": 2149,
"upvotes": 13,
"saves": 6,
"comments": 4,
"created_at": "2026-01-04",
"updated_at": "2026-01-28",
"updated_at": "2026-02-07",
"url": "https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0"
},
{
"title": "GitHub Copilot Official SDK Pipe",
"slug": "github_copilot_official_sdk_pipe_ce96f7b4",
"type": "pipe",
"version": "0.5.1",
"author": "Fu-Jie",
"description": "Integrate GitHub Copilot SDK. Supports dynamic models, multi-turn conversation, streaming, multimodal input, infinite sessions, and frontend debug logging.",
"downloads": 71,
"views": 2069,
"upvotes": 13,
"saves": 6,
"comments": 4,
"created_at": "2026-01-26",
"updated_at": "2026-02-08",
"url": "https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4"
},
{
"title": "智能信息图",
"slug": "智能信息图_e04a48ff",
@@ -179,8 +196,8 @@
"version": "1.5.0",
"author": "Fu-Jie",
"description": "基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。",
"downloads": 56,
"views": 1003,
"downloads": 58,
"views": 1031,
"upvotes": 10,
"saves": 1,
"comments": 0,
@@ -195,8 +212,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": 55,
"views": 1195,
"downloads": 58,
"views": 1247,
"upvotes": 6,
"saves": 8,
"comments": 0,
@@ -204,22 +221,6 @@
"updated_at": "2026-01-20",
"url": "https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2"
},
{
"title": "GitHub Copilot Official SDK Pipe",
"slug": "github_copilot_official_sdk_pipe_ce96f7b4",
"type": "action",
"version": "0.2.3",
"author": "Fu-Jie",
"description": "Integrate GitHub Copilot SDK. Supports dynamic models, multi-turn conversation, streaming, multimodal input, infinite sessions, and frontend debug logging.",
"downloads": 49,
"views": 1835,
"upvotes": 11,
"saves": 5,
"comments": 1,
"created_at": "2026-01-26",
"updated_at": "2026-01-29",
"url": "https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4"
},
{
"title": "思维导图",
"slug": "智能生成交互式思维导图帮助用户可视化知识_8d4b097b",
@@ -228,7 +229,7 @@
"author": "Fu-Jie",
"description": "智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。",
"downloads": 37,
"views": 563,
"views": 573,
"upvotes": 6,
"saves": 2,
"comments": 0,
@@ -243,10 +244,10 @@
"version": "1.2.2",
"author": "Fu-Jie",
"description": "通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。",
"downloads": 31,
"views": 624,
"downloads": 33,
"views": 646,
"upvotes": 7,
"saves": 4,
"saves": 5,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-01-28",
@@ -260,7 +261,7 @@
"author": "Fu-Jie",
"description": "快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类。",
"downloads": 27,
"views": 659,
"views": 670,
"upvotes": 8,
"saves": 1,
"comments": 0,
@@ -275,8 +276,8 @@
"version": "1.0.0",
"author": "Fu-Jie",
"description": "全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。",
"downloads": 17,
"views": 400,
"downloads": 24,
"views": 419,
"upvotes": 5,
"saves": 1,
"comments": 0,
@@ -292,10 +293,10 @@
"author": "",
"description": "",
"downloads": 0,
"views": 1421,
"upvotes": 11,
"saves": 16,
"comments": 7,
"views": 1460,
"upvotes": 12,
"saves": 18,
"comments": 8,
"created_at": "2026-01-25",
"updated_at": "2026-01-28",
"url": "https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e"
@@ -308,7 +309,7 @@
"author": "",
"description": "",
"downloads": 0,
"views": 134,
"views": 139,
"upvotes": 2,
"saves": 0,
"comments": 0,
@@ -324,9 +325,9 @@
"author": "",
"description": "",
"downloads": 0,
"views": 1391,
"views": 1411,
"upvotes": 14,
"saves": 9,
"saves": 10,
"comments": 2,
"created_at": "2026-01-10",
"updated_at": "2026-01-10",
@@ -338,11 +339,11 @@
"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": 203,
"followers": 210,
"following": 4,
"total_points": 246,
"post_points": 211,
"comment_points": 35,
"contributions": 40
"total_points": 259,
"post_points": 219,
"comment_points": 40,
"contributions": 43
}
}

View File

@@ -1,21 +1,22 @@
# 📊 OpenWebUI Community Stats Report
> 📅 Updated: 2026-02-07 09:38
> 📅 Updated: 2026-02-09 11:10
## 📈 Overview
| Metric | Value |
|------|------|
| 📝 Total Posts | 20 |
| ⬇️ Total Downloads | 3675 |
| 👁️ Total Views | 43326 |
| 👍 Total Upvotes | 213 |
| 💾 Total Saves | 253 |
| 💬 Total Comments | 46 |
| ⬇️ Total Downloads | 3909 |
| 👁️ Total Views | 45507 |
| 👍 Total Upvotes | 221 |
| 💾 Total Saves | 264 |
| 💬 Total Comments | 51 |
## 📂 By Type
- **action**: 15
- **pipe**: 1
- **action**: 14
- **unknown**: 4
- **filter**: 1
@@ -23,23 +24,23 @@
| 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 | 0.9.2 | 877 | 7765 | 21 | 47 | 2026-01-28 |
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.5.0 | 631 | 5825 | 23 | 33 | 2026-01-30 |
| 3 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 343 | 2659 | 12 | 26 | 2026-01-28 |
| 4 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 327 | 1519 | 7 | 6 | 2026-01-29 |
| 5 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.2.2 | 327 | 3412 | 13 | 33 | 2026-01-28 |
| 6 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.4 | 293 | 4243 | 17 | 27 | 2026-01-29 |
| 7 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 213 | 3239 | 13 | 14 | 2026-01-28 |
| 8 | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | unknown | | 141 | 2180 | 8 | 3 | 2026-01-28 |
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 132 | 1155 | 6 | 11 | 2026-01-08 |
| 10 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 119 | 2104 | 13 | 6 | 2026-01-28 |
| 11 | [智能信息图](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.5.0 | 56 | 1003 | 10 | 1 | 2026-01-29 |
| 12 | [📂 Folder Memory Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | 0.1.0 | 55 | 1195 | 6 | 8 | 2026-01-20 |
| 13 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | action | 0.2.3 | 49 | 1835 | 11 | 5 | 2026-01-29 |
| 14 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.2 | 37 | 563 | 6 | 2 | 2026-01-28 |
| 15 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.2.2 | 31 | 624 | 7 | 4 | 2026-01-28 |
| 16 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 27 | 659 | 8 | 1 | 2026-01-28 |
| 17 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 17 | 400 | 5 | 1 | 2026-01-08 |
| 18 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | unknown | | 0 | 1421 | 11 | 16 | 2026-01-28 |
| 19 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 134 | 2 | 0 | 2026-01-14 |
| 20 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 1391 | 14 | 9 | 2026-01-10 |
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.2 | 916 | 8070 | 22 | 50 | 2026-01-28 |
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.5.0 | 665 | 6205 | 24 | 34 | 2026-01-30 |
| 3 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.4 | 365 | 2841 | 14 | 26 | 2026-02-07 |
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.2.2 | 350 | 3580 | 14 | 33 | 2026-01-28 |
| 5 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 334 | 1569 | 7 | 6 | 2026-01-29 |
| 6 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.4 | 310 | 4433 | 17 | 28 | 2026-01-29 |
| 7 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 217 | 3302 | 13 | 14 | 2026-01-28 |
| 8 | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | unknown | | 178 | 2475 | 8 | 4 | 2026-01-28 |
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 144 | 1218 | 6 | 11 | 2026-01-08 |
| 10 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.4 | 122 | 2149 | 13 | 6 | 2026-02-07 |
| 11 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | 0.5.1 | 71 | 2069 | 13 | 6 | 2026-02-08 |
| 12 | [智能信息图](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.5.0 | 58 | 1031 | 10 | 1 | 2026-01-29 |
| 13 | [📂 Folder Memory Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | 0.1.0 | 58 | 1247 | 6 | 8 | 2026-01-20 |
| 14 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.2 | 37 | 573 | 6 | 2 | 2026-01-28 |
| 15 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.2.2 | 33 | 646 | 7 | 5 | 2026-01-28 |
| 16 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 27 | 670 | 8 | 1 | 2026-01-28 |
| 17 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 24 | 419 | 5 | 1 | 2026-01-08 |
| 18 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | unknown | | 0 | 1460 | 12 | 18 | 2026-01-28 |
| 19 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 139 | 2 | 0 | 2026-01-14 |
| 20 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 1411 | 14 | 10 | 2026-01-10 |

View File

@@ -1,21 +1,22 @@
# 📊 OpenWebUI 社区统计报告
> 📅 更新时间: 2026-02-07 09:38
> 📅 更新时间: 2026-02-09 11:10
## 📈 总览
| 指标 | 数值 |
|------|------|
| 📝 发布数量 | 20 |
| ⬇️ 总下载量 | 3675 |
| 👁️ 总浏览量 | 43326 |
| 👍 总点赞数 | 213 |
| 💾 总收藏数 | 253 |
| 💬 总评论数 | 46 |
| ⬇️ 总下载量 | 3909 |
| 👁️ 总浏览量 | 45507 |
| 👍 总点赞数 | 221 |
| 💾 总收藏数 | 264 |
| 💬 总评论数 | 51 |
## 📂 按类型分类
- **action**: 15
- **pipe**: 1
- **action**: 14
- **unknown**: 4
- **filter**: 1
@@ -23,23 +24,23 @@
| 排名 | 标题 | 类型 | 版本 | 下载 | 浏览 | 点赞 | 收藏 | 更新日期 |
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.2 | 877 | 7765 | 21 | 47 | 2026-01-28 |
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.5.0 | 631 | 5825 | 23 | 33 | 2026-01-30 |
| 3 | [Export to Word (Enhanced)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.3 | 343 | 2659 | 12 | 26 | 2026-01-28 |
| 4 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 327 | 1519 | 7 | 6 | 2026-01-29 |
| 5 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.2.2 | 327 | 3412 | 13 | 33 | 2026-01-28 |
| 6 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.4 | 293 | 4243 | 17 | 27 | 2026-01-29 |
| 7 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 213 | 3239 | 13 | 14 | 2026-01-28 |
| 8 | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | unknown | | 141 | 2180 | 8 | 3 | 2026-01-28 |
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 132 | 1155 | 6 | 11 | 2026-01-08 |
| 10 | [导出为 Word (增强版)](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.3 | 119 | 2104 | 13 | 6 | 2026-01-28 |
| 11 | [智能信息图](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.5.0 | 56 | 1003 | 10 | 1 | 2026-01-29 |
| 12 | [📂 Folder Memory Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | 0.1.0 | 55 | 1195 | 6 | 8 | 2026-01-20 |
| 13 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | action | 0.2.3 | 49 | 1835 | 11 | 5 | 2026-01-29 |
| 14 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.2 | 37 | 563 | 6 | 2 | 2026-01-28 |
| 15 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.2.2 | 31 | 624 | 7 | 4 | 2026-01-28 |
| 16 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 27 | 659 | 8 | 1 | 2026-01-28 |
| 17 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 17 | 400 | 5 | 1 | 2026-01-08 |
| 18 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | unknown | | 0 | 1421 | 11 | 16 | 2026-01-28 |
| 19 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 134 | 2 | 0 | 2026-01-14 |
| 20 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 1391 | 14 | 9 | 2026-01-10 |
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | 0.9.2 | 916 | 8070 | 22 | 50 | 2026-01-28 |
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.5.0 | 665 | 6205 | 24 | 34 | 2026-01-30 |
| 3 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.4 | 365 | 2841 | 14 | 26 | 2026-02-07 |
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.2.2 | 350 | 3580 | 14 | 33 | 2026-01-28 |
| 5 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 334 | 1569 | 7 | 6 | 2026-01-29 |
| 6 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.4 | 310 | 4433 | 17 | 28 | 2026-01-29 |
| 7 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 217 | 3302 | 13 | 14 | 2026-01-28 |
| 8 | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | unknown | | 178 | 2475 | 8 | 4 | 2026-01-28 |
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 144 | 1218 | 6 | 11 | 2026-01-08 |
| 10 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.4 | 122 | 2149 | 13 | 6 | 2026-02-07 |
| 11 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | 0.5.1 | 71 | 2069 | 13 | 6 | 2026-02-08 |
| 12 | [智能信息图](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.5.0 | 58 | 1031 | 10 | 1 | 2026-01-29 |
| 13 | [📂 Folder Memory Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | 0.1.0 | 58 | 1247 | 6 | 8 | 2026-01-20 |
| 14 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.2 | 37 | 573 | 6 | 2 | 2026-01-28 |
| 15 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.2.2 | 33 | 646 | 7 | 5 | 2026-01-28 |
| 16 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 27 | 670 | 8 | 1 | 2026-01-28 |
| 17 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 24 | 419 | 5 | 1 | 2026-01-08 |
| 18 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | unknown | | 0 | 1460 | 12 | 18 | 2026-01-28 |
| 19 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 139 | 2 | 0 | 2026-01-14 |
| 20 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 1411 | 14 | 10 | 2026-01-10 |

View File

@@ -1,7 +1,7 @@
# Export to Word
<span class="category-badge action">Action</span>
<span class="version-badge">v0.4.3</span>
<span class="version-badge">v0.4.4</span>
Export conversation to Word (.docx) with **syntax highlighting**, **native math equations**, **Mermaid diagrams**, **citations**, and **enhanced table formatting**.
@@ -53,11 +53,17 @@ You can configure the following settings via the **Valves** button in the plugin
| `MATH_ENABLE` | Enable LaTeX math block conversion. | `True` |
| `MATH_INLINE_DOLLAR_ENABLE` | Enable inline `$ ... $` math conversion. | `True` |
## 🔥 What's New in v0.4.3
## 🔥 What's New in v0.4.4
- 🧹 **Content Cleanup**: Enhanced stripping of `<details>` blocks (often used for tool calls/thinking process) to ensure a clean final document.
- 📄 **Standard Document Formatting**: Applied professional document formatting standards for titles and headings (centered title, bold, optimized font sizes and spacing), including GB/T compliance for Chinese content.
- 🔠 **Font Rendering Fix**: Fixed an issue where CJK characters would fallback to MS Gothic in Word; now correctly uses the configured Asian font (e.g., SimSun).
- ⚙️ **Title Alignment**: Added `TITLE_ALIGNMENT` valve to configure document title alignment (left, center, right).
### User-Level Configuration (UserValves)
Users can override the following settings in their personal settings:
- `TITLE_SOURCE`
- `UI_LANGUAGE`
- `FONT_LATIN`, `FONT_ASIAN`, `FONT_CODE`
@@ -120,4 +126,4 @@ Users can override the following settings in their personal settings:
## Source Code
[:fontawesome-brands-github: View on GitHub](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/export_to_docx){ .md-button }
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.4.3 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.4.4 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)

View File

@@ -1,7 +1,7 @@
# Export to Word导出为 Word
<span class="category-badge action">Action</span>
<span class="version-badge">v0.4.3</span>
<span class="version-badge">v0.4.4</span>
将当前对话导出为完美格式的 Word 文档,支持**代码语法高亮**、**原生数学公式**、**Mermaid 图表**、**引用资料**以及**增强表格**渲染。
@@ -53,9 +53,17 @@ Export to Word 插件会把聊天消息从 Markdown 转成精致的 Word 文档
| `启用数学公式` | 启用 LaTeX 数学公式块转换。 | `True` |
| `启用行内公式` | 启用行内 `$ ... $` 数学公式转换。 | `True` |
## 🔥 v0.4.4 更新内容
- 🧹 **内容清理加强**: 增强了对 `<details>` 块(通常包含工具调用或思考过程)的清理,确保最终文档整洁。
- 📄 **文档格式标准化**: 采用了专业的文档排版标准(兼容中文 GB/T 规范),标题居中加粗,各级标题使用标准字号和间距。
- 🔠 **字体渲染修复**: 修复了 CJK 字符在 Word 中回退到 MS Gothic 的问题;现在正确使用配置的中文字体(例如宋体)。
- ⚙️ **标题对齐配置**: 新增 `标题对齐方式` Valve支持配置文档标题的对齐方式左对齐、居中、右对齐
### 用户级配置 (UserValves)
用户可以在个人设置中覆盖以下配置:
- `文档标题来源`
- `界面语言`
- `英文字体`, `中文字体`, `代码字体`
@@ -117,4 +125,5 @@ Export to Word 插件会把聊天消息从 Markdown 转成精致的 Word 文档
## 源码
[:fontawes**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.4.3 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)/tree/main/plugins/actions/export_to_docx){ .md-button }
[:fontawesome-brands-github: View on GitHub](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/export_to_docx){ .md-button }
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.4.4 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)

View File

@@ -17,7 +17,7 @@ Actions are interactive plugins that:
<div class="grid cards" markdown>
- :material-brain:{ .lg .middle } **Smart Mind Map**
- :material-brain:{ .lg .middle } **Smart Mind Map**
---
@@ -27,7 +27,7 @@ Actions are interactive plugins that:
[:octicons-arrow-right-24: Documentation](smart-mind-map.md)
- :material-chart-bar:{ .lg .middle } **Smart Infographic**
- :material-chart-bar:{ .lg .middle } **Smart Infographic**
---
@@ -37,7 +37,7 @@ Actions are interactive plugins that:
[:octicons-arrow-right-24: Documentation](smart-infographic.md)
- :material-card-text:{ .lg .middle } **Flash Card**
- :material-card-text:{ .lg .middle } **Flash Card**
---
@@ -47,7 +47,7 @@ Actions are interactive plugins that:
[:octicons-arrow-right-24: Documentation](flash-card.md)
- :material-file-excel:{ .lg .middle } **Export to Excel**
- :material-file-excel:{ .lg .middle } **Export to Excel**
---
@@ -57,17 +57,17 @@ Actions are interactive plugins that:
[:octicons-arrow-right-24: Documentation](export-to-excel.md)
- :material-file-word-box:{ .lg .middle } **Export to Word (Enhanced Formatting)**
- :material-file-word-box:{ .lg .middle } **Export to Word (Enhanced Formatting)**
---
Export the current conversation to a formatted Word doc with **syntax highlighting**, **native math equations**, **Mermaid diagrams**, **citations**, and **enhanced table formatting**.
**Version:** 0.4.2
**Version:** 0.4.4
[:octicons-arrow-right-24: Documentation](export-to-word.md)
- :material-brain:{ .lg .middle } **Deep Dive**
- :material-brain:{ .lg .middle } **Deep Dive**
---
@@ -77,8 +77,6 @@ Actions are interactive plugins that:
[:octicons-arrow-right-24: Documentation](deep-dive.md)
</div>
---

View File

@@ -17,7 +17,7 @@ Actions 是交互式插件,能够:
<div class="grid cards" markdown>
- :material-brain:{ .lg .middle } **Smart Mind Map**
- :material-brain:{ .lg .middle } **Smart Mind Map**
---
@@ -27,7 +27,7 @@ Actions 是交互式插件,能够:
[:octicons-arrow-right-24: 查看文档](smart-mind-map.md)
- :material-chart-bar:{ .lg .middle } **Smart Infographic**
- :material-chart-bar:{ .lg .middle } **Smart Infographic**
---
@@ -37,7 +37,7 @@ Actions 是交互式插件,能够:
[:octicons-arrow-right-24: 查看文档](smart-infographic.md)
- :material-card-text:{ .lg .middle } **Flash Card闪记卡**
- :material-card-text:{ .lg .middle } **Flash Card闪记卡**
---
@@ -47,7 +47,7 @@ Actions 是交互式插件,能够:
[:octicons-arrow-right-24: 查看文档](flash-card.md)
- :material-file-excel:{ .lg .middle } **Export to Excel**
- :material-file-excel:{ .lg .middle } **Export to Excel**
---
@@ -57,17 +57,17 @@ Actions 是交互式插件,能够:
[:octicons-arrow-right-24: 查看文档](export-to-excel.md)
- :material-file-word-box:{ .lg .middle } **Word 导出 (格式增强)**
- :material-file-word-box:{ .lg .middle } **Word 导出 (格式增强)**
---
将当前对话导出为完美格式的 Word 文档,支持**代码语法高亮**、**原生数学公式**、**Mermaid 图表**、**引用资料**以及**增强表格**渲染。
**版本:** 0.4.2
**版本:** 0.4.4
[:octicons-arrow-right-24: 查看文档](export-to-word.md)
- :material-brain:{ .lg .middle } **精读 (Deep Dive)**
- :material-brain:{ .lg .middle } **精读 (Deep Dive)**
---
@@ -77,8 +77,6 @@ Actions 是交互式插件,能够:
[:octicons-arrow-right-24: 查看文档](deep-dive.zh.md)
</div>
---

View File

@@ -0,0 +1,53 @@
# GitHub Copilot SDK Files Filter (v0.1.2)
This is a dedicated **companion filter plugin** designed specifically for the [GitHub Copilot SDK Pipe](../pipes/github-copilot-sdk.md).
Its core mission is to **protect user-uploaded files from being "pre-processed" by the OpenWebUI core system, ensuring that the Copilot Agent receives the raw files for autonomous analysis.**
## 🎯 Why is this needed?
In OpenWebUI's default workflow, when you upload a file (e.g., PDF, Excel, Python script), OpenWebUI automatically initiates a **RAG (Retrieval-Augmented Generation)** process: parsing the file, vectorizing it, extracting text, and injecting it into the prompt.
While useful for standard models, this is often disruptive for a **Copilot SDK Agent**:
1. **Agent Needs Raw Files**: The Agent may need to run Python code to read an Excel file or analyze a full directory structure, not chopped-up text fragments.
2. **Context Pollution**: Large amounts of text injected by RAG consume tokens and can confuse the Agent about "where the file is."
3. **Conflicts**: If you have other multimodal plugins installed (like Gemini Filter), they might compete for file processing rights.
**This plugin acts as a "bodyguard" to solve these issues.**
## 🚀 How it Works
When you select a Copilot model (name containing `copilot_sdk`) in OpenWebUI and send a file:
1. **Intercept**: This plugin runs with high priority (Priority 0), before RAG and other filters.
2. **Relocate**: Detecting a Copilot model, it moves the `files` list from the request to a secure custom field `copilot_files`.
3. **Hide**: It clears the original `files` field.
4. **Status Update**: It emits a status message "Managed X files for Copilot (RAG Bypassed)" to the UI.
5. **Pass**: The OpenWebUI core sees an empty `files` list and **does not trigger RAG**.
6. **Deliver**: The subsequent [Copilot SDK Pipe](../pipes/github-copilot-sdk.md) plugin checks `copilot_files`, retrieves file information, and automatically copies them into the Agent's isolated workspace.
## 📦 Installation & Configuration
### 1. Installation
Import this plugin on the OpenWebUI **Functions** page.
### 2. Enable
Ensure this Filter is enabled globally or in chat settings.
### 3. Configuration (Valves)
Default settings work for most users unless you have specific needs:
| Parameter | Description | Default |
| :--- | :--- | :--- |
| **priority** | Execution priority of the filter. **Must be lower than OpenWebUI RAG priority**. | `0` |
| **target_model_keyword** | Keyword to identify Copilot models. Only models containing this keyword will trigger file interception. | `copilot_sdk` |
## ⚠️ Important Notes
* **Must be used with Copilot SDK Pipe**: If you install this plugin without the main Pipe plugin, uploaded files will simply "disappear" (as no subsequent plugin will look for them in `copilot_files`).
* **Gemini Filter Compatibility**: This plugin is fully compatible with the Gemini Multimodal Filter. As long as the priority is set correctly (This Plugin < Gemini Plugin), they can coexist without interference.
* **Physical File Path**: Ensure the `OPENWEBUI_UPLOAD_PATH` is correctly set in the Pipe plugin Valves for the actual file transport to work.

View File

@@ -0,0 +1,53 @@
# GitHub Copilot SDK 文件过滤器 (v0.1.2)
这是一个专门为 [GitHub Copilot SDK Pipe](../pipes/github-copilot-sdk.zh.md) 设计的**伴侣过滤器插件**。
它的核心使命是:**保护用户上传的文件不被 OpenWebUI 核心系统“抢先处理”,确保 Copilot Agent 能够接收到原始文件并进行自主分析。**
## 🎯 为什么需要它?
在 OpenWebUI 的默认流程中,当你上传一个文件(如 PDF、Excel、Python 脚本OpenWebUI 会自动启动 **RAG检索增强生成** 流程:解析文件、向量化、提取文本并注入到提示词中。
虽然这对普通模型很有用,但对于 **Copilot SDK Agent** 来说,这往往是干扰:
1. **Agent 需要原始文件**Agent 可能需要运行 Python 代码读取 Excel或者分析完整的代码结构而不是被切碎的文本片段。
2. **上下文污染**RAG 注入的大量文本会消耗 Token且容易让 Agent 混淆“文件在哪里”。
3. **冲突**:如果你同时安装了其他多模态插件(如 Gemini Filter它们可能会争夺文件的处理权。
**本插件就是为了解决这个问题而生的“保镖”。**
## 🚀 功能原理
当你在 OpenWebUI 中选择了一个 Copilot 模型(名称包含 `copilot_sdk`)并发送文件时:
1. **拦截 (Intercept)**本插件会以极高的优先级Priority 0运行先于 RAG 和其他过滤器。
2. **搬运 (Relocate)**:它检测到模型是 Copilot便将请求中的 `files`(文件列表)移动到一个安全的自定义字段 `copilot_files` 中。
3. **隐身 (Hide)**:它清空原始的 `files` 字段。
4. **状态更新**: 它会在 UI 中发出“已为 Copilot 管理 X 个文件 (已绕过 RAG)”的状态消息。
5. **放行 (Pass)**OpenWebUI 核心看到 `files` 为空,便**不会触发 RAG**。
6. **交付 (Deliver)**:后续的 [Copilot SDK Pipe](../pipes/github-copilot-sdk.zh.md) 插件会检查 `copilot_files`,从中获取文件信息,并自动将其复制到 Agent 的独立工作区中。
## 📦 安装与配置
### 1. 安装
在 OpenWebUI 的 **Functions** 页面导入此插件。
### 2. 启用
确保在全局或对话设置中启用了此 Filter。
### 3. 配置 (Valves)
通常不需要修改默认配置,除非你有特殊需求:
| 参数 | 说明 | 默认值 |
| :--- | :--- | :--- |
| **priority** | 过滤器的执行优先级。**必须小于 OpenWebUI RAG 的优先级**。 | `0` |
| **target_model_keyword** | 用于识别 Copilot 模型的关键词。只有包含此关键词的模型才会触发 file 拦截。 | `copilot_sdk` |
## ⚠️ 注意事项
* **必须配合 Copilot SDK Pipe 使用**:如果你没有安装主 Pipe 插件,本插件将导致上传的文件“凭空消失”(因为没有后续插件去 `copilot_files` 里找它们)。
* **Gemini Filter 兼容性**:本插件已完美兼容 Gemini 多模态过滤器。只要优先级设置正确(本插件 < Gemini 插件),它们可以共存互不干扰。
* **物理文件路径**: 确保在 Pipe 插件的 Valves 中正确设置了 `OPENWEBUI_UPLOAD_PATH`,以便文件自动搬运功能正常工作。

View File

@@ -16,7 +16,7 @@ Filters act as middleware in the message pipeline:
<div class="grid cards" markdown>
- :material-arrow-collapse-vertical:{ .lg .middle } **Async Context Compression**
- :material-arrow-collapse-vertical:{ .lg .middle } **Async Context Compression**
---
@@ -26,7 +26,7 @@ Filters act as middleware in the message pipeline:
[:octicons-arrow-right-24: Documentation](async-context-compression.md)
- :material-text-box-plus:{ .lg .middle } **Context Enhancement**
- :material-text-box-plus:{ .lg .middle } **Context Enhancement**
---
@@ -36,7 +36,7 @@ Filters act as middleware in the message pipeline:
[:octicons-arrow-right-24: Documentation](context-enhancement.md)
- :material-folder-refresh:{ .lg .middle } **Folder Memory**
- :material-folder-refresh:{ .lg .middle } **Folder Memory**
---
@@ -46,7 +46,7 @@ Filters act as middleware in the message pipeline:
[:octicons-arrow-right-24: Documentation](folder-memory.md)
- :material-format-paint:{ .lg .middle } **Markdown Normalizer**
- :material-format-paint:{ .lg .middle } **Markdown Normalizer**
---
@@ -56,7 +56,7 @@ Filters act as middleware in the message pipeline:
[:octicons-arrow-right-24: Documentation](markdown_normalizer.md)
- :material-merge:{ .lg .middle } **Multi-Model Context Merger**
- :material-merge:{ .lg .middle } **Multi-Model Context Merger**
---
@@ -66,7 +66,7 @@ Filters act as middleware in the message pipeline:
[:octicons-arrow-right-24: Documentation](multi-model-context-merger.md)
- :material-file-document-multiple:{ .lg .middle } **Web Gemini Multimodal Filter**
- :material-file-document-multiple:{ .lg .middle } **Web Gemini Multimodal Filter**
---
@@ -76,6 +76,16 @@ Filters act as middleware in the message pipeline:
[:octicons-arrow-right-24: Documentation](web-gemini-multimodel.md)
- :material-file-shield:{ .lg .middle } **Copilot SDK Files Filter**
---
A specialized filter to bypass OpenWebUI's default RAG for GitHub Copilot SDK models. It ensures the Agent receives raw files for autonomous analysis.
**Version:** 0.1.2
[:octicons-arrow-right-24: Documentation](github-copilot-sdk-files-filter.md)
</div>
---

View File

@@ -16,7 +16,7 @@ Filter 充当消息管线中的中间件:
<div class="grid cards" markdown>
- :material-arrow-collapse-vertical:{ .lg .middle } **Async Context Compression**
- :material-arrow-collapse-vertical:{ .lg .middle } **Async Context Compression**
---
@@ -26,7 +26,7 @@ Filter 充当消息管线中的中间件:
[:octicons-arrow-right-24: 查看文档](async-context-compression.md)
- :material-text-box-plus:{ .lg .middle } **Context Enhancement**
- :material-text-box-plus:{ .lg .middle } **Context Enhancement**
---
@@ -36,7 +36,7 @@ Filter 充当消息管线中的中间件:
[:octicons-arrow-right-24: 查看文档](context-enhancement.md)
- :material-folder-refresh:{ .lg .middle } **Folder Memory**
- :material-folder-refresh:{ .lg .middle } **Folder Memory**
---
@@ -46,7 +46,7 @@ Filter 充当消息管线中的中间件:
[:octicons-arrow-right-24: 查看文档](folder-memory.zh.md)
- :material-format-paint:{ .lg .middle } **Markdown Normalizer**
- :material-format-paint:{ .lg .middle } **Markdown Normalizer**
---
@@ -56,6 +56,16 @@ Filter 充当消息管线中的中间件:
[:octicons-arrow-right-24: 查看文档](markdown_normalizer.zh.md)
- :material-file-shield:{ .lg .middle } **Copilot SDK 文件过滤器**
---
专门用于绕过 OpenWebUI 默认 RAG 机制的过滤器,针对 GitHub Copilot SDK 模型。确保 Agent 能够接收到原始文件进行自主分析。
**版本:** 0.1.2
[:octicons-arrow-right-24: 查看文档](github-copilot-sdk-files-filter.zh.md)
</div>
---

View File

@@ -0,0 +1,80 @@
# GitHub Copilot SDK Plugin Deep Dive
**Version:** 0.6.0 | **Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Status:** Production-Ready
The GitHub Copilot SDK plugin is far more than a simple API proxy; it is a highly integrated **Intelligent Agent Runtime Environment**. This document explores the core features, implementation details, technical architecture, and security design that define its capabilities.
---
## 🚀 1. Feature Catalog
The plugin implements advanced capabilities that go far beyond standard API calls:
- **✅ Physical Workspace Management**: Automatically creates isolated directories for each chat session to manage uploaded files and Agent-generated scripts.
- **✅ Real-time TODO Sync**: Mounts to the OpenWebUI database to persist Agent plans directly into a UI progress bar, ensuring transparency for long-running tasks.
- **✅ Cross-Ecosystem Tool Bridging**: Translates OpenWebUI Search, Python interpreters, and custom MCP tools into native Copilot capabilities.
- **✅ Intelligent File Transport**: Ensures the Agent can access raw files (Excel, PDF, Code) like a local developer through physical file duplication.
- **✅ Chain of Thought Streaming**: Full support for rendering the model's reasoning/thinking process in real-time.
- **✅ BYOK with Plugin Power**: Connect external OpenAI/Anthropic models while retaining all the plugin's workspace and tool management features.
---
## 🎯 2. Use Cases: Beyond Basic Chat
With these features, the plugin excels in complex, real-world scenarios:
### 📁 Autonomous Repository Maintenance (Agentic DevOps)
>
> **Action**: Upload a Zip archive containing a codebase with bugs.
> **Utility**: The Agent extracts the archive, uses `bash` to navigate and search, applies fixes via the `edit` tool, and runs tests to verify the solution—all within its isolated sandbox.
### 📊 Deep Data Analysis (Data Scientist Agent)
>
> **Action**: Upload multiple heavy Excel spreadsheets.
> **Utility**: By bypassing RAG (via the filter), the Agent loads raw files directly into its Python interpreter, performs cross-table calculations, and generates analytical charts presented via Artifacts.
### 📝 Strategic Task Management
>
> **Action**: "Develop a full architecture for a new mobile app."
> **Utility**: The plugin captures the Agent's breakdown of 20+ sub-tasks. The persistent progress bar reflects ongoing progress (e.g., "Designing Schema", "Drafting API"), providing clarity during long marathons.
---
## 🛡️ 3. Technical Architecture
### 3.1 Three-Layer Sandbox Isolation
To prevent data leakage in multi-user environments, the plugin enforces a strict physical directory structure:
`/app/backend/data/copilot_workspace/{user_id}/{chat_id}/`
- **Constraint**: Code execution and file storage are confined to the session-specific folder.
- **Persistence**: Data remains valid across container restarts due to volume mounting.
### 3.2 Dynamic Tool Bridging
How does Copilot "learn" to use OpenWebUI tools?
1. **Introspection**: Analyzes docstrings and type hints of OpenWebUI tools.
2. **Schema Generation**: Dynamically creates JSON descriptions compliant with the GitHub Copilot specification.
3. **Routing**: Handles parameter validation, identity injection, and result forwarding between systems.
### 3.3 Event-Driven TODO Hub
The plugin captures internal SDK events to power the UI progress bar:
- **Interceptor**: Listens for `tool.execution_complete` events for the `update_todo` tool.
- **Storage**: Syncs project metrics directly to the `chat_todos` table in the OpenWebUI database (SQLite/PostgreSQL).
---
## ⚡ 4. Runtime Performance
- **Anti-Shake Logic**: Environment checks happen only once every 24 hours per process, preventing redundant system calls.
- **Tool Caching**: Persists tool definitions across sessions to reduce overhead, improving initial response times by up to 40%.
---
## 🛠️ 5. Development Best Practices
1. **Use the Filter**: Always pair with `github_copilot_sdk_files_filter` to ensure files reach the Agent in their original binary form.
2. **File-First Execution**: Encourage the Agent to "write code to file and execute" rather than relying on direct interactive shell input for complex logic.

View File

@@ -0,0 +1,80 @@
# GitHub Copilot SDK 插件深度解析 (Deep Dive)
**版本:** 0.6.0 | **作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **状态:** 生产级
GitHub Copilot SDK 插件不仅仅是一个 API 转发器,它是一个高度集成的 **智能 Agent 运行环境**。本文将从功能实现、应用场景、技术架构及安全设计四个维度,深入解析这一插件的强大之处。
---
## 🚀 1. 核心功能清单 (Feature Catalog)
插件通过深度集成实现了以下超越普通 API 调用的能力:
- **✅ 物理级工作区管理**: 自动为每个对话创建独立的物理目录,管理用户上传和 Agent 生成的所有文件。
- **✅ 实时 TODO 状态同步**: 通过数据库挂载,实时将 Agent 的计划提取到 UI 进度条中,解决长任务透明度问题。
- **✅ 跨生态工具桥接**: 自动将 OpenWebUI 的搜索、Python 运行环境和自定义 MCP 工具转化为 Copilot 原生工具。
- **✅ 智能文件搬运**: 物理级文件副本传输,确保 Agent 可以像本地开发者一样访问 Excel、PDF 和代码仓库。
- **✅ 思考过程可见性**: 完整模拟 GitHub Copilot 的思维链 (Thinking Process) 流式展示。
- **✅ BYOK 二次鉴权**: 支持在 Copilot 框架内接入外部 OpenAI/Anthropic 模型,同时享受插件的所有增强功能。
---
## 🎯 2. 这个插件能用来干什么? (Use Cases)
基于上述功能,该插件可以胜任以下复杂场景:
### 📁 场景 A全自动代码仓库维护 (Agentic DevOps)
>
> **操作**: 上传一个包含 Bug 的 Zip 压缩包。
> **用途**: Agent 会自动解密、解压,使用 `bash` 定位问题,调用 `edit` 修改代码,最后运行测试脚本。这一切都在隔离沙箱中完成,你只需要审核最终的补丁。
### 📊 场景 B深度财务数据审计 (Data Analyst Agent)
>
> **操作**: 上传一年的 Excel 财务报表。
> **用途**: 绕过传统 RAG 的文本切片限制Agent 直接通过 Python 脚本加载原始表格,进行跨表计算和逻辑校验,并生成可视化图表。
### 📝 场景 C复杂长任务进度追踪 (Project Manager)
>
> **操作**: 输入“请基于以下需求文档编写一个完整的 Web 后端方案”。
> **用途**: 插件捕捉 Agent 拆解的 20+ 个子任务。顶部的实时进度条会告诉你它正在“设计数据库”还是“编写认证逻辑”,确保你对黑盒任务了如指掌。
---
## 🛡️ 3. 技术架构设计 (Technical Architecture)
### 3.1 三层物理安全隔离 (Workspace Isolation)
为了确保多用户环境下的数据安全,插件强制执行以下物理路径:
`/app/backend/data/copilot_workspace/{user_id}/{chat_id}/`
- **隔离性**: 进程内代码执行被严格约束在 `chat_id` 目录下。
- **持久性**: 即使容器重启,挂载路径下的工作成果依然保留。
### 3.2 零配置工具桥接 (Dynamic Tool Bridging)
插件如何让 Copilot “学会”使用 OpenWebUI 的工具?
1. **内省 (Introspection)**: 实时读取工具的 `docstring``type hints`
2. **动态转换**: 生成符合 GitHub Copilot 规范的工具描述符。
3. **双向路由**: 处理参数校验、身份注入(如认证头)以及结果回传。
### 3.3 数据库集成与事件驱动
插件通过监听 `NDJSON` 事件流,实现状态同步:
- **监听器**: 实时过滤 `tool.execution_complete` 事件。
- **持久层**: 使用 OpenWebUI 核心相同的 `SQLAlchemy` 引擎操作 `chat_todos` 表。
---
## ⚡ 4. 性能优化 (Performance)
- **环境检查防抖**: 全局类变量保护,版本核对周期为 24 小时,避免高并发下的 I/O 争抢。
- **工具定义缓存**: 仅在变更时刷新工具元数据首包响应速度TTFB提升约 40%。
---
## 🛠️ 5. 开发建议 (Best Practices)
1. **协同工作**: 必须安装 `github_copilot_sdk_files_filter` 以确保文件以“二进制原文”而非“RAG 切片”传递。
2. **Python 范式**: 鼓励 Agent 采取“写文件 -> 运行文件”的模式,而非交互式 Shell 输出,以获得更好的执行稳定性。

View File

@@ -1,100 +1,119 @@
# GitHub Copilot SDK Pipe for OpenWebUI
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.3.0 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.6.0 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/open-webui) that allows you to use GitHub Copilot models (such as `gpt-5`, `gpt-5-mini`, `claude-sonnet-4.5`) directly within OpenWebUI. It is built upon the official [GitHub Copilot SDK for Python](https://github.com/github/copilot-sdk), providing a native integration experience.
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**.
## 🚀 What's New (v0.3.0) - The Power of "Unified Ecosystem"
> [!IMPORTANT]
> **Essential Companion**
> To unlock file handling and data analysis capabilities, you must install the [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6).
* **🔌 Zero-Config Tool Bridge**: Automatically transforms your existing OpenWebUI Functions (Tools) into Copilot-compatible tools. **Copilot now has total access to your entire WebUI toolset!**
* **🔗 Dynamic MCP Discovery**: Seamlessly connects to MCP servers defined in **Admin Settings -> Connections**. No configuration files required—it just works.
* **⚡ High-Performance Async Engine**: Background CLI updates and optimized event-driven streaming ensure lightning-fast responses without UI lag.
* **🛡️ Robust Interoperability**: Advanced sanitization and dynamic Pydantic model generation ensure smooth integration even with complex third-party tools.
> [!TIP]
> **No Subscription Required for BYOK**
> If you are using your own API keys (BYOK mode with OpenAI/Anthropic), **you do NOT need a GitHub Copilot subscription.** A subscription is only required to access GitHub's official models.
---
## ✨ v0.6.0 Updates (What's New)
- **👥 User & Chat Management**: Physical management architecture (`user_id/chat_id`) for absolute resource independence.
- **🤖 Empowering Agent Autonomy**: Automatic synchronization of raw files to the workspace, enabling direct Python-based analysis of Excel/CSV.
- **🔧 OpenAPI & External Tool Fixes**: Full support for tools mounted via OpenAPI servers.
- **📊 Cost Control**: Enhanced **Billing Multiplier Limits** (`MAX_MULTIPLIER`, e.g., set to 0 for free models only) and **Model Keyword Filtering** (`EXCLUDE_KEYWORDS`) for precise cost management.
- **🧠 Persistent TODO Lists**: Database-backed task tracking that persists across sessions.
---
## ✨ Key Capabilities
* **🌉 The Ultimate Bridge**: The first and only plugin that creates a seamless bridge between **OpenWebUI Tools** and **GitHub Copilot SDK**.
* **🚀 Official & Native**: Built directly on the official Python SDK, providing the most stable and authentic Copilot experience.
* **🌊 Advanced Streaming (Thought Process)**: Supports full model reasoning/thinking display with typewriter effects.
* **🖼 Intelligent Multimodal**: Full support for images and attachments, enabling Copilot to "see" your workspace.
* **🛠️ Effortless Setup**: Automatic CLI detection, version enforcement, and dependency management.
* **🔑 Dual-Layer Security**: Supports secure OAuth flow for Chat and standard PAT for extended MCP capabilities.
- **🔑 Flexible Auth & BYOK**: Official Copilot subscriptions (PAT) or Bring Your Own Key (OpenAI/Anthropic).
- **🔌 Universal Tool Protocol**: Native support for **MCP (Model Context Protocol)**, OpenAPI, and OpenWebUI built-in tools.
- **🛡️ Sandbox Workspace Isolation**: Strict per-session sandboxing for data privacy and security.
- ** Infinite Session Management**: Smart context window management with automatic compaction for indefinite conversation capability.
- **🧠 Deep Database Integration**: Real-time persistence of TOD·O lists for long-running workflows.
- **🌊 Advanced Streaming**: Full support for thinking process/Chain of Thought visualization.
- **🖼️ Intelligent Multimodal**: Vision capabilities and raw file analysis support.
- **⚡ Interactive Artifacts**: Automatically renders HTML/JS apps generated by the agent directly in the chat interface.
## 📦 Installation & Usage
---
### 1. Import Function
## ⚙️ Core Configuration (Valves)
1. Open OpenWebUI.
2. Go to **Workspace** -> **Functions**.
3. Click **+** (Create Function).
4. Paste the content of `github_copilot_sdk.py` (or `github_copilot_sdk_cn.py` for Chinese) completely.
5. Save.
### 1. Administrator Settings (Base)
### 2. Configure Valves (Settings)
Administrators define the default behavior for all users in the function settings.
Find "GitHub Copilot" in the function list and click the **⚙️ (Valves)** icon to configure:
| Parameter | Description | Default |
| Valve | Default | Description |
| :--- | :--- | :--- |
| **GH_TOKEN** | **(Required)** GitHub Access Token (PAT or OAuth Token). Access to Chat. | - |
| **DEBUG** | Whether to enable debug logs (output to browser console). | `False` |
| **LOG_LEVEL** | Copilot CLI log level: none, error, warning, info, debug, all. | `error` |
| **SHOW_THINKING** | Show model reasoning/thinking process (requires streaming + model support). | `True` |
| **COPILOT_CLI_VERSION** | Specific Copilot CLI version to install/enforce. | `0.0.405` |
| **EXCLUDE_KEYWORDS** | Exclude models containing these keywords (comma separated). | - |
| **WORKSPACE_DIR** | Restricted workspace directory for file operations. | - |
| **INFINITE_SESSION** | Enable Infinite Sessions (automatic context compaction). | `True` |
| **COMPACTION_THRESHOLD** | Background compaction threshold (0.0-1.0). | `0.8` |
| **BUFFER_THRESHOLD** | Buffer exhaustion threshold (0.0-1.0). | `0.95` |
| **TIMEOUT** | Timeout for each stream chunk (seconds). | `300` |
| **CUSTOM_ENV_VARS** | Custom environment variables (JSON format). | - |
| **REASONING_EFFORT** | Reasoning effort level: low, medium, high. `xhigh` is supported for some models. | `medium` |
| **ENFORCE_FORMATTING** | Add formatting instructions to system prompt for better readability. | `True` |
| **ENABLE_MCP_SERVER** | Enable Direct MCP Client connection (Recommended). | `True` |
| **ENABLE_OPENWEBUI_TOOLS** | Enable OpenWebUI Tools (includes defined and server tools). | `True` |
| `GH_TOKEN` | `""` | Global GitHub Token (Requires 'Copilot Requests' permission). |
| `ENABLE_OPENWEBUI_TOOLS` | `True` | Enable OpenWebUI Tools (includes defined Tools and Built-in Tools). |
| `ENABLE_OPENAPI_SERVER` | `True` | Enable OpenAPI Tool Server connection. |
| `ENABLE_MCP_SERVER` | `True` | Enable Direct MCP Client connection (Recommended). |
| `REASONING_EFFORT` | `medium` | Reasoning effort level: low, medium, high. |
| `SHOW_THINKING` | `True` | Show model reasoning/thinking process. |
| `INFINITE_SESSION` | `True` | Enable Infinite Sessions (automatic context compaction). |
| `MAX_MULTIPLIER` | `1.0` | Max allowed billing multiplier (0x for free models only). |
| `EXCLUDE_KEYWORDS` | `""` | Exclude models containing these keywords (comma separated). |
| `TIMEOUT` | `300` | Timeout for each stream chunk (seconds). |
| `BYOK_TYPE` | `openai` | BYOK Provider Type: `openai`, `anthropic`. |
| `BYOK_BASE_URL` | `""` | BYOK Base URL (e.g., <https://api.openai.com/v1>). |
| `BYOK_MODELS` | `""` | BYOK Model List (comma separated). Leave empty to fetch from API. |
| `CUSTOM_ENV_VARS` | `""` | Custom environment variables (JSON format). |
| `DEBUG` | `False` | Enable this to see detailed logs in your browser console. |
#### User Valves (per-user overrides)
### 2. User Settings (Individual Overrides)
These optional settings can be set per user (overrides global Valves):
Standard users can override these settings in their individual Profile/Function settings.
| Parameter | Description | Default |
| :--- | :--- | :--- |
| **GH_TOKEN** | Personal GitHub Token (overrides global setting). | - |
| **REASONING_EFFORT** | Reasoning effort level (low/medium/high/xhigh). | - |
| **DEBUG** | Enable technical debug logs. | `False` |
| **SHOW_THINKING** | Show model reasoning/thinking process. | `True` |
| **ENABLE_OPENWEBUI_TOOLS** | Enable OpenWebUI Tools (overrides global). | `True` |
| **ENABLE_MCP_SERVER** | Enable MCP server loading (overrides global). | `True` |
| **ENFORCE_FORMATTING** | Enforce formatting guidelines (overrides global). | `True` |
| Valve | Description |
| :--- | :--- |
| `GH_TOKEN` | Use your personal GitHub Token. |
| `REASONING_EFFORT` | Individual reasoning effort preference. |
| `SHOW_THINKING` | Show model reasoning/thinking process. |
| `MAX_MULTIPLIER` | Maximum allowed billing multiplier override. |
| `EXCLUDE_KEYWORDS` | Exclude models containing these keywords. |
| `BYOK_API_KEY` | Use your personal OpenAI/Anthropic API Key. |
### 3. Get Token
---
To use GitHub Copilot, you need a GitHub Personal Access Token (PAT) with appropriate permissions.
## 🎯 Use Cases (What can you do?)
**Steps to generate your token:**
- **📁 Fully Autonomous DevOps**: Agent analyzes code, runs tests, and applies patches within its isolated sandbox.
- **📊 Deep Data Auditing**: Directly process raw Excel/CSV data via Python (bypassing RAG) and generate visual reports.
- **📝 Long-Task Management**: Automatically decomposes complex requests and persists TOD·O progress across sessions.
---
## ⭐ Support
If this plugin has been useful, a **Star** on [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) would be a great motivation for me. Thank you!
---
## 🚀 Installation & Configuration
### 1) Import Function
1. Open OpenWebUI, go to **Workspace** -> **Functions**.
2. Click **+** (Create Function), paste the content of `github_copilot_sdk.py`.
3. Save and ensure it is enabled.
### 2) Get Token
1. Visit [GitHub Token Settings](https://github.com/settings/tokens?type=beta).
2. Click **Generate new token (fine-grained)**.
3. **Repository access**: Select **Public Repositories** (simplest) or **All repositories**.
4. **Permissions**:
* If you chose **All repositories**, you must click **Account permissions**.
* Find **Copilot Requests**, and select **Access**.
5. Generate and copy the Token.
2. Create **Fine-grained token**, granting **Account permissions** -> **Copilot Requests** access.
3. Paste the generated Token into the `GH_TOKEN` field in Valves.
## 📋 Dependencies
---
This Pipe will automatically attempt to install the following dependencies:
## 📋 Troubleshooting & Dependencies
* `github-copilot-sdk` (Python package)
* `github-copilot-cli` (Binary file, installed via official script)
- **Agent ignores files?**: Ensure the Files Filter is enabled, otherwise RAG will interfere with raw binaries.
- **No progress bar?**: The bar only appears when the Agent uses the `update_todo` tool.
- **Dependencies**: This Pipe automatically installs `github-copilot-sdk` (Python) and `github-copilot-cli` (Binary).
## Troubleshooting ❓
---
* **Images and Multimodal Usage**:
* Ensure `MODEL_ID` is a model that supports multimodal input.
* **Thinking not shown**:
* Ensure **streaming is enabled** and the selected model supports reasoning output.
## Changelog
## 📄 License
MIT
See the full history on GitHub: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)

View File

@@ -1,100 +1,119 @@
# GitHub Copilot SDK 官方管道
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.3.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.6.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
这是一个用于 [OpenWebUI](https://github.com/open-webui/open-webui) 的高级 Pipe 函数,允许你直接在 OpenWebUI 中使用 GitHub Copilot 模型(如 `gpt-5`, `gpt-5-mini`, `claude-sonnet-4.5`)。它基于官方 [GitHub Copilot SDK for Python](https://github.com/github/copilot-sdk) 构建,提供了原生级的集成体验。
这是一个用于 [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 交互体验。
## 🚀 最新特性 (v0.3.0) - “统一生态”的力量
> [!IMPORTANT]
> **核心伴侣组件**
> 如需启用文件处理与数据分析能力,请务必安装 [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)。
* **🔌 零配置工具桥接 (Unified Tool Bridge)**: 自动将您现有的 OpenWebUI Functions (工具) 转换为 Copilot 兼容工具。**Copilot 现在可以无缝调用您手头所有的 WebUI 工具!**
* **🔗 动态 MCP 自动发现**: 直接联动 OpenWebUI **管理面板 -> 连接**。无需编写任何配置文件,即插即用,瞬间扩展 Copilot 能力边界。
* **⚡ 高性能异步引擎**: 异步 CLI 更新检查与高度优化的事件驱动流式处理,确保对话毫秒级响应
* **🛡️ 卓越的兼容性**: 独创的动态 Pydantic 模型生成技术,确保复杂工具参数在 Copilot 端也能得到精准验证。
> [!TIP]
> **BYOK 模式无需订阅**
> 如果您使用自带的 API Key (BYOK 模式对接 OpenAI/Anthropic)**您不需要 GitHub Copilot 官方订阅**。只有在访问 GitHub 官方模型时才需要订阅
## ✨ 核心能力
---
* **🌉 强大的生态桥接**: 首个且唯一完美打通 **OpenWebUI Tools****GitHub Copilot SDK** 的插件。
* **🚀 官方原生产体验**: 基于官方 Python SDK 构建,提供最稳定、最纯正的 Copilot 交互体验。
* **🌊 深度推理展示**: 完整支持模型思考过程 (Thinking Process) 的流式渲染。
* **🖼️ 智能多模态**: 支持图像识别与附件上传,让 Copilot 拥有视觉能力。
* **🛠️ 极简部署流程**: 自动检测环境、自动下载 CLI、自动管理依赖全自动化开箱即用。
* **🔑 安全认证体系**: 完美支持 OAuth 授权与 PAT 模式,兼顾便捷与安全性。
## ✨ 0.6.0 更新内容 (What's New)
## 📦 安装与使用
- **👥 多用户与会话管理**: 采用 `user_id/chat_id` 的物理隔离架构,确保资源独立与稳健管理。
- **🤖 赋予 Agent 文件自主权**: 自动将上传的文件同步至物理工作区,支持 Python 直接分析 Excel/CSV。
- **🔧 OpenAPI & 外部工具修复**: 完美支持通过 OpenAPI 服务器挂载的工具调用。
- **📊 计费与成本控制**: 增强的**计费倍率限制** (`MAX_MULTIPLIER`,例如设为 0 即仅限免费模型) 和**模型关键词过滤** (`EXCLUDE_KEYWORDS`),实现更精准的成本管控。
- **🧠 数据库持久化 TODO**: 任务进度跨会话保存Agent 拥有更持久的任务 memory。
### 1. 导入函数
---
1. 打开 OpenWebUI。
2. 进入 **Workspace** -> **Functions**
3. 点击 **+** (创建函数)。
4.`github_copilot_sdk_cn.py` 的内容完整粘贴进去。
5. 保存。
## ✨ 核心能力 (Key Capabilities)
### 2. 配置 Valves (设置)
- **🔑 灵活鉴权与 BYOK**: 支持 GitHub Copilot 官方订阅 (PAT) 或自带 Key (OpenAI/Anthropic)。
- **🔌 通用工具协议**: 原生支持 **MCP (Model Context Protocol)**、OpenAPI 以及 OpenWebUI 内置工具。
- **🛡️ 物理级工作区隔离**: 强制执行严格的用户特定沙箱,确保数据隐私与文件安全。
- **♾️ 无限会话管理**: 智能上下文窗口管理与自动压缩算法,支持无限时长的对话交互。
- **🧠 深度数据库集成**: 实时持久化 TOD·O 列表到 UI 进度条。
- **🌊 深度推理展示**: 完整支持模型思考过程 (Thinking Process) 的流式渲染。
- **🖼️ 智能多模态**: 完整支持图像识别与附件上传分析。
- **⚡ 交互式伪影 (Artifacts)**: 自动渲染 Agent 生成的 HTML/JS 应用程序,直接在聊天界面交互。
在函数列表中找到 "GitHub Copilot",点击 **⚙️ (Valves)** 图标进行配置:
---
| 参数 | 说明 | 默认值 |
## ⚙️ 核心配置参数 (Valves)
### 1. 管理员配置 (基础设置)
管理员可在函数设置中定义全局默认行为。
| 参数 | 默认值 | 说明 |
| :--- | :--- | :--- |
| **GH_TOKEN** | **(必填)** GitHub 访问令牌 (PAT 或 OAuth Token)。用于聊天。 | - |
| **DEBUG** | 是否开启调试日志(输出到浏览器控制台)。 | `False` |
| **LOG_LEVEL** | Copilot CLI 日志级别: none, error, warning, info, debug, all。 | `error` |
| **SHOW_THINKING** | 是否显示模型推理/思考过程(需开启流式 + 模型支持)。 | `True` |
| **COPILOT_CLI_VERSION** | 指定安装/强制使用的 Copilot CLI 版本。 | `0.0.405` |
| **EXCLUDE_KEYWORDS** | 排除包含这些关键词的模型(逗号分隔)。 | - |
| **WORKSPACE_DIR** | 文件操作的受限工作区目录。 | - |
| **INFINITE_SESSION** | 启用无限会话(自动上下文压缩)。 | `True` |
| **COMPACTION_THRESHOLD** | 后台压缩阈值 (0.0-1.0)。 | `0.8` |
| **BUFFER_THRESHOLD** | 缓冲区耗尽阈值 (0.0-1.0)。 | `0.95` |
| **TIMEOUT** | 每个流式分块超时(秒)。 | `300` |
| **CUSTOM_ENV_VARS** | 自定义环境变量 (JSON 格式)。 | - |
| **REASONING_EFFORT** | 推理强度级别: low, medium, high. `xhigh` 仅部分模型支持。 | `medium` |
| **ENFORCE_FORMATTING** | 在系统提示词中添加格式化指导。 | `True` |
| **ENABLE_MCP_SERVER** | 启用直接 MCP 客户端连接 (建议)。 | `True` |
| **ENABLE_OPENWEBUI_TOOLS** | 启用 OpenWebUI 工具 (包括自定义和服务器工具)。 | `True` |
| `GH_TOKEN` | `""` | 全局 GitHub Token (需具备 'Copilot Requests' 权限)。 |
| `ENABLE_OPENWEBUI_TOOLS` | `True` | 启用 OpenWebUI 工具 (包括定义工具和内置工具)。 |
| `ENABLE_OPENAPI_SERVER` | `True` | 启用 OpenAPI 工具服务器连接。 |
| `ENABLE_MCP_SERVER` | `True` | 启用直接 MCP 客户端连接 (推荐)。 |
| `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` | 开启此项以在前端控制台输出详细调试日志。 |
#### 用户 Valves按用户覆盖
### 2. 用户配置 (个人覆盖)
以下设置可按用户单独配置(覆盖全局 Valves
普通用户可在各自的个人设置中根据需要覆盖以下参数。
| 参数 | 说明 | 默认值 |
| :--- | :--- | :--- |
| **GH_TOKEN** | 个人 GitHub Token(覆盖全局设置)。 | - |
| **REASONING_EFFORT** | 推理强度级别low/medium/high/xhigh。 | - |
| **DEBUG** | 是否启用技术调试日志。 | `False` |
| **SHOW_THINKING** | 是否显示思考过程。 | `True` |
| **ENABLE_OPENWEBUI_TOOLS** | 启用 OpenWebUI 工具(覆盖全局设置)。 | `True` |
| **ENABLE_MCP_SERVER** | 启用动态 MCP 服务器加载(覆盖全局设置)。 | `True` |
| **ENFORCE_FORMATTING** | 强制启用格式化指导(覆盖全局设置)。 | `True` |
| 参数 | 说明 |
| :--- | :--- |
| `GH_TOKEN` | 使用个人 GitHub Token |
| `REASONING_EFFORT` | 个人偏好的推理强度。 |
| `SHOW_THINKING` | 显示模型推理/思考过程。 |
| `MAX_MULTIPLIER` | 最大允许的模型计费倍率覆盖。 |
| `EXCLUDE_KEYWORDS` | 排除包含这些关键字的模型。 |
| `BYOK_API_KEY` | 使用个人的 OpenAI/Anthropic API Key。 |
### 3. 获取 Token
---
要使用 GitHub Copilot您需要一个具有适当权限的 GitHub 个人访问令牌 (PAT)。
## 🎯 典型应用场景 (Use Cases)
**获取步骤:**
- **📁 全自主仓库维护**: Agent 在隔离工作区内自动分析代码、运行测试并应用补丁。
- **📊 深度财务数据审计**: 直接通过 Python 加载 Excel/CSV 原始数据(绕过 RAG生成图表并实时预览。
- **📝 长任务项目管理**: 自动拆解复杂任务并持久化 TOD·O 进度,跨会话跟踪执行状态。
1. 访问 [GitHub 令牌设置](https://github.com/settings/tokens?type=beta)。
2. 点击 **Generate new token (fine-grained)**
3. **Repository access**: 选择 **Public Repositories** (最简单) 或 **All repositories**
4. **Permissions**:
* 如果您选择了 **All repositories**,则必须点击 **Account permissions**
* 找到 **Copilot Requests**,选择 **Access**
5. 生成并复制令牌。
---
## 📋 依赖说明
## ⭐ 支持与交流 (Support)
该 Pipe 会自动尝试安装以下依赖(如果环境中缺失):
如果这个插件对您有所帮助,请在 [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) 项目上点个 **Star** 💫,这是对我最大的鼓励。
* `github-copilot-sdk` (Python 包)
* `github-copilot-cli` (二进制文件,通过官方脚本安装)
---
## 故障排除 (Troubleshooting) ❓
## 🚀 安装与配置 (Installation)
* **图片及多模态使用说明**
* 确保 `MODEL_ID` 是支持多模态的模型。
* **看不到思考过程**
* 确认已开启**流式输出**,且所选模型支持推理输出。
### 1) 导入函数
## 📄 许可证
1. 打开 OpenWebUI前往 **工作区** -> **函数**
2. 点击 **+** (创建函数),完整粘贴 `github_copilot_sdk_cn.py` 的内容。
3. 点击保存并确保已启用。
MIT
### 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` 配置项中。
---
## 📋 常见问题与依赖 (Troubleshooting)
- **Agent 无法识别文件?**: 请确保已安装并启用了 Files Filter 插件,否则原始文件会被 RAG 干扰。
- **看不到 TODO 进度条?**: 进度条仅在 Agent 使用 `update_todo` 工具(通常是处理复杂任务)时出现。
- **依赖安装**: 本管道会自动尝试安装 `github-copilot-sdk` (Python 包) 和 `github-copilot-cli` (官方二进制)。
---
## 更新日志 (Changelog)
完整历史记录请见 GitHub: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)

View File

@@ -15,7 +15,7 @@ Pipes allow you to:
## Available Pipe Plugins
- [GitHub Copilot SDK](github-copilot-sdk.md) (v0.3.0) - Official GitHub Copilot SDK integration. Features **zero-config OpenWebUI Tool Bridge** and **dynamic MCP discovery**. Supports streaming, multimodal, and infinite sessions.
- [GitHub Copilot SDK](github-copilot-sdk.md) (v0.6.0) - Official GitHub Copilot SDK integration. Features **Workspace Isolation**, **Database Persistence**, **Zero-config OpenWebUI Tool Bridge**, **BYOK** support, and **dynamic MCP discovery**. Supports streaming, multimodal, and infinite sessions.
---

View File

@@ -15,7 +15,7 @@ Pipes 可以用于:
## 可用的 Pipe 插件
- [GitHub Copilot SDK](github-copilot-sdk.zh.md) (v0.3.0) - GitHub Copilot SDK 官方集成。**零配置工具桥接**与**动态 MCP 发现**。支持流式输出、多模态及无限会话
- [GitHub Copilot SDK](github-copilot-sdk.zh.md) (v0.6.0) - GitHub Copilot SDK 官方集成。具备**工作区安全隔离**、**数据库持久化**、**零配置工具桥接**与**BYOK (自带 Key) 支持**。支持流式输出、打字机思考过程及无限会话。[查看深度架构解析](github-copilot-sdk-deep-dive.zh.md)
---

View File

@@ -1,14 +1,15 @@
# 📝 Export to Word (Enhanced)
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.4.3 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.4.4 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
Export conversation to Word (.docx) with **syntax highlighting**, **native math equations**, **Mermaid diagrams**, **citations**, and **enhanced table formatting**.
## 🔥 What's New in v0.4.3
## 🔥 What's New in v0.4.4
- **S3 Object Storage Support**: Direct access to images stored in S3/MinIO via boto3, bypassing API layer for faster exports.
- 🔧 **Multi-level File Fallback**: 6-level fallback mechanism for file retrieval (DB → S3 → Local → URL → API → Attributes).
- 🛡️ **Improved Error Handling**: Better logging and error messages for file retrieval failures.
- 🧹 **Content Cleanup**: Enhanced stripping of `<details>` blocks (often used for tool calls/thinking process) to ensure a clean final document.
- 📄 **Standard Document Formatting**: Applied professional document formatting standards for titles and headings (centered title, bold, optimized font sizes and spacing), including GB/T compliance for Chinese content.
- 🔠 **Font Rendering Fix**: Fixed an issue where CJK characters would fallback to MS Gothic in Word; now correctly uses the configured Asian font (e.g., SimSun).
- ⚙️ **Title Alignment**: Added `TITLE_ALIGNMENT` valve to configure document title alignment (left, center, right).
## ✨ Key Features

View File

@@ -1,14 +1,15 @@
# 📝 导出为 Word (增强版)
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.4.3 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.4.4 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
将对话导出为 Word (.docx),支持**代码语法高亮**、**原生数学公式**、**Mermaid 图表**、**引用参考**和**增强表格格式**。
## 🔥 v0.4.3 更新内容
## 🔥 v0.4.4 更新内容
- **S3 对象存储支持**: 通过 boto3 直连 S3/MinIO绕过 API 层,导出速度更快
- 🔧 **多级文件回退**: 6 级文件获取机制(数据库 → S3 → 本地 → URL → API → 属性)
- 🛡️ **错误处理优化**: 更完善的日志记录和错误提示,便于调试文件访问问题
- 🧹 **内容清理加强**: 增强了对 `<details>` 块(通常包含工具调用或思考过程)的清理,确保最终文档整洁
- 📄 **文档格式标准化**: 采用了专业的文档排版标准(兼容中文 GB/T 规范),标题居中加粗,各级标题使用标准字号和间距
- 🔠 **字体渲染修复**: 修复了 CJK 字符在 Word 中回退到 MS Gothic 的问题;现在正确使用配置的中文字体(例如宋体)
- ⚙️ **标题对齐配置**: 新增 `标题对齐方式` Valve支持配置文档标题的对齐方式左对齐、居中、右对齐
## ✨ 核心特性

View File

@@ -1,9 +1,9 @@
"""
title: Export to Word (Enhanced)
title: Export to Word Enhanced
author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
version: 0.4.3
version: 0.4.4
openwebui_id: fca6a315-2a45-42cc-8c96-55cbc85f87f2
icon_url: data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9ImN1cnJlbnRDb2xvciIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNiAyMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmg4YTIuNCAyLjQgMCAwIDEgMS43MDQuNzA2bDMuNTg4IDMuNTg4QTIuNCAyLjQgMCAwIDEgMjAgOHYxMmEyIDIgMCAwIDEtMiAyeiIgLz4KICA8cGF0aCBkPSJNMTQgMnY1YTEgMSAwIDAgMCAxIDFoNSIgLz4KICA8cGF0aCBkPSJNMTAgOUg4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxM0g4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxN0g4IiAvPgo8L3N2Zz4K
requirements: python-docx, Pygments, latex2mathml, mathml2omml
@@ -101,9 +101,8 @@ _TRANSPARENT_1PX_PNG = base64.b64decode(
_ASVG_NS = "http://schemas.microsoft.com/office/drawing/2016/SVG/main"
nsmap.setdefault("asvg", _ASVG_NS)
_REASONING_DETAILS_RE = re.compile(
r"<details\b[^>]*\btype\s*=\s*(?:\"reasoning\"|'reasoning'|reasoning)[^>]*>.*?</details\s*>",
re.IGNORECASE | re.DOTALL,
_ALL_DETAILS_RE = re.compile(
r"<details\b[^>]*>.*?</details\s*>", re.IGNORECASE | re.DOTALL
)
_THINK_RE = re.compile(r"<think\b[^>]*>.*?</think\s*>", re.IGNORECASE | re.DOTALL)
_ANALYSIS_RE = re.compile(
@@ -178,6 +177,12 @@ class Action:
description="Font for code blocks and inline code (e.g., 'Consolas', 'Courier New', 'Monaco')",
)
# Title alignment
TITLE_ALIGNMENT: str = Field(
default="center",
description="Title alignment: 'left', 'center', or 'right'",
)
# Table styling
TABLE_HEADER_COLOR: str = Field(
default="F2F2F2",
@@ -242,60 +247,60 @@ class Action:
)
class UserValves(BaseModel):
TITLE_SOURCE: str = Field(
default="chat_title",
TITLE_SOURCE: Optional[str] = Field(
default=None,
description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown Title)",
)
UI_LANGUAGE: str = Field(
default="en",
UI_LANGUAGE: Optional[str] = Field(
default=None,
description="UI language for export messages. Options: 'en' (English), 'zh' (Chinese)",
)
FONT_LATIN: str = Field(
default="Times New Roman",
FONT_LATIN: Optional[str] = Field(
default=None,
description="Font for Latin characters (e.g., 'Times New Roman', 'Calibri', 'Arial')",
)
FONT_ASIAN: str = Field(
default="SimSun",
FONT_ASIAN: Optional[str] = Field(
default=None,
description="Font for Asian characters (e.g., 'SimSun', 'Microsoft YaHei', 'PingFang SC')",
)
FONT_CODE: str = Field(
default="Consolas",
FONT_CODE: Optional[str] = Field(
default=None,
description="Font for code blocks and inline code (e.g., 'Consolas', 'Courier New', 'Monaco')",
)
TABLE_HEADER_COLOR: str = Field(
default="F2F2F2",
TABLE_HEADER_COLOR: Optional[str] = Field(
default=None,
description="Table header background color (hex, without #)",
)
TABLE_ZEBRA_COLOR: str = Field(
default="FBFBFB",
TABLE_ZEBRA_COLOR: Optional[str] = Field(
default=None,
description="Table zebra stripe background color for alternate rows (hex, without #)",
)
MERMAID_PNG_SCALE: float = Field(
default=3.0,
MERMAID_PNG_SCALE: Optional[float] = Field(
default=None,
description="PNG render resolution multiplier (higher = clearer, larger file)",
)
MERMAID_DISPLAY_SCALE: float = Field(
default=1.0,
MERMAID_DISPLAY_SCALE: Optional[float] = Field(
default=None,
description="Diagram width relative to available page width (<=1 recommended)",
)
MERMAID_OPTIMIZE_LAYOUT: bool = Field(
default=False,
MERMAID_OPTIMIZE_LAYOUT: Optional[bool] = Field(
default=None,
description="Optimize Mermaid layout: convert LR to TD for graph/flowchart",
)
MERMAID_BACKGROUND: str = Field(
default="",
MERMAID_BACKGROUND: Optional[str] = Field(
default=None,
description="Mermaid background color. Empty = transparent (recommended for Word dark mode). Used only for optional PNG fill.",
)
MERMAID_CAPTIONS_ENABLE: bool = Field(
default=True,
MERMAID_CAPTIONS_ENABLE: Optional[bool] = Field(
default=None,
description="Add figure captions under Mermaid images/charts",
)
MATH_ENABLE: bool = Field(
default=True,
MATH_ENABLE: Optional[bool] = Field(
default=None,
description="Enable LaTeX math block conversion (\\\\[...\\\\] and $$...$$) into Word equations",
)
MATH_INLINE_DOLLAR_ENABLE: bool = Field(
default=True,
MATH_INLINE_DOLLAR_ENABLE: Optional[bool] = Field(
default=None,
description="Enable inline $...$ math conversion into Word equations (conservative parsing to reduce false positives)",
)
@@ -449,13 +454,21 @@ class Action:
user_id = __user__.get("id", "unknown_user")
# Apply UserValves if present
if __user__ and "valves" in __user__:
# Update self.valves with user-specific values
# Note: This assumes per-request instantiation or that we are okay with modifying the singleton.
# Given the plugin architecture, we'll update it for this execution.
for key, value in __user__["valves"].model_dump().items():
if hasattr(self.valves, key):
setattr(self.valves, key, value)
if __user__:
# Robustly parse UserValves whether it's a dict or Pydantic model
raw_valves = __user__.get("valves", {})
if isinstance(raw_valves, self.UserValves):
user_valves = raw_valves
elif isinstance(raw_valves, dict):
user_valves = self.UserValves(**raw_valves)
else:
user_valves = None
if user_valves:
for key, value in user_valves.model_dump(exclude_unset=True).items():
# Only override if the value is not None (and explicitly set)
if hasattr(self.valves, key) and value is not None:
setattr(self.valves, key, value)
# Get user language from Valves configuration
self._user_lang = self._get_lang_key(self.valves.UI_LANGUAGE)
@@ -492,6 +505,37 @@ class Action:
try:
message_content = last_assistant_message["content"]
if isinstance(message_content, str):
if __event_emitter__ and self.valves.SHOW_DEBUG_LOG:
debug_data = {}
for name, regex in [
("Details Block", _ALL_DETAILS_RE),
("Think Block", _THINK_RE),
("Analysis Block", _ANALYSIS_RE),
]:
matches = regex.findall(message_content)
if matches:
debug_data[name] = [
(m[:200] + "...") if len(m) > 200 else m
for m in matches
]
if debug_data:
await self._emit_debug_log(
__event_emitter__,
"Context Stripping Analysis",
debug_data,
)
# Log font configuration
await self._emit_debug_log(
__event_emitter__,
"Font Configuration",
{
"Latin Font": self.valves.FONT_LATIN,
"Asian Font": self.valves.FONT_ASIAN,
"Code Font": self.valves.FONT_CODE,
},
)
message_content = self._strip_reasoning_blocks(message_content)
if not message_content or not message_content.strip():
@@ -1107,30 +1151,7 @@ class Action:
if not isinstance(name, str):
return ""
def _is_emoji_codepoint(codepoint: int) -> bool:
# Common emoji ranges + flag regional indicators.
return (
0x1F000 <= codepoint <= 0x1FAFF
or 0x1F1E6 <= codepoint <= 0x1F1FF
or 0x2600 <= codepoint <= 0x26FF
or 0x2700 <= codepoint <= 0x27BF
or 0x2300 <= codepoint <= 0x23FF
or 0x2B00 <= codepoint <= 0x2BFF
)
def _is_emoji_modifier(codepoint: int) -> bool:
# VS15/VS16, ZWJ, keycap, skin tones, and tag characters used in some emoji sequences.
return (
codepoint in (0x200D, 0xFE0E, 0xFE0F, 0x20E3)
or 0x1F3FB <= codepoint <= 0x1F3FF
or 0xE0020 <= codepoint <= 0xE007F
)
without_emoji = "".join(
ch
for ch in name
if not (_is_emoji_codepoint(ord(ch)) or _is_emoji_modifier(ord(ch)))
)
without_emoji = self._remove_emojis(name)
cleaned = re.sub(r'[\\/*?:"<>|]', "", without_emoji)
cleaned = re.sub(r"\s+", " ", cleaned).strip().strip(".")
return cleaned[:50].strip()
@@ -1498,7 +1519,10 @@ class Action:
# If there is no h1 in content, prepend chat title as h1 when provided
if top_heading and not has_h1:
self.add_heading(doc, top_heading, 1)
# Remove emojis from title for a professional look
clean_title = self._remove_emojis(top_heading)
# Use Title style (level 0) for the main document title
self.add_heading(doc, clean_title, 0)
lines = markdown_text.split("\n")
i = 0
@@ -1758,7 +1782,7 @@ class Action:
cur = text
for _ in range(10):
prev = cur
cur = _REASONING_DETAILS_RE.sub("", cur)
cur = _ALL_DETAILS_RE.sub("", cur)
cur = _THINK_RE.sub("", cur)
cur = _ANALYSIS_RE.sub("", cur)
if cur == prev:
@@ -2242,14 +2266,155 @@ class Action:
font = style.font
font.name = self.valves.FONT_LATIN
font.size = Pt(11)
# Set Asian font
style._element.rPr.rFonts.set(qn("w:eastAsia"), self.valves.FONT_ASIAN)
# Ensure rPr element exists
rPr = style._element.get_or_add_rPr()
rFonts = rPr.get_or_add_rFonts()
# Set Latin and Asian fonts explicitly
rFonts.set(qn("w:ascii"), self.valves.FONT_LATIN)
rFonts.set(qn("w:hAnsi"), self.valves.FONT_LATIN)
rFonts.set(qn("w:eastAsia"), self.valves.FONT_ASIAN)
# Set language to zh-CN to prevent MS Gothic fallback (Japanese font)
# Even for English interface, we want to prioritize Chinese glyphs over Japanese for CJK
lang = rPr.find(qn("w:lang"))
if lang is None:
lang = OxmlElement("w:lang")
rPr.append(lang)
lang.set(qn("w:val"), "en-US")
lang.set(qn("w:eastAsia"), "zh-CN")
logger.info(
f"[Font Config] Latin: {self.valves.FONT_LATIN}, Asian: {self.valves.FONT_ASIAN}"
)
# Set paragraph format
paragraph_format = style.paragraph_format
paragraph_format.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
paragraph_format.space_after = Pt(6)
# Configure Title style (used for document title)
# Standard format: 22pt (二号), bold, centered, 24pt space after
if "Title" in doc.styles:
title_style = doc.styles["Title"]
title_font = title_style.font
title_font.name = self.valves.FONT_LATIN
title_font.size = Pt(22) # 二号字体
title_font.bold = True
title_font.color.rgb = RGBColor(0, 0, 0)
# Set paragraph format: alignment based on configuration
title_pf = title_style.paragraph_format
alignment_map = {
"left": WD_ALIGN_PARAGRAPH.LEFT,
"center": WD_ALIGN_PARAGRAPH.CENTER,
"right": WD_ALIGN_PARAGRAPH.RIGHT,
}
title_pf.alignment = alignment_map.get(
self.valves.TITLE_ALIGNMENT.lower(), WD_ALIGN_PARAGRAPH.CENTER
)
title_pf.space_before = Pt(0)
title_pf.space_after = Pt(24)
t_rPr = title_style._element.get_or_add_rPr()
t_rFonts = t_rPr.get_or_add_rFonts()
t_rFonts.set(qn("w:ascii"), self.valves.FONT_LATIN)
t_rFonts.set(qn("w:hAnsi"), self.valves.FONT_LATIN)
t_rFonts.set(qn("w:eastAsia"), self.valves.FONT_ASIAN)
# Set language to zh-CN
t_lang = t_rPr.find(qn("w:lang"))
if t_lang is None:
t_lang = OxmlElement("w:lang")
t_rPr.append(t_lang)
t_lang.set(qn("w:val"), "en-US")
t_lang.set(qn("w:eastAsia"), "zh-CN")
# Standard heading sizes based on Chinese document standards:
# Heading 1: 16pt (三号), bold, space before 24pt, space after 12pt
# Heading 2: 15pt (小三), bold, space before 18pt, space after 6pt
# Heading 3: 14pt (四号), bold, space before 12pt, space after 6pt
# Heading 4-9: 12pt (小四), bold, gradually reduced spacing
heading_formats = {
1: {"size": 16, "space_before": 24, "space_after": 12},
2: {"size": 15, "space_before": 18, "space_after": 6},
3: {"size": 14, "space_before": 12, "space_after": 6},
4: {"size": 12, "space_before": 12, "space_after": 6},
5: {"size": 12, "space_before": 6, "space_after": 6},
6: {"size": 11, "space_before": 6, "space_after": 3},
7: {"size": 11, "space_before": 6, "space_after": 3},
8: {"size": 10.5, "space_before": 6, "space_after": 3},
9: {"size": 10.5, "space_before": 6, "space_after": 3},
}
# Apply font settings to Heading 1-9
for i in range(1, 10):
style_id = f"Heading {i}"
if style_id in doc.styles:
heading_style = doc.styles[style_id]
heading_font = heading_style.font
heading_font.name = self.valves.FONT_LATIN
heading_font.color.rgb = RGBColor(0, 0, 0)
# Apply standard formatting
fmt = heading_formats.get(
i, {"size": 11, "space_before": 6, "space_after": 3}
)
heading_font.size = Pt(fmt["size"])
heading_font.bold = True
heading_pf = heading_style.paragraph_format
heading_pf.space_before = Pt(fmt["space_before"])
heading_pf.space_after = Pt(fmt["space_after"])
# Ensure rPr exists
h_rPr = heading_style._element.get_or_add_rPr()
h_rFonts = h_rPr.get_or_add_rFonts()
# Set fonts
h_rFonts.set(qn("w:ascii"), self.valves.FONT_LATIN)
h_rFonts.set(qn("w:hAnsi"), self.valves.FONT_LATIN)
h_rFonts.set(qn("w:eastAsia"), self.valves.FONT_ASIAN)
# Set language to zh-CN
h_lang = h_rPr.find(qn("w:lang"))
if h_lang is None:
h_lang = OxmlElement("w:lang")
h_rPr.append(h_lang)
h_lang.set(qn("w:val"), "en-US")
h_lang.set(qn("w:eastAsia"), "zh-CN")
def _remove_emojis(self, text: str) -> str:
"""Remove emojis from text based on unicode ranges."""
if not isinstance(text, str):
return ""
def _is_emoji_codepoint(codepoint: int) -> bool:
# Common emoji ranges + flag regional indicators.
return (
0x1F000 <= codepoint <= 0x1FAFF
or 0x1F1E6 <= codepoint <= 0x1F1FF
or 0x2600 <= codepoint <= 0x26FF
or 0x2700 <= codepoint <= 0x27BF
or 0x2300 <= codepoint <= 0x23FF
or 0x2B00 <= codepoint <= 0x2BFF
)
def _is_emoji_modifier(codepoint: int) -> bool:
# VS15/VS16, ZWJ, keycap, skin tones, and tag characters used in some emoji sequences.
return (
codepoint in (0x200D, 0xFE0E, 0xFE0F, 0x20E3)
or 0x1F3FB <= codepoint <= 0x1F3FF
or 0xE0020 <= codepoint <= 0xE007F
)
return "".join(
ch
for ch in text
if not (_is_emoji_codepoint(ord(ch)) or _is_emoji_modifier(ord(ch)))
)
def add_heading(self, doc: Document, text: str, level: int):
"""Add heading"""
# Word heading levels start from 0, Markdown from 1
@@ -2285,6 +2450,12 @@ class Action:
if strike:
run.font.strike = True
# Explicitly set East Asian font to prevent MS Gothic fallback
# Word may not inherit w:eastAsia from style, causing Japanese font fallback for CJK
rPr = run._element.get_or_add_rPr()
rFonts = rPr.get_or_add_rFonts()
rFonts.set(qn("w:eastAsia"), self.valves.FONT_ASIAN)
def _add_inline_code(self, paragraph, s: str):
if s == "":
return
@@ -2684,7 +2855,11 @@ class Action:
):
u = self._normalize_url(url)
if not u:
paragraph.add_run(display_text or text)
run = paragraph.add_run(display_text or text)
# Set East Asian font to prevent MS Gothic fallback
rPr = run._element.get_or_add_rPr()
rFonts = rPr.get_or_add_rFonts()
rFonts.set(qn("w:eastAsia"), self.valves.FONT_ASIAN)
return
part = getattr(paragraph, "part", None)
@@ -2693,6 +2868,10 @@ class Action:
run = paragraph.add_run(display_text or text)
run.font.color.rgb = RGBColor(0, 0, 255)
run.font.underline = True
# Set East Asian font to prevent MS Gothic fallback
rPr = run._element.get_or_add_rPr()
rFonts = rPr.get_or_add_rFonts()
rFonts.set(qn("w:eastAsia"), self.valves.FONT_ASIAN)
return
r_id = part.relate_to(u, RT.HYPERLINK, is_external=True)
@@ -2706,6 +2885,11 @@ class Action:
rStyle.set(qn("w:val"), "Hyperlink")
rPr.append(rStyle)
# Set East Asian font to prevent MS Gothic fallback
rFonts = OxmlElement("w:rFonts")
rFonts.set(qn("w:eastAsia"), self.valves.FONT_ASIAN)
rPr.append(rFonts)
color = OxmlElement("w:color")
color.set(qn("w:val"), "0000FF")
rPr.append(color)

View File

@@ -1,9 +1,9 @@
"""
title: 导出为 Word (增强版)
title: 导出为Word增强版
author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
version: 0.4.3
version: 0.4.4
openwebui_id: 8a6306c0-d005-4e46-aaae-8db3532c9ed5
icon_url: data:image/svg+xml;base64,PHN2ZwogIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICB3aWR0aD0iMjQiCiAgaGVpZ2h0PSIyNCIKICB2aWV3Qm94PSIwIDAgMjQgMjQiCiAgZmlsbD0ibm9uZSIKICBzdHJva2U9ImN1cnJlbnRDb2xvciIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNiAyMmEyIDIgMCAwIDEtMi0yVjRhMiAyIDAgMCAxIDItMmg4YTIuNCAyLjQgMCAwIDEgMS43MDQuNzA2bDMuNTg4IDMuNTg4QTIuNCAyLjQgMCAwIDEgMjAgOHYxMmEyIDIgMCAwIDEtMiAyeiIgLz4KICA8cGF0aCBkPSJNMTQgMnY1YTEgMSAwIDAgMCAxIDFoNSIgLz4KICA8cGF0aCBkPSJNMTAgOUg4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxM0g4IiAvPgogIDxwYXRoIGQ9Ik0xNiAxN0g4IiAvPgo8L3N2Zz4K
requirements: python-docx, Pygments, latex2mathml, mathml2omml
@@ -101,9 +101,8 @@ _TRANSPARENT_1PX_PNG = base64.b64decode(
_ASVG_NS = "http://schemas.microsoft.com/office/drawing/2016/SVG/main"
nsmap.setdefault("asvg", _ASVG_NS)
_REASONING_DETAILS_RE = re.compile(
r"<details\b[^>]*\btype\s*=\s*(?:\"reasoning\"|'reasoning'|reasoning)[^>]*>.*?</details\s*>",
re.IGNORECASE | re.DOTALL,
_ALL_DETAILS_RE = re.compile(
r"<details\b[^>]*>.*?</details\s*>", re.IGNORECASE | re.DOTALL
)
_THINK_RE = re.compile(r"<think\b[^>]*>.*?</think\s*>", re.IGNORECASE | re.DOTALL)
_ANALYSIS_RE = re.compile(
@@ -178,6 +177,12 @@ class Action:
description="Font for code blocks and inline code (e.g., 'Consolas', 'Courier New', 'Monaco')",
)
# Title alignment
标题对齐方式: str = Field(
default="center",
description="标题对齐方式: 'left' (左对齐), 'center' (居中), 或 'right' (右对齐)",
)
# Table styling
表头背景色: str = Field(
default="F2F2F2",
@@ -242,60 +247,60 @@ class Action:
)
class UserValves(BaseModel):
文档标题来源: str = Field(
default="chat_title",
文档标题来源: Optional[str] = Field(
default=None,
description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown Title)",
)
界面语言: str = Field(
default="zh",
界面语言: Optional[str] = Field(
default=None,
description="UI language for export messages. Options: 'en' (English), 'zh' (Chinese)",
)
英文字体: str = Field(
default="Calibri",
英文字体: Optional[str] = Field(
default=None,
description="Font for Latin characters (e.g., 'Times New Roman', '', 'Arial')",
)
中文字体: str = Field(
default="SimSun",
中文字体: Optional[str] = Field(
default=None,
description="Font for Asian characters (e.g., 'SimSun', 'Microsoft YaHei', 'PingFang SC')",
)
代码字体: str = Field(
default="Consolas",
代码字体: Optional[str] = Field(
default=None,
description="Font for code blocks and inline code (e.g., 'Consolas', 'Courier New', 'Monaco')",
)
表头背景色: str = Field(
default="F2F2F2",
表头背景色: Optional[str] = Field(
default=None,
description="Table header background color (hex, without #)",
)
表格隔行背景色: str = Field(
default="FBFBFB",
表格隔行背景色: Optional[str] = Field(
default=None,
description="Table zebra stripe background color for alternate rows (hex, without #)",
)
Mermaid_PNG缩放比例: float = Field(
default=3.0,
Mermaid_PNG缩放比例: Optional[float] = Field(
default=None,
description="PNG render resolution multiplier (higher = clearer, larger file)",
)
Mermaid显示比例: float = Field(
default=1.0,
Mermaid显示比例: Optional[float] = Field(
default=None,
description="Diagram width relative to available page width (<=1 recommended)",
)
Mermaid布局优化: bool = Field(
default=False,
Mermaid布局优化: Optional[bool] = Field(
default=None,
description="Optimize Mermaid layout: convert LR to TD for graph/flowchart",
)
Mermaid背景色: str = Field(
default="",
Mermaid背景色: Optional[str] = Field(
default=None,
description="Mermaid background color. Empty = transparent (recommended for Word dark mode). Used only for optional PNG fill.",
)
启用Mermaid图注: bool = Field(
default=True,
启用Mermaid图注: Optional[bool] = Field(
default=None,
description="Add figure captions under Mermaid images/charts",
)
启用数学公式: bool = Field(
default=True,
启用数学公式: Optional[bool] = Field(
default=None,
description="Enable LaTeX math block conversion (\\\\[...\\\\] and $$...$$) into Word equations",
)
启用行内公式: bool = Field(
default=True,
启用行内公式: Optional[bool] = Field(
default=None,
description="Enable inline $...$ math conversion into Word equations (conservative parsing to reduce false positives)",
)
@@ -449,11 +454,21 @@ class Action:
user_id = __user__.get("id", "unknown_user")
# Apply UserValves if present
if __user__ and "valves" in __user__:
# Update self.valves with user-specific values
for key, value in __user__["valves"].model_dump().items():
if hasattr(self.valves, key):
setattr(self.valves, key, value)
if __user__:
# Robustly parse UserValves whether it's a dict or Pydantic model
raw_valves = __user__.get("valves", {})
if isinstance(raw_valves, self.UserValves):
user_valves = raw_valves
elif isinstance(raw_valves, dict):
user_valves = self.UserValves(**raw_valves)
else:
user_valves = None
if user_valves:
for key, value in user_valves.model_dump(exclude_unset=True).items():
# Only override if the value is not None (and explicitly set)
if hasattr(self.valves, key) and value is not None:
setattr(self.valves, key, value)
# Get user language from Valves configuration
self._user_lang = self._get_lang_key(self.valves.界面语言)
@@ -490,6 +505,37 @@ class Action:
try:
message_content = last_assistant_message["content"]
if isinstance(message_content, str):
if __event_emitter__ and self.valves.SHOW_DEBUG_LOG:
debug_data = {}
for name, regex in [
("Details Block (详情块)", _ALL_DETAILS_RE),
("Think Block (思考块)", _THINK_RE),
("Analysis Block (分析块)", _ANALYSIS_RE),
]:
matches = regex.findall(message_content)
if matches:
debug_data[name] = [
(m[:200] + "...") if len(m) > 200 else m
for m in matches
]
if debug_data:
await self._emit_debug_log(
__event_emitter__,
"上下文内容清理分析 (Context Stripping Analysis)",
debug_data,
)
# Log font configuration
await self._emit_debug_log(
__event_emitter__,
"字体配置 (Font Configuration)",
{
"英文字体 (Latin Font)": self.valves.英文字体,
"中文字体 (Asian Font)": self.valves.中文字体,
"代码字体 (Code Font)": self.valves.代码字体,
},
)
message_content = self._strip_reasoning_blocks(message_content)
if not message_content or not message_content.strip():
@@ -1101,34 +1147,11 @@ class Action:
return title.strip() if isinstance(title, str) else ""
def clean_filename(self, name: str) -> str:
"""Clean illegal characters from filename and strip emoji."""
"""清理文件名中的非法字符并移除 Emoji"""
if not isinstance(name, str):
return ""
def _is_emoji_codepoint(codepoint: int) -> bool:
# Common emoji ranges + flag regional indicators.
return (
0x1F000 <= codepoint <= 0x1FAFF
or 0x1F1E6 <= codepoint <= 0x1F1FF
or 0x2600 <= codepoint <= 0x26FF
or 0x2700 <= codepoint <= 0x27BF
or 0x2300 <= codepoint <= 0x23FF
or 0x2B00 <= codepoint <= 0x2BFF
)
def _is_emoji_modifier(codepoint: int) -> bool:
# VS15/VS16, ZWJ, keycap, skin tones, and tag characters used in some emoji sequences.
return (
codepoint in (0x200D, 0xFE0E, 0xFE0F, 0x20E3)
or 0x1F3FB <= codepoint <= 0x1F3FF
or 0xE0020 <= codepoint <= 0xE007F
)
without_emoji = "".join(
ch
for ch in name
if not (_is_emoji_codepoint(ord(ch)) or _is_emoji_modifier(ord(ch)))
)
without_emoji = self._remove_emojis(name)
cleaned = re.sub(r'[\\/*?:"<>|]', "", without_emoji)
cleaned = re.sub(r"\s+", " ", cleaned).strip().strip(".")
return cleaned[:50].strip()
@@ -1496,7 +1519,10 @@ class Action:
# If there is no h1 in content, prepend chat title as h1 when provided
if top_heading and not has_h1:
self.add_heading(doc, top_heading, 1)
# Remove emojis from title for a professional look
clean_title = self._remove_emojis(top_heading)
# Use Title style (level 0) for the main document title
self.add_heading(doc, clean_title, 0)
lines = markdown_text.split("\n")
i = 0
@@ -1756,7 +1782,7 @@ class Action:
cur = text
for _ in range(10):
prev = cur
cur = _REASONING_DETAILS_RE.sub("", cur)
cur = _ALL_DETAILS_RE.sub("", cur)
cur = _THINK_RE.sub("", cur)
cur = _ANALYSIS_RE.sub("", cur)
if cur == prev:
@@ -2240,14 +2266,154 @@ class Action:
font = style.font
font.name = self.valves.英文字体
font.size = Pt(11)
# Set Asian font
style._element.rPr.rFonts.set(qn("w:eastAsia"), self.valves.中文字体)
# Ensure rPr element exists
rPr = style._element.get_or_add_rPr()
rFonts = rPr.get_or_add_rFonts()
# Set Latin and Asian fonts explicitly
rFonts.set(qn("w:ascii"), self.valves.英文字体)
rFonts.set(qn("w:hAnsi"), self.valves.英文字体)
rFonts.set(qn("w:eastAsia"), self.valves.中文字体)
# Set language to zh-CN to prevent MS Gothic fallback (Japanese font)
lang = rPr.find(qn("w:lang"))
if lang is None:
lang = OxmlElement("w:lang")
rPr.append(lang)
lang.set(qn("w:val"), "en-US")
lang.set(qn("w:eastAsia"), "zh-CN")
logger.info(
f"[Font Config] Latin: {self.valves.英文字体}, Asian: {self.valves.中文字体}"
)
# Set paragraph format
paragraph_format = style.paragraph_format
paragraph_format.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
paragraph_format.space_after = Pt(6)
# 配置 Title 样式 (用于文档标题)
# 标准格式: 22pt (二号), 加粗, 居中, 段后 24pt
if "Title" in doc.styles:
title_style = doc.styles["Title"]
title_font = title_style.font
title_font.name = self.valves.英文字体
title_font.size = Pt(22) # 二号字体
title_font.bold = True
title_font.color.rgb = RGBColor(0, 0, 0)
# 段落格式: 根据配置设置对齐方式和间距
title_pf = title_style.paragraph_format
alignment_map = {
"left": WD_ALIGN_PARAGRAPH.LEFT,
"center": WD_ALIGN_PARAGRAPH.CENTER,
"right": WD_ALIGN_PARAGRAPH.RIGHT,
}
title_pf.alignment = alignment_map.get(
self.valves.标题对齐方式.lower(), WD_ALIGN_PARAGRAPH.CENTER
)
title_pf.space_before = Pt(0)
title_pf.space_after = Pt(24)
t_rPr = title_style._element.get_or_add_rPr()
t_rFonts = t_rPr.get_or_add_rFonts()
t_rFonts.set(qn("w:ascii"), self.valves.英文字体)
t_rFonts.set(qn("w:hAnsi"), self.valves.英文字体)
t_rFonts.set(qn("w:eastAsia"), self.valves.中文字体)
# Set language for Title
t_lang = t_rPr.find(qn("w:lang"))
if t_lang is None:
t_lang = OxmlElement("w:lang")
t_rPr.append(t_lang)
t_lang.set(qn("w:val"), "en-US")
t_lang.set(qn("w:eastAsia"), "zh-CN")
# 标准标题字号 (基于中文文档规范):
# Heading 1: 16pt (三号), 加粗, 段前 24pt, 段后 12pt
# Heading 2: 15pt (小三), 加粗, 段前 18pt, 段后 6pt
# Heading 3: 14pt (四号), 加粗, 段前 12pt, 段后 6pt
# Heading 4-9: 12pt (小四), 加粗, 逐级减小间距
heading_formats = {
1: {"size": 16, "space_before": 24, "space_after": 12},
2: {"size": 15, "space_before": 18, "space_after": 6},
3: {"size": 14, "space_before": 12, "space_after": 6},
4: {"size": 12, "space_before": 12, "space_after": 6},
5: {"size": 12, "space_before": 6, "space_after": 6},
6: {"size": 11, "space_before": 6, "space_after": 3},
7: {"size": 11, "space_before": 6, "space_after": 3},
8: {"size": 10.5, "space_before": 6, "space_after": 3},
9: {"size": 10.5, "space_before": 6, "space_after": 3},
}
# Apply font settings to Heading 1-9
for i in range(1, 10):
style_id = f"Heading {i}"
if style_id in doc.styles:
heading_style = doc.styles[style_id]
heading_font = heading_style.font
heading_font.name = self.valves.英文字体
heading_font.color.rgb = RGBColor(0, 0, 0)
# 应用标准格式
fmt = heading_formats.get(
i, {"size": 11, "space_before": 6, "space_after": 3}
)
heading_font.size = Pt(fmt["size"])
heading_font.bold = True
heading_pf = heading_style.paragraph_format
heading_pf.space_before = Pt(fmt["space_before"])
heading_pf.space_after = Pt(fmt["space_after"])
# Ensure rPr exists
h_rPr = heading_style._element.get_or_add_rPr()
h_rFonts = h_rPr.get_or_add_rFonts()
# Set fonts
h_rFonts.set(qn("w:ascii"), self.valves.英文字体)
h_rFonts.set(qn("w:hAnsi"), self.valves.英文字体)
h_rFonts.set(qn("w:eastAsia"), self.valves.中文字体)
# Set language for Heading
h_lang = h_rPr.find(qn("w:lang"))
if h_lang is None:
h_lang = OxmlElement("w:lang")
h_rPr.append(h_lang)
h_lang.set(qn("w:val"), "en-US")
h_lang.set(qn("w:eastAsia"), "zh-CN")
def _remove_emojis(self, text: str) -> str:
"""从文本中移除 Emoji (基于 Unicode 范围)"""
if not isinstance(text, str):
return ""
def _is_emoji_codepoint(codepoint: int) -> bool:
# Common emoji ranges + flag regional indicators.
return (
0x1F000 <= codepoint <= 0x1FAFF
or 0x1F1E6 <= codepoint <= 0x1F1FF
or 0x2600 <= codepoint <= 0x26FF
or 0x2700 <= codepoint <= 0x27BF
or 0x2300 <= codepoint <= 0x23FF
or 0x2B00 <= codepoint <= 0x2BFF
)
def _is_emoji_modifier(codepoint: int) -> bool:
# VS15/VS16, ZWJ, keycap, skin tones, and tag characters used in some emoji sequences.
return (
codepoint in (0x200D, 0xFE0E, 0xFE0F, 0x20E3)
or 0x1F3FB <= codepoint <= 0x1F3FF
or 0xE0020 <= codepoint <= 0xE007F
)
return "".join(
ch
for ch in text
if not (_is_emoji_codepoint(ord(ch)) or _is_emoji_modifier(ord(ch)))
)
def add_heading(self, doc: Document, text: str, level: int):
"""Add heading"""
# Word heading levels start from 0, Markdown from 1
@@ -2283,6 +2449,12 @@ class Action:
if strike:
run.font.strike = True
# Explicitly set East Asian font to prevent MS Gothic fallback
# Word may not inherit w:eastAsia from style, causing Japanese font fallback for CJK
rPr = run._element.get_or_add_rPr()
rFonts = rPr.get_or_add_rFonts()
rFonts.set(qn("w:eastAsia"), self.valves.中文字体)
def _add_inline_code(self, paragraph, s: str):
if s == "":
return
@@ -2678,7 +2850,11 @@ class Action:
):
u = self._normalize_url(url)
if not u:
paragraph.add_run(display_text or text)
run = paragraph.add_run(display_text or text)
# Set East Asian font to prevent MS Gothic fallback
rPr = run._element.get_or_add_rPr()
rFonts = rPr.get_or_add_rFonts()
rFonts.set(qn("w:eastAsia"), self.valves.中文字体)
return
part = getattr(paragraph, "part", None)
@@ -2687,6 +2863,10 @@ class Action:
run = paragraph.add_run(display_text or text)
run.font.color.rgb = RGBColor(0, 0, 255)
run.font.underline = True
# Set East Asian font to prevent MS Gothic fallback
rPr = run._element.get_or_add_rPr()
rFonts = rPr.get_or_add_rFonts()
rFonts.set(qn("w:eastAsia"), self.valves.中文字体)
return
r_id = part.relate_to(u, RT.HYPERLINK, is_external=True)
@@ -2700,6 +2880,11 @@ class Action:
rStyle.set(qn("w:val"), "Hyperlink")
rPr.append(rStyle)
# Set East Asian font to prevent MS Gothic fallback
rFonts = OxmlElement("w:rFonts")
rFonts.set(qn("w:eastAsia"), self.valves.中文字体)
rPr.append(rFonts)
color = OxmlElement("w:color")
color.set(qn("w:val"), "0000FF")
rPr.append(color)

View File

@@ -0,0 +1,98 @@
# 开发指南勘误与更新
## 权限控制章节修正(第 2.2 节)
### ⚠️ 关键勘误
在实际测试中发现Shell 权限请求使用的是 **`fullCommandText`** 字段,而非文档中提到的 `command` 字段。
### 需要修改的代码行
**第 89 行(错误):**
```python
command = request.get("command", "")
```
**应改为(正确):**
```python
command = request.get("fullCommandText", "") or request.get("command", "")
```
### 完整的正确实现
```python
async def on_user_permission_request(request, context):
"""
统一权限审批网关
"""
kind = request.get("kind") # shell, write, mcp, read, url
# ✅ 正确:使用 fullCommandTextshell或 command其他
command = request.get("fullCommandText", "") or request.get("command", "")
# 1. 超级模式:全部允许
if self.valves.PERMISSIONS_ALLOW_ALL:
return {"kind": "approved"}
# 2. 默认安全:始终允许 "读" 和 "Web浏览"
if kind in ["read", "url"]:
return {"kind": "approved"}
# 3. 细粒度控制
if kind == "shell":
if self.valves.PERMISSIONS_ALLOW_SHELL:
return {"kind": "approved"}
pattern = self.valves.PERMISSIONS_SHELL_ALLOW_PATTERN
if pattern and command:
try:
if re.match(pattern, command):
return {"kind": "approved"}
except re.error:
print(f"[Config Error] Invalid Regex: {pattern}")
if kind == "write" and self.valves.PERMISSIONS_ALLOW_WRITE:
return {"kind": "approved"}
if kind == "mcp" and self.valves.PERMISSIONS_ALLOW_MCP:
return {"kind": "approved"}
# 4. 默认拒绝
print(f"[Permission Denied] Blocked: {kind} {command}")
return {
"kind": "denied-by-rules",
"rules": [{"kind": "check-openwebui-valves"}]
}
```
### Shell 权限请求的完整结构
```json
{
"kind": "shell",
"toolCallId": "call_xxx",
"fullCommandText": "ls -la", // ← 关键字段
"intention": "List all files and directories",
"commands": [
{
"identifier": "ls -la",
"readOnly": false
}
],
"possiblePaths": [],
"possibleUrls": [],
"hasWriteFileRedirection": false,
"canOfferSessionApproval": false
}
```
## 测试验证
已通过完整测试套件验证8/8 通过),详见 [PERMISSION_TEST_REPORT.md](./PERMISSION_TEST_REPORT.md)。
---
**更新日期**: 2026-01-30
**验证状态**: ✅ 已测试
**影响范围**: 2.2 权限与确认章节

View File

@@ -0,0 +1,173 @@
# GitHub Copilot SDK 权限控制测试报告
## 测试日期
2026-01-30
## 测试环境
- **Model**: gpt-4.1
- **Python**: 3.12
- **Copilot SDK**: Latest
## 关键发现
### 1. Shell 权限请求结构
Shell 类型的权限请求使用 **`fullCommandText`** 字段,而非 `command` 字段。
**完整请求示例:**
```json
{
"kind": "shell",
"toolCallId": "call_JKLi7tz3uSDQWE3LgzCpvSVy",
"fullCommandText": "ls -la",
"intention": "List all files and directories with details in the current directory",
"commands": [
{
"identifier": "ls -la",
"readOnly": false
}
],
"possiblePaths": [],
"possibleUrls": [],
"hasWriteFileRedirection": false,
"canOfferSessionApproval": false
}
```
### 2. 正则匹配模式验证
正则白名单模式已验证有效,必须使用 `fullCommandText` 字段:
```python
command = request.get("fullCommandText", "") or request.get("command", "")
pattern = self.valves.PERMISSIONS_SHELL_ALLOW_PATTERN
if pattern and command:
if re.match(pattern, command):
return {"kind": "approved"}
```
## 测试结果
### 完整测试套件8/8 通过 ✅)
| # | 测试用例 | 配置 | 提示词 | 预期 | 结果 | 状态 |
|---|---------|------|--------|------|------|------|
| 1 | Default Deny Shell | 默认 | 请执行: ls -la | ❌ Denied | ❌ Denied | ✅ |
| 2 | Allow All | allow_all=True | 请执行: ls -la | ✅ Approved | ✅ Approved | ✅ |
| 3 | Allow Shell | allow_shell=True | 请执行: pwd | ✅ Approved | ✅ Approved | ✅ |
| 4 | Regex Match: ^ls | pattern='^ls' | 请执行: ls -la | ✅ Approved | ✅ Approved | ✅ |
| 5 | Regex No Match | pattern='^ls' | 请执行: pwd | ❌ Denied | ❌ Denied | ✅ |
| 6 | Regex Complex | pattern='^(ls\|pwd\|echo)' | 请执行: pwd | ✅ Approved | ✅ Approved | ✅ |
| 7 | Regex No Match: git | pattern='^(ls\|pwd\|echo)' | 请执行: git status | ❌ Denied | ❌ Denied | ✅ |
| 8 | Read Permission | 默认 | Read: README.md | ✅ Approved | ✅ Approved | ✅ |
**总体通过率: 100%** 🎉
## 推荐配置示例
### 1. 安全模式(推荐生产环境)
```python
PERMISSIONS_ALLOW_ALL: bool = False
PERMISSIONS_ALLOW_SHELL: bool = False
PERMISSIONS_SHELL_ALLOW_PATTERN: str = "^(ls|pwd|echo|cat).*"
PERMISSIONS_ALLOW_WRITE: bool = False
PERMISSIONS_ALLOW_MCP: bool = True
```
### 2. 开发模式
```python
PERMISSIONS_ALLOW_ALL: bool = False
PERMISSIONS_ALLOW_SHELL: bool = False
PERMISSIONS_SHELL_ALLOW_PATTERN: str = "^(ls|pwd|echo|cat|grep|git status|npm test).*"
PERMISSIONS_ALLOW_WRITE: bool = False
PERMISSIONS_ALLOW_MCP: bool = True
```
### 3. 完全信任模式(仅限受控环境)
```python
PERMISSIONS_ALLOW_ALL: bool = True
```
## 实现建议
### 正确的权限处理代码
```python
import re
from typing import Any, Dict
async def on_user_permission_request(request: Dict[str, Any], context: Dict[str, str]):
"""
统一权限审批网关
"""
kind = request.get("kind")
# 关键:使用 fullCommandText 而非 command
command = request.get("fullCommandText", "") or request.get("command", "")
# 1. 超级模式
if self.valves.PERMISSIONS_ALLOW_ALL:
return {"kind": "approved"}
# 2. 默认安全read、url
if kind in ["read", "url"]:
return {"kind": "approved"}
# 3. Shell 细粒度控制
if kind == "shell":
if self.valves.PERMISSIONS_ALLOW_SHELL:
return {"kind": "approved"}
pattern = self.valves.PERMISSIONS_SHELL_ALLOW_PATTERN
if pattern and command:
try:
if re.match(pattern, command):
return {"kind": "approved"}
except re.error as e:
logger.error(f"Invalid regex: {pattern} - {e}")
# 4. Write 权限
if kind == "write" and self.valves.PERMISSIONS_ALLOW_WRITE:
return {"kind": "approved"}
# 5. MCP 权限
if kind == "mcp" and self.valves.PERMISSIONS_ALLOW_MCP:
return {"kind": "approved"}
# 6. 默认拒绝
logger.warning(f"Permission Denied: {kind} {command}")
return {
"kind": "denied-by-rules",
"rules": [{"kind": "security-policy"}]
}
```
## 常见正则模式示例
| 用途 | 正则表达式 | 说明 |
|------|-----------|------|
| 只读命令 | `^(ls|pwd|cat|echo|grep).*` | 允许常见只读命令 |
| Git 只读 | `^git (status\|log\|diff\|show).*` | 允许 Git 只读操作 |
| npm/yarn 测试 | `^(npm\|yarn) (test\|run).*` | 允许测试脚本 |
| 完全 shell | `.*` | ⚠️ 危险:允许所有命令 |
## 测试脚本位置
- 基础测试: [test_shell_permission_pattern.py](./test_shell_permission_pattern.py)
- 完整测试套件: [test_permission_comprehensive.py](./test_permission_comprehensive.py)
## 结论
**GitHub Copilot SDK 的权限控制机制完全有效**
**正则白名单模式已验证可用**
⚠️ **必须使用 `fullCommandText` 字段获取命令内容**
---
**测试执行者**: GitHub Copilot
**审核状态**: ✅ 已验证

View File

@@ -0,0 +1,238 @@
# OpenWebUI GitHub Copilot Pipe Enhancement Guide
基于 Copilot SDK 源码级研究的深度技术总结,旨在指导 OpenWebUI Pipe 的功能增强开发。
## 1. 认证机制 (Authentication)
官方支持通过环境变量传递 Token。在 Pipe 中,只要确保 `GH_TOKEN``GITHUB_TOKEN` 存在于环境变量中Copilot CLI 即可自动识别,无需在 `CopilotClient` 构造函数中重复注入。
### 核心实现
Pipe 应确保将 Token来自 Valve 或 Env正确设置到当前进程的环境变量中。
```python
import os
from copilot import CopilotClient
# 1. 设置环境变量 (如果从 Valve 获取)
if self.valves.GH_TOKEN:
os.environ["GH_TOKEN"] = self.valves.GH_TOKEN
# 2. 初始化客户端
# CopilotClient 启动的 CLI 子进程会自动继承当前环境中的 GH_TOKEN
client = CopilotClient({
# "cli_path": ...,
# 注意:无需在此处重复传入 github_tokenCLI 会自动读取环境变量
})
# 3. 启动前检查 (建议)
# status = await client.get_auth_status()
# if not status.isAuthenticated: ...
```
## 2. 权限与确认 (Permissions & Tools) - 核心控制点
这是用户最关心的部分:如何知道有哪些工具,以及如何控制它们的执行。
### 2.1 内置工具 (Built-in Tools)
Copilot CLI 内部管理了一组标准工具,**Python SDK 目前没有直接的 API (`client.list_tools()`) 来列出这些工具**。
但是,根据 SDK 的 `PermissionRequest` 类型定义 (`copilot/types.py`),我们可以反推其能力类别:
* **`shell`**: 执行终端命令 (对应 `run_terminal_command` 等)
* **`filesystem`** (对应 `read/write`): 文件读写 (对应 `read_file`, `edit_file`, `delete_file` 等)
* **`url`**: 网络访问 (对应 `fetch_url` 等)
* **`mcp`**: 连接的 MCP 服务器工具
> **提示**: `available_tools` 参数可以用来“隐藏”工具,让 Agent 根本不知道它有一把锤子。而 `on_permission_request` 是用来拦截 Agent 挥舞锤子的动作。通常我们建议**能力全开 (不设置 available_tools 限制)**,而在**权限层 (on_permission_request) 做拦截**。
### 2.2 实现“全部允许”与“按需允许”
建议在 Valves 中增加权限控制字段,并在 `on_permission_request` 中实现逻辑。
```python
import re
class Valves(BaseModel):
# ... 其他 Valve ...
# 权限控制开关
PERMISSIONS_ALLOW_ALL: bool = Field(default=False, description="DANGER: Auto-approve ALL actions (shell, write, etc).")
PERMISSIONS_ALLOW_SHELL: bool = Field(default=False, description="Auto-approve shell commands.")
PERMISSIONS_SHELL_ALLOW_PATTERN: str = Field(default="", description="Regex for approved shell commands (e.g., '^ls|^grep').")
PERMISSIONS_ALLOW_WRITE: bool = Field(default=False, description="Auto-approve file write/edit/delete.")
PERMISSIONS_ALLOW_MCP: bool = Field(default=True, description="Auto-approve MCP tool execution.")
# 权限处理 Hook 实现
async def on_user_permission_request(request, context):
"""
统一权限审批网关
request keys: kind, toolCallId, ... (shell requests have 'command')
"""
kind = request.get("kind") # shell, write, mcp, read, url
# 1. 超级模式:全部允许
if self.valves.PERMISSIONS_ALLOW_ALL:
return {"kind": "approved"}
# 2. 默认安全:始终允许 "读" 和 "Web浏览" (根据需求调整)
if kind in ["read", "url"]:
return {"kind": "approved"}
# 3. 细粒度控制
if kind == "shell":
# 3.1 完全允许 Shell
if self.valves.PERMISSIONS_ALLOW_SHELL:
return {"kind": "approved"}
# 3.2 基于正则允许特定命令
command = request.get("command", "")
pattern = self.valves.PERMISSIONS_SHELL_ALLOW_PATTERN
if pattern and command:
try:
if re.match(pattern, command):
return {"kind": "approved"}
except re.error:
print(f"[Config Error] Invalid Regex: {pattern}")
if kind == "write" and self.valves.PERMISSIONS_ALLOW_WRITE:
return {"kind": "approved"}
if kind == "mcp" and self.valves.PERMISSIONS_ALLOW_MCP:
return {"kind": "approved"}
# 4. 默认拒绝
print(f"[Permission Denied] Blocked request for: {kind} {request.get('command', '')}")
return {
"kind": "denied-by-rules",
"rules": [{"kind": "check-openwebui-valves"}]
}
# 注册 Hook
session = await client.create_session({
# ...
"on_permission_request": on_user_permission_request
})
```
## 3. Agent 与 MCP 集成 (Agents & MCP)
SDK 中的 Agent 和 MCP 并非独立文件,而是会话配置 (`SessionConfig`) 的一部分。Pipe 可以通过 Valves 动态构建这些配置。
### 关键映射关系
| SDK 概念 | OpenWebUI 对应 | 实现位置 | 关键参数 |
| :--- | :--- | :--- | :--- |
| **Custom Agent** | 自定义模型 / Persona | `create_session(custom_agents=[...])` | `name`, `prompt`, `tools` (仅名称) |
| **Agent Tools** | Valve 开关 / 预置工具 | `create_session(tools=[func1, func2])` | 必须先在 `tools` 注册函数Agent 才能引用 |
| **MCP Server** | Valve 配置 (JSON) | `create_session(mcp_servers={...})` | `command`, `args`, `env` (本地) |
### 代码范式:动态构建 Agent
```python
async def create_agent_session(client, user_prompt, model_name):
# 1. 定义工具 (必须是函数引用)
# 假设已从 OpenWebUI Tools 转换或内置
available_tools = [tool_web_search, tool_run_script]
# 2. 构建 Agent Manifest (针对当前请求的虚拟 Agent)
agent_manifest = {
"name": "openwebui_agent",
"description": "Dynamic agent from OpenWebUI",
"prompt": "You are a helpful assistant...", # 这里注入 System Prompt
"tools": ["web_search", "run_script"], # 引用上方工具的 name
"mcp_servers": {
# 可以在这里为特定 Agent 绑定 MCP
}
}
# 3. 创建会话
session = await client.create_session({
"model": "gpt-4", # 底层模型
"custom_agents": [agent_manifest],
"tools": available_tools, # 注册实际代码
"available_tools": ["web_search"], # 白名单控制当前可用工具
# ... 权限配置
})
```
## 4. MCP 服务器配置 (Native MCP Support)
Pipe 可以直接支持标准 MCP 协议Stdio。不需要额外的 MCP 客户端代理SDK 原生支持。
### Valve 配置结构建议
建议在 Pipe 的 Valves 中增加一个 `MCP_CONFIG` 字段JSON 字符串),解析后直接传给 SDK。
```python
# Valve 输入示例 (JSON)
# {
# "brave_search": {
# "type": "local",
# "command": "npx",
# "args": ["-y", "@modelcontextprotocol/server-brave-search"],
# "env": {"BRAVE_API_KEY": "..."}
# }
# }
# 代码实现
mcp_config = json.loads(self.valves.MCP_CONFIG)
session = await client.create_session({
# ...
"mcp_servers": mcp_config,
# 注意:必须配合权限自动审批,否则 MCP 工具无法调用
"on_permission_request": auto_approve_policy
})
```
## 5. 会话管理:持久化 vs 重放 (Persistence)
OpenWebUI 是无状态的,但 Copilot SDK 是有状态的(保留上下文窗口优化)。
### 最佳实践:以 `chat_id` 为锚点
利用 OpenWebUI 提供的 `chat_id` 来决定是 `resume` 还是 `start`
1. **Map**: 维护 `Dict[chat_id, session_id]` (内存或数据库)。
2. **Flow**:
* 请求进来 -> 检查 `chat_id` 是否有对应的 `session_id`
* **有**: 尝试 `client.resume_session(session_id)`
* *注意*Resume 时必须重新传入 `tools`, `hooks`, `on_permission_request`,因为这些 Python 对象不会被序列化保存。
* **无/失败**: 调用 `client.create_session()`,并将新 `session_id` 存入 Map。
3. **Fallback**: 如果 Resume 失败(例如后端重启 SDK 进程丢失),回退到 Create 新会话,并可选地将 OpenWebUI 传来的 `messages` 历史以 System Message 或历史插入的方式“重放”进去(虽然 SDK 不直接支持 insert history但可以通过连续的 `send` 模拟,但这很慢)。
* *简易方案*Resume 失败就作为新对话开始,只带入 System Prompt。
## 6. 高级 Hook提示词增强
利用 `on_user_prompt_submitted` 钩子,可以在不修改用户可见内容的情况下,向 Copilot 注入隐式上下文例如当前文件内容、Pipe 的元指令)。
```python
async def inject_context_hook(input_data, ctx):
user_prompt = input_data["prompt"]
# 比如:检测到用户在问代码,自动附加上下文
additional_context = "Current Language: Python. Framework: OpenWebUI."
return {
"modifiedPrompt": user_prompt, # 可以在这里改写提示词
"additionalContext": additional_context # 注入隐藏上下文
}
session = await client.create_session({
# ...
"hooks": {
"on_user_prompt_submitted": inject_context_hook
}
})
```
---
**总结开发清单:**
1. [ ] **Env Auth**: 读取环境变量 -> `CopilotClient`
2. [ ] **Permission Valve**: 实现 `PERMISSIONS_ALLOW_ALL/SHELL` 等 Valves。
3. [ ] **Auto-Approve Hook**: 实现 `on_permission_request` 逻辑。
4. [ ] **MCP Valve**: 添加 JSON Valve -> `session.mcp_servers`
5. [ ] **Session Map**: 实现 `chat_id` <-> `session_id` 的简单的内存映射。
6. [ ] **Resume Logic**: 优先 `resume_session`,并记得并在 resume 时重传 Hook 和 Tools。

View File

@@ -0,0 +1,620 @@
#!/usr/bin/env python3
import argparse
import asyncio
import datetime as dt
import json
import logging
import os
import sys
import textwrap
from typing import Iterable, List, Optional
from copilot import CopilotClient
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger("copilot_sdk_guide")
DEFAULT_CONTEXT_URLS = [
"https://raw.githubusercontent.com/github/copilot-sdk/main/README.md",
"https://raw.githubusercontent.com/github/copilot-sdk/main/python/README.md",
"https://raw.githubusercontent.com/github/copilot-sdk/main/docs/getting-started.md",
"https://raw.githubusercontent.com/github/copilot-cli/main/README.md",
"https://raw.githubusercontent.com/github/copilot-cli/main/changelog.md",
"https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli",
"https://docs.github.com/en/copilot/concepts/agents/about-agent-skills",
"https://raw.githubusercontent.com/github/awesome-copilot/main/README.md",
"https://raw.githubusercontent.com/github/awesome-copilot/main/skills/copilot-sdk/SKILL.md",
"https://raw.githubusercontent.com/github/awesome-copilot/main/instructions/agent-skills.instructions.md",
]
AWESOME_COPILOT_REPO = "github/awesome-copilot"
AWESOME_COPILOT_BRANCH = "main"
AWESOME_COPILOT_DOC_DIRS = ["docs/", "instructions/"]
TOPICS = [
"MCP Server Integration: JSON-RPC config and SDK hooks",
"Agent Manifests: Defining capabilities and permissions programmatically",
"Headless Auth: Device Code Flow and credential persistence",
"Session Replay vs Resume: Handling stateless frontend history",
"Advanced Session Hooks: Intercepting and modifying user prompts",
"Workspace Virtualization: Handling CWD for remote/virtual files",
"Error Recovery: Handling session disconnects and re-auth",
"Confirmation Events: programmatic handling of 'confirmation_required'",
"Skills: Conflict resolution and precedence defaults",
"Debugging: Tracing JSON-RPC traffic in the SDK",
"Billing & Policies: How seat management affects SDK features",
]
QUESTION_TEMPLATES = [
"Give a concise overview of {topic}.",
"Provide best practices and common pitfalls for {topic}.",
"Show a minimal example snippet for {topic}.",
"List recommended configuration defaults for {topic}.",
"How does {topic} relate to building a custom Agent?",
]
CLI_FOCUS_QUESTIONS = [
"How to configure MCP servers in ~/.copilot/config.json for SDK usage?",
"What CLI environment variables force 'Agent' mode vs 'Generic' mode?",
"Explain the 'confirmation' flow in CLI and how it maps to SDK events.",
"Does the CLI support 'dry-run' permission checks for tools?",
"What are the undocumented requirements for 'workspace' context updates?",
"How does the CLI handle 'device code' re-authentication automatically?",
]
def build_questions(max_questions: int) -> List[str]:
questions: List[str] = []
for topic in TOPICS:
for template in QUESTION_TEMPLATES:
questions.append(template.format(topic=topic))
questions.extend(CLI_FOCUS_QUESTIONS)
# De-duplicate while preserving order
seen = set()
uniq: List[str] = []
for q in questions:
if q in seen:
continue
seen.add(q)
uniq.append(q)
return uniq[:max_questions]
def build_deep_dive_prompts() -> List[str]:
return [
"Provide a python code example for configuring `CopilotClient` to connect to a local MCP server (e.g. Brave Search) via `CopilotClient` config.",
"Explain how to programmatically handle `tool.confirmation_required` events in a non-interactive stream using `session.on()`.",
"Show how to implement a 'Device Flow' login helper using SDK primitives (if available) or raw HTTP showing how to persist credentials.",
"Compare the pros and cons of 'Session Replay' (fast-forwarding history) vs 'Session Resume' (stateful ID) for a stateless web backend like OpenWebUI.",
"Detail the exact protocol for 'Virtual Workspace': how to implement a file system provider that feeds content to Copilot without physical files.",
"Create an 'Agent Manifest' example: how to define an Agent capable of specific high-privileged tools via SDK.",
"List all 'hidden' `SessionConfig` parameters relevant to Agent behavior and personality.",
]
def load_questions(path: str) -> List[str]:
if path.lower().endswith(".json"):
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
if isinstance(data, list):
return [str(x).strip() for x in data if str(x).strip()]
raise ValueError("JSON must be an array of strings")
with open(path, "r", encoding="utf-8") as f:
lines = [line.strip() for line in f.readlines()]
return [line for line in lines if line]
def fetch_url(url: str, headers: Optional[dict] = None) -> str:
import urllib.request
import time
retries = 3
if headers is None:
headers = {}
req = urllib.request.Request(url, headers=headers)
for i in range(retries):
try:
with urllib.request.urlopen(req, timeout=20) as response:
return response.read().decode("utf-8", errors="replace")
except Exception as exc:
if i == retries - 1:
logger.warning(
"Failed to fetch %s after %d attempts: %s", url, retries, exc
)
return ""
time.sleep(1 * (i + 1))
return ""
def list_repo_markdown_urls(
repo: str,
branch: str,
dir_prefixes: List[str],
) -> List[str]:
api_url = f"https://api.github.com/repos/{repo}/git/trees/{branch}?recursive=1"
headers = {}
if os.environ.get("GITHUB_TOKEN"):
headers["Authorization"] = f"token {os.environ.get('GITHUB_TOKEN')}"
try:
content = fetch_url(api_url, headers=headers)
if not content:
return []
data = json.loads(content)
except Exception as exc:
logger.warning("Failed to list repo tree: %s", exc)
return []
tree = data.get("tree", []) if isinstance(data, dict) else []
urls: List[str] = []
for item in tree:
if not isinstance(item, dict):
continue
path = item.get("path", "")
if not path or not path.endswith(".md"):
continue
if any(path.startswith(prefix) for prefix in dir_prefixes):
raw = f"https://raw.githubusercontent.com/{repo}/{branch}/{path}"
urls.append(raw)
return urls
def read_local_sdk_source(max_chars: int = 300000) -> str:
"""
Locates the installed 'copilot' package and reads its source code.
This ensures analysis is based on the actual installed version, not just docs.
"""
try:
import copilot
except ImportError:
logger.error("Could not import 'copilot' SDK. Is it installed?")
return ""
package_dir = os.path.dirname(copilot.__file__)
logger.info(f"Reading SDK source from: {package_dir}")
source_chunks = []
total_chars = 0
# Prioritize key files that define core logic
priority_files = ["client.py", "session.py", "types.py", "events.py", "__init__.py"]
# First pass: Recursively find all .py files
all_py_files = []
for root, dirs, files in os.walk(package_dir):
if "__pycache__" in root:
continue
for file in files:
if file.endswith(".py"):
all_py_files.append(os.path.join(root, file))
# Sort files: priority files first, then alphabetical
def sort_key(path):
fname = os.path.basename(path)
if fname in priority_files:
return (0, priority_files.index(fname))
return (1, path)
all_py_files.sort(key=sort_key)
for path in all_py_files:
rel_path = os.path.relpath(path, os.path.dirname(package_dir))
try:
with open(path, "r", encoding="utf-8") as f:
content = f.read()
# Add file delimiter for the model
header = f"\n\n# ==================================================\n# SOURCE CODE FILE: {rel_path}\n# ==================================================\n"
chunk = header + content
if total_chars + len(chunk) > max_chars:
remaining = max_chars - total_chars
if remaining > len(header) + 100:
source_chunks.append(
chunk[:remaining] + "\n# [TRUNCATED DUE TO LENGTH LIMIT]"
)
logger.warning(f"Context limit reached. Stopping at {rel_path}")
break
source_chunks.append(chunk)
total_chars += len(chunk)
logger.info(f"Loaded source file: {rel_path} ({len(content)} chars)")
except Exception as e:
logger.warning(f"Failed to read source file {path}: {e}")
return "".join(source_chunks)
def build_context(urls: Iterable[str], max_chars: int) -> str:
chunks: List[str] = []
remaining = max_chars
for url in urls:
if remaining <= 0:
break
try:
content = fetch_url(url)
header = f"[Source: {url}]\n"
if len(header) >= remaining:
break
remaining -= len(header)
if len(content) > remaining:
content = content[:remaining] + "\n[TRUNCATED]\n"
remaining = 0
else:
remaining -= len(content)
chunks.append(header + content)
logger.info("Fetched context: %s", url)
except Exception as exc:
logger.warning("Failed to fetch %s: %s", url, exc)
return "\n\n".join(chunks)
def write_jsonl(path: str, item: dict) -> None:
with open(path, "a", encoding="utf-8") as f:
f.write(json.dumps(item, ensure_ascii=False) + "\n")
def write_markdown_header(path: str, title: str, meta: dict) -> None:
with open(path, "w", encoding="utf-8") as f:
f.write(f"# {title}\n\n")
for k, v in meta.items():
f.write(f"- **{k}**: {v}\n")
f.write("\n---\n\n")
def append_markdown_qa(path: str, question: str, answer: str) -> None:
with open(path, "a", encoding="utf-8") as f:
f.write(f"## Q: {question}\n\n")
f.write(f"{answer}\n\n")
def clamp_questions(questions: List[str], max_questions: int) -> List[str]:
return questions[: max(1, min(max_questions, 400))]
def print_progress_bar(
iteration,
total,
prefix="",
suffix="",
decimals=1,
length=50,
fill="",
printEnd="\r",
):
"""
Call in a loop to create terminal progress bar
"""
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
filledLength = int(length * iteration // total)
bar = fill * filledLength + "-" * (length - filledLength)
# Clear line extension to handle shrinking suffixes
print(f"\r{prefix} |{bar}| {percent}% {suffix}\033[K", end=printEnd)
# Print New Line on Complete
if iteration == total:
print()
async def run_session(
model: str,
questions: List[str],
output_dir: str,
context: str,
session_id: Optional[str],
delay: float,
output_lang: str,
enable_infinite_sessions: bool,
timeout: int,
) -> None:
client = CopilotClient()
await client.start()
session_config = {"model": model}
if session_id:
session_config["session_id"] = session_id
if enable_infinite_sessions:
session_config["infinite_sessions"] = {
"enabled": True,
"background_compaction_threshold": 0.8,
"buffer_exhaustion_threshold": 0.95,
}
session = await client.create_session(session_config)
timestamp = dt.datetime.now().strftime("%Y%m%d_%H%M%S")
jsonl_path = os.path.join(output_dir, f"copilot_sdk_guide_{timestamp}.jsonl")
md_path = os.path.join(output_dir, f"copilot_sdk_guide_{timestamp}.md")
write_markdown_header(
md_path,
"GitHub Copilot SDK & CLI 研究报告",
{
"model": model,
"questions": len(questions),
"timestamp": timestamp,
"language": output_lang,
},
)
lang_instruction = "Chinese" if "zh" in output_lang.lower() else "English"
system_prompt = textwrap.dedent(
f"""
You are an expert assistant. Focus on GitHub Copilot SDK and GitHub Copilot CLI.
CRITICAL INSTRUCTION: SOURCE CODE FIRST.
You have been provided with the ACTUAL PYTHON SOURCE CODE of the `copilot` SDK in the context.
When answering questions:
1. FIRST, analyze the provided source code (look for class definitions, type hints, methods).
2. THEN, refer to documentation if source code is ambiguous.
3. Do NOT hallucinate methods that do not exist in the source code.
4. If a feature (like MCP) is not explicitly in the code, explain how to implement it using the available primitives (low-level hooks/events).
Provide accurate, concise answers in {lang_instruction}. When relevant, include command names,
configuration keys, and pitfalls. Use bullet points where useful.
Output requirements:
- Write in {lang_instruction}.
- Provide practical code snippets (Python/TypeScript/CLI) when helpful.
- Include a short "建议/落地" section for integration into a pipe.
- If citing facts from provided context, briefly mention the source URL.
"""
).strip()
if context:
system_prompt += "\n\nAdditional context:\n" + context
await session.send_and_wait({"prompt": system_prompt}, timeout=timeout)
total_q = len(questions)
print_progress_bar(0, total_q, prefix="Progress:", suffix="Starting...", length=30)
for idx, question in enumerate(questions, start=1):
# Update progress bar (Asking...)
q_short = (question[:40] + "...") if len(question) > 40 else question.ljust(43)
print_progress_bar(
idx - 1, total_q, prefix="Progress:", suffix=f"Asking: {q_short}", length=30
)
# Log to file/debug only
logger.debug("[%s/%s] Asking: %s", idx, total_q, question)
answer = ""
max_retries = 3
for attempt in range(max_retries):
try:
response = await session.send_and_wait(
{"prompt": question}, timeout=timeout
)
answer = response.data.content if response and response.data else ""
break
except Exception as e:
logger.error(
f"Error asking question (Attempt {attempt+1}/{max_retries}): {e}"
)
if attempt < max_retries - 1:
await asyncio.sleep(2)
else:
answer = f"Error retrieving answer: {e}"
write_jsonl(
jsonl_path,
{
"index": idx,
"question": question,
"answer": answer,
"model": model,
},
)
append_markdown_qa(md_path, question, answer)
# Update progress bar (Done...)
print_progress_bar(
idx, total_q, prefix="Progress:", suffix=f"Done: {q_short}", length=30
)
if delay > 0:
await asyncio.sleep(delay)
await session.destroy()
await client.stop()
logger.info("Saved output to %s and %s", jsonl_path, md_path)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Ask up to 100 Copilot SDK questions via GitHub Copilot SDK",
)
parser.add_argument("--model", default="gpt-5.2-codex", help="Model to use")
parser.add_argument(
"--max-questions",
type=int,
default=100,
help="Max number of questions (1-400)",
)
parser.add_argument(
"--questions-file",
default="",
help="Path to .txt or .json list of questions",
)
parser.add_argument(
"--context-url",
action="append",
default=[],
help="Additional context URL (repeatable)",
)
parser.add_argument(
"--no-default-context",
action="store_true",
help="Disable default Copilot SDK context URLs",
)
parser.add_argument(
"--include-awesome-copilot-docs",
action="store_true",
help="Include all markdown files from awesome-copilot/docs",
)
parser.add_argument(
"--include-awesome-copilot-instructions",
action="store_true",
help="Include all markdown files from awesome-copilot/instructions",
)
parser.add_argument(
"--no-sdk-source",
action="store_true",
help="Do NOT read local SDK source code (default: reads source)",
)
parser.add_argument(
"--session-id",
default="",
help="Optional custom session ID",
)
parser.add_argument(
"--output-dir",
default="",
help="Directory to save outputs",
)
parser.add_argument(
"--delay",
type=float,
default=0.5,
help="Delay between questions (seconds)",
)
parser.add_argument(
"--max-context-chars",
type=int,
default=400000,
help="Max characters of aggregated context (default: 400000)",
)
parser.add_argument(
"--disable-infinite-sessions",
action="store_true",
help="Disable infinite sessions (default: enabled)",
)
parser.add_argument(
"--output-lang",
default="zh-CN",
help="Output language (default: zh-CN)",
)
parser.add_argument(
"--deep-dive",
action="store_true",
help="Append deep-dive prompts for more detailed research",
)
parser.add_argument(
"--timeout",
type=int,
default=3600,
help="Session request timeout in seconds (default: 3600)",
)
return parser.parse_args()
def main() -> None:
args = parse_args()
if args.questions_file:
questions = load_questions(args.questions_file)
else:
# Generate enough questions to cover everything
questions = build_questions(9999)
if args.deep_dive:
# Prepend deep dive questions to ensure they are prioritized
questions = build_deep_dive_prompts() + questions
questions = clamp_questions(questions, args.max_questions)
if not questions:
logger.error("No questions to ask")
sys.exit(1)
context_urls = [] if args.no_default_context else list(DEFAULT_CONTEXT_URLS)
if args.include_awesome_copilot_docs:
context_urls.extend(
list_repo_markdown_urls(
AWESOME_COPILOT_REPO,
AWESOME_COPILOT_BRANCH,
["docs/"],
)
)
if args.include_awesome_copilot_instructions:
context_urls.extend(
list_repo_markdown_urls(
AWESOME_COPILOT_REPO,
AWESOME_COPILOT_BRANCH,
["instructions/"],
)
)
context_urls.extend(args.context_url or [])
# 1. Read local source code first (Priority: High)
# We allocate up to max_context_chars to source code initially.
# The actual usage will likely be less for a typical SDK.
source_context = ""
source_chars_count = 0
if not args.no_sdk_source:
source_context = read_local_sdk_source(args.max_context_chars)
source_chars_count = len(source_context)
logger.info(f"Source context usage: {source_chars_count} chars")
# 2. Calculate remaining budget for Web Docs (Priority: Secondary)
# We ensure we don't exceed the global limit.
remaining_chars = max(10000, args.max_context_chars - source_chars_count)
logger.info(f"Remaining budget for web docs: {remaining_chars} chars")
# 3. Fetch remote docs
web_context = build_context(context_urls, remaining_chars)
combined_context = ""
# Assemble context in order of authority (Source > Docs)
if source_context:
combined_context += (
"# PRIMARY SOURCE: LOCAL SDK CODE (AUTHORITATIVE)\n"
+ source_context
+ "\n\n"
)
if web_context:
combined_context += (
"# SECONDARY SOURCE: WEB DOCUMENTATION & AWESOME-COPILOT\n" + web_context
)
output_dir = args.output_dir or os.path.join(
os.getcwd(), "plugins", "debug", "copilot_sdk_research", "outputs"
)
os.makedirs(output_dir, exist_ok=True)
asyncio.run(
run_session(
model=args.model,
questions=questions,
output_dir=output_dir,
context=combined_context,
session_id=args.session_id or None,
delay=args.delay,
output_lang=args.output_lang,
enable_infinite_sessions=not args.disable_infinite_sessions,
timeout=args.timeout,
)
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,2 @@
This is a dummy test file for verifying permission controls.
You can delete this file safely.

View File

@@ -0,0 +1,26 @@
import inspect
import json
try:
import copilot
from copilot import types
print(f"Copilot location: {copilot.__file__}")
print("\n=== Inspecting PermissionRequest types ===")
# Look for definition of PermissionRequest or similar
# In the absence of direct access to the CLI output structure documentation,
# we can check if there are type hints or typed dicts in copilot.types
for name, obj in inspect.getmembers(types):
if "Permission" in name or "Request" in name:
print(f"\nType: {name}")
try:
if hasattr(obj, "__annotations__"):
print(obj.__annotations__)
except:
pass
except ImportError:
print("copilot module not installed")

View File

@@ -0,0 +1,68 @@
{"index": 1, "question": "Provide a python code example for configuring `CopilotClient` to connect to a local MCP server (e.g. Brave Search) via `CopilotClient` config.", "answer": "SDK 源码显示 MCP 服务器通过 `create_session`/`resume_session` 的 `mcp_servers` 配置传入并映射为 `mcpServers`,不是 CopilotClient 构造参数(源:本地源码 copilot/types.py / copilot/client.py。下面是本地 stdio MCP以 Brave Search 为例,命令/密钥请替换)的 Python 示例。 \n```python\nimport asyncio\nfrom copilot import CopilotClient\n\nasync def main():\n client = CopilotClient()\n await client.start()\n\n session = await client.create_session({\n \"model\": \"gpt-5\",\n \"mcp_servers\": {\n \"brave_search\": {\n \"type\": \"local\", # 或 \"stdio\"\n \"command\": \"/path/to/brave-search-mcp\", # 替换为你的 MCP server 启动命令\n \"args\": [],\n \"env\": {\"BRAVE_API_KEY\": \"YOUR_KEY\"},\n \"tools\": [\"*\"],\n \"timeout\": 30000, # 毫秒\n }\n },\n })\n\n await session.send_and_wait({\"prompt\": \"用 Brave Search 查询 OpenAI 最新公告\"})\n await client.stop()\n\nasyncio.run(main())\n```\n\n要点/坑:\n- `tools`: `[\"*\"]` 表示全量,`[]` 表示不启用\n- `timeout`: 毫秒\n- `command/args/env`: 按你的 MCP server 实际启动方式替换\n\n建议/落地:\n- pipe 内复用同一 session结束时统一 `client.stop()`", "model": "gpt-5.2-codex"}
{"index": 2, "question": "Explain how to programmatically handle `tool.confirmation_required` events in a non-interactive stream using `session.on()`.", "answer": "SDK 源码未定义 `tool.confirmation_required` 事件,权限确认由 JSONRPC `permission.request` 触发并交给 `on_permission_request` 回调处理,`session.on()` 只能订阅 `tool.user_requested`/`tool.execution_start` 等事件做记录关联(来源:本地源码 copilot/client.py、copilot/session.py、copilot/types.py。 \n在非交互流里必须提供 `on_permission_request` 自动审批/拒绝,否则默认拒绝且回调异常也会被拒绝。\n\n要点/坑 \n- 配置键: `on_permission_request`SessionConfig \n- 权限类型: `shell` / `write` / `mcp` / `read` / `url` \n- 回调异常或缺失 -> `denied-no-approval-rule-and-could-not-request-from-user`\n\n```python\nimport asyncio\nfrom copilot import CopilotClient\nfrom copilot.generated.session_events import SessionEventType\n\nasync def main():\n client = CopilotClient()\n await client.start()\n\n def on_event(ev):\n if ev.type == SessionEventType.TOOL_USER_REQUESTED:\n print(f\"tool requested: {ev.data.tool_name} {ev.data.tool_call_id}\")\n elif ev.type == SessionEventType.TOOL_EXECUTION_START:\n print(f\"tool start: {ev.data.tool_name}\")\n\n async def on_permission_request(req, ctx):\n kind = req.get(\"kind\")\n if kind in (\"read\", \"mcp\"):\n return {\"kind\": \"approved\"}\n return {\"kind\": \"denied-by-rules\", \"rules\": [{\"kind\": \"deny-non-read\"}]}\n\n session = await client.create_session({\n \"model\": \"gpt-5\",\n \"on_permission_request\": on_permission_request,\n })\n session.on(on_event)\n\n await session.send_and_wait({\"prompt\": \"请调用需要权限的工具\"})\n await client.stop()\n\nasyncio.run(main())\n```\n\n建议/落地 \n- pipe 启动时注册审批策略 + `session.on` 记录 `tool_call_id` 做审计", "model": "gpt-5.2-codex"}
{"index": 3, "question": "Show how to implement a 'Device Flow' login helper using SDK primitives (if available) or raw HTTP showing how to persist credentials.", "answer": "SDK 本身没有 deviceflow 登录 API可用鉴权原语只有 CopilotClient 的 `github_token`/`use_logged_in_user` 与 `get_auth_status`(源:本地源码 copilot/client.py、copilot/types.py。 \n下面用 GitHub OAuth Device Flow 原始 HTTP 获取 token、持久化本地并注入 `github_token`端点说明https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow。\n\n```python\nimport asyncio\nimport json\nimport os\nimport time\nimport urllib.parse\nimport urllib.request\nfrom pathlib import Path\n\nfrom copilot import CopilotClient\n\nCLIENT_ID = \"YOUR_OAUTH_APP_CLIENT_ID\"\nSCOPE = \"read:user\" # 按你的 OAuth App 需求调整\nTOKEN_PATH = Path.home() / \".config\" / \"myapp\" / \"copilot_token.json\"\n\ndef http_post(url, data):\n body = urllib.parse.urlencode(data).encode()\n req = urllib.request.Request(url, data=body, headers={\"Accept\": \"application/json\"})\n with urllib.request.urlopen(req) as resp:\n return json.loads(resp.read().decode())\n\ndef load_token():\n if TOKEN_PATH.exists():\n return json.loads(TOKEN_PATH.read_text()).get(\"access_token\")\n return None\n\ndef save_token(token):\n TOKEN_PATH.parent.mkdir(parents=True, exist_ok=True)\n TOKEN_PATH.write_text(json.dumps(token))\n os.chmod(TOKEN_PATH, 0o600)\n\ndef device_flow():\n code = http_post(\n \"https://github.com/login/device/code\",\n {\"client_id\": CLIENT_ID, \"scope\": SCOPE},\n )\n print(f\"Open {code['verification_uri']} and enter {code['user_code']}\")\n interval = int(code.get(\"interval\", 5))\n\n while True:\n token = http_post(\n \"https://github.com/login/oauth/access_token\",\n {\n \"client_id\": CLIENT_ID,\n \"device_code\": code[\"device_code\"],\n \"grant_type\": \"urn:ietf:params:oauth:grant-type:device_code\",\n },\n )\n if \"access_token\" in token:\n return token\n if token.get(\"error\") == \"slow_down\":\n interval += 5\n elif token.get(\"error\") not in (\"authorization_pending\",):\n raise RuntimeError(token)\n time.sleep(interval)\n\nasync def main():\n access_token = load_token()\n if not access_token:\n token = device_flow()\n save_token(token)\n access_token = token[\"access_token\"]\n\n client = CopilotClient({\"github_token\": access_token})\n await client.start()\n # ... create_session / send ...\n await client.stop()\n\nif __name__ == \"__main__\":\n asyncio.run(main())\n```\n\n要点/坑 \n- `cli_url` 与 `github_token` 互斥 \n- SDK 不保存凭据 需自行持久化 \n- 轮询需处理 `authorization_pending` / `slow_down`\n\n建议/落地 \n- pipe 启动时读取 token 文件 不存在则走 device flow \n- 用 `github_token` 注入 保持非交互运行", "model": "gpt-5.2-codex"}
{"index": 4, "question": "Compare the pros and cons of 'Session Replay' (fast-forwarding history) vs 'Session Resume' (stateful ID) for a stateless web backend like OpenWebUI.", "answer": "对无状态后端(OpenWebUI)而言SDK 只支持通过 `resume_session(session_id)` 的状态续接所谓“Session Replay”需要你自行重放历史 prompt来源本地 SDK 源码 copilot/client.py、copilot/session.py。 \n对比要点短语 \n- Replay无状态/易扩展;缺点是耗时耗 tokens、易截断/丢工具状态、可能重复副作用 \n- Resume省 tokens、保留工具/compaction/`workspace_path` 状态;缺点是需持久化 session_id、依赖会话存活/跨实例一致性。 \n建议/落地:优先持久化 chat_id→session_id 并 resume_session失效时回退为重放最近 N 轮并重建工具上下文。", "model": "gpt-5.2-codex"}
{"index": 5, "question": "Detail the exact protocol for 'Virtual Workspace': how to implement a file system provider that feeds content to Copilot without physical files.", "answer": "SDK 源码里没有“Virtual Workspace”协议或文件系统 provider 接口;会话只识别真实路径(`SessionConfig.working_directory`、`MessageOptions.attachments`)。要在无物理文件情况下供给内容,只能用自定义工具或 MCP 服务器把“读写文件”变成 tool 调用(来源:本地 SDK 源码 copilot/types.py / copilot/client.py。\n\n可用原语源码\n- `SessionConfig.tools` / `available_tools`:自定义工具与白名单\n- `MessageOptions.attachments`:仅支持真实路径文件/目录\n\n方案 A自定义工具实现 VFS推荐\n```python\nimport asyncio\nfrom pydantic import BaseModel, Field\nfrom copilot import CopilotClient, define_tool\n\nVFS = {\"README.md\": \"# Virtual README\\n内容来自内存\\n\"}\n\nclass VfsReadParams(BaseModel):\n path: str = Field(description=\"virtual path\")\n\n@define_tool(name=\"vfs_read\", description=\"Read a virtual file\")\ndef vfs_read(params: VfsReadParams) -> str:\n return VFS.get(params.path, \"\")\n\n@define_tool(name=\"vfs_list\", description=\"List virtual files\")\ndef vfs_list() -> list[str]:\n return sorted(VFS.keys())\n\nasync def main():\n client = CopilotClient()\n await client.start()\n session = await client.create_session({\n \"model\": \"gpt-5\",\n \"tools\": [vfs_read, vfs_list],\n \"available_tools\": [\"vfs_read\", \"vfs_list\"],\n \"system_message\": {\"content\": \"Use vfs_* tools for all file access.\"},\n })\n await session.send_and_wait({\"prompt\": \"打开 README.md\"})\n await client.stop()\n\nasyncio.run(main())\n```\n\n方案 BMCP 服务器(本地 stdioVFS 由你实现)\n```python\nsession = await client.create_session({\n \"model\": \"gpt-5\",\n \"mcp_servers\": {\n \"vfs\": {\n \"type\": \"local\",\n \"command\": \"python\",\n \"args\": [\"-m\", \"my_vfs_mcp_server\"],\n \"tools\": [\"*\"],\n }\n },\n})\n```\n\n注意/坑 \n- `available_tools` 只保留你的 VFS 工具以强制使用 \n- 内置文件工具只读真实文件;要兼容只能落地临时文件 \n- 大文件需分块返回,避免上下文爆炸 \n\n建议/落地 \n- pipe 内维护 VFS 存储DB/Redis/内存) \n- 启动 session 时注入 `system_message` + `available_tools` 强制 VFS 入口", "model": "gpt-5.2-codex"}
{"index": 6, "question": "Create an 'Agent Manifest' example: how to define an Agent capable of specific high-privileged tools via SDK.", "answer": "SDK 没有独立“Agent Manifest”文件格式等价于 `create_session` 的 `custom_agents`CustomAgentConfig并用 `tools`/`available_tools` 约束高权限工具,权限由 `on_permission_request` 处理(来源:本地 SDK 源码 copilot/types.py、copilot/client.py。 \n示例如下manifest 为 Python dict可序列化为 JSON 配置)。\n\n```python\nimport asyncio\nfrom pydantic import BaseModel, Field\nfrom copilot import CopilotClient, define_tool\n\nclass AdminShellParams(BaseModel):\n cmd: str = Field(description=\"approved shell command\")\n\n@define_tool(name=\"admin_shell\", description=\"Run an approved shell command\")\ndef admin_shell(params: AdminShellParams) -> str:\n # TODO: 执行受控命令(白名单/沙箱)\n return f\"approved: {params.cmd}\"\n\nclass AdminWriteParams(BaseModel):\n path: str = Field(description=\"target path\")\n content: str = Field(description=\"content\")\n\n@define_tool(name=\"admin_write\", description=\"Write to privileged store\")\ndef admin_write(params: AdminWriteParams) -> str:\n # TODO: 受控写入\n return \"ok\"\n\nasync def on_permission_request(req, _ctx):\n if req.get(\"kind\") in (\"shell\", \"write\"):\n return {\"kind\": \"approved\"}\n return {\"kind\": \"denied-by-rules\", \"rules\": [{\"kind\": \"deny\"}]}\n\nasync def main():\n client = CopilotClient()\n await client.start()\n\n agent_manifest = {\n \"name\": \"ops-admin\",\n \"display_name\": \"Ops Admin\",\n \"description\": \"Privileged ops agent\",\n \"prompt\": \"Use only admin_* tools for privileged ops.\",\n \"tools\": [\"admin_shell\", \"admin_write\"],\n \"infer\": True,\n }\n\n session = await client.create_session({\n \"model\": \"gpt-5\",\n \"tools\": [admin_shell, admin_write],\n \"custom_agents\": [agent_manifest],\n \"available_tools\": [\"admin_shell\", \"admin_write\"],\n \"on_permission_request\": on_permission_request,\n })\n\n await session.send_and_wait({\"prompt\": \"使用 ops-admin 执行受控操作\"})\n await client.stop()\n\nasyncio.run(main())\n```\n\n要点/坑 \n- session.tools 未注册 -> agent.tools 不可用 \n- custom_agents.tools 仅写名称 \n- 非交互缺少 on_permission_request -> 默认拒绝 \n- SDK 无显式 agent 选择参数 -> 需在 prompt 指定 \n\n建议/落地 \n- pipe 启动时加载 manifest 注入 custom_agents + available_tools + on_permission_request", "model": "gpt-5.2-codex"}
{"index": 7, "question": "List all 'hidden' `SessionConfig` parameters relevant to Agent behavior and personality.", "answer": "基于本地 SDK 源码 `copilot/types.py` 的 SessionConfig影响 Agent 行为/人格的相关参数如下(来源:本地 SDK 源码)。 \n- `system_message`: `append`/`replace``replace` 会移除 SDK guardrails风险 \n- `custom_agents`: `prompt`/`description`/`display_name`/`tools`/`mcp_servers`/`infer``tools` 仅名称且必须在 `tools` 注册 \n- `tools` / `available_tools` / `excluded_tools`: 能力与行为边界;`available_tools` 优先于 `excluded_tools` \n- `hooks`: `on_user_prompt_submitted`/`on_pre_tool_use`/`on_post_tool_use`/`on_session_start`/`on_session_end`/`on_error_occurred`,可改写 prompt/工具参数/结果 \n- `on_permission_request`: 非交互审批策略;缺失或异常 => 拒绝 \n- `on_user_input_request`: 启用/处理 `ask_user` \n- `mcp_servers`: 注入外部工具集 \n- `skill_directories` / `disabled_skills`: 技能启用/禁用 \n- `model` / `provider`: 模型与 BYOK 影响输出风格与能力 \n- `working_directory`: 上下文范围 \n- `infinite_sessions`: compaction 阈值影响记忆保留 \n- `config_dir`: CLI 配置/状态目录,行为取决于 CLI 侧配置 \n\n建议/落地:在 pipe 启动时固定 `system_message` + `available_tools/excluded_tools` + `hooks`,并持久化 session_id 以便 `resume_session`。", "model": "gpt-5.2-codex"}
{"index": 8, "question": "Give a concise overview of MCP Server Integration: JSON-RPC config and SDK hooks.", "answer": "SDK 中 MCP 通过 create_session/resume_session 的 mcp_servers 传入(映射到 JSONRPC 的 mcpServers支持 local/stdio 与 http/sse两类配置字段分别为 type/command/args/env/cwd/timeout/tools 或 type/url/headers/timeout/tools来源本地 SDK 源码)。 \nSDK hooks 使用 hooks(on_pre_tool_use/on_post_tool_use/on_user_prompt_submitted/on_session_start/on_session_end/on_error_occurred) 进行拦截与附加上下文,权限需 on_permission_request 对 kind='mcp' 明确批准,且 session.on 可审计 tool.execution_* 事件中的 mcp_server_name/mcp_tool_name。 \n示例见下建议/落地pipe 中启用 on_permission_request、限制 tools 白名单并记录 MCP 事件。\n\n```python\nimport asyncio\nfrom copilot import CopilotClient\n\nasync def on_permission_request(req, _):\n return {\"kind\": \"approved\"} if req.get(\"kind\") == \"mcp\" else {\n \"kind\": \"denied-by-rules\",\n \"rules\": [{\"kind\": \"deny\"}],\n }\n\nasync def on_pre_tool_use(input, _):\n if input.get(\"toolName\", \"\").startswith(\"mcp.\"):\n return {\"additionalContext\": \"MCP tool call observed\"}\n\nasync def main():\n client = CopilotClient()\n await client.start()\n\n session = await client.create_session({\n \"model\": \"gpt-5\",\n \"mcp_servers\": {\n \"brave\": {\n \"type\": \"local\",\n \"command\": \"/path/to/brave-mcp\",\n \"args\": [],\n \"env\": {\"BRAVE_API_KEY\": \"YOUR_KEY\"},\n \"tools\": [\"*\"],\n \"timeout\": 30000,\n }\n },\n \"hooks\": {\"on_pre_tool_use\": on_pre_tool_use},\n \"on_permission_request\": on_permission_request,\n })\n\n await session.send_and_wait({\"prompt\": \"用 Brave Search 查询 Copilot SDK 新闻\"})\n await client.stop()\n\nasyncio.run(main())\n```", "model": "gpt-5.2-codex"}
{"index": 9, "question": "Provide best practices and common pitfalls for MCP Server Integration: JSON-RPC config and SDK hooks.", "answer": "基于本地 SDK 源码MCP 配置通过 `create_session`/`resume_session` 的 `mcp_servers` 进入 JSONRPC `mcpServers`,并通过 `on_permission_request` 与 `hooks` 控制工具审批与行为(来源:本地 SDK 源码) \n- 最佳实践local/stdio 用 `type/command/args/env/cwd/timeout/tools`remote http/sse 用 `type/url/headers/timeout/tools``tools` 尽量白名单、必要时 `[\"*\"]``timeout` 毫秒;`hooks.on_pre_tool_use` 做改参/拒绝,`session.on` 记录 `tool.execution_*` 与 `mcp_server_name`/`mcp_tool_name` \n- 常见坑:把 MCP 配置放在 CopilotClient缺少 `on_permission_request`kind=\"mcp\")导致默认拒绝;`tools: []` 等于禁用全部;设置 `available_tools` 却未包含 MCP 工具名导致被过滤 \n\n建议/落地pipe 中固定审批策略 + 统一 MCP 配置模板 + 审计日志", "model": "gpt-5.2-codex"}
{"index": 10, "question": "Show a minimal example snippet for MCP Server Integration: JSON-RPC config and SDK hooks.", "answer": "基于本地 SDK 源码MCP 通过 `mcp_servers` 传入并用 `hooks` 处理工具调用(来源:本地 SDK 源码)。最小示例: \n```python\nimport asyncio\nfrom copilot import CopilotClient\n\nasync def main():\n client = CopilotClient()\n await client.start()\n\n session = await client.create_session({\n \"model\": \"gpt-5\",\n \"mcp_servers\": {\n \"brave\": {\n \"type\": \"local\",\n \"command\": \"/path/to/brave-mcp\",\n \"args\": [],\n \"env\": {\"BRAVE_API_KEY\": \"YOUR_KEY\"},\n \"tools\": [\"*\"],\n }\n },\n \"hooks\": {\n \"on_pre_tool_use\": lambda input, _: {\"additionalContext\": \"MCP tool call\"},\n },\n \"on_permission_request\": lambda req, _: {\"kind\": \"approved\"},\n })\n\n await session.send_and_wait({\"prompt\": \"用 Brave Search 搜索 Copilot SDK\"})\n await client.stop()\n\nasyncio.run(main())\n```\n\n建议/落地:固定 `on_permission_request`kind=\"mcp\")并限制 `tools` 白名单。", "model": "gpt-5.2-codex"}
{"index": 11, "question": "List recommended configuration defaults for MCP Server Integration: JSON-RPC config and SDK hooks.", "answer": "推荐默认值MCP JSONRPC 配置,来源:本地 SDK 源码local/stdio 服务器用 `type=\"local\"/\"stdio\"`(远程用 `http`/`sse`)、`tools` 白名单(无则 `\"*\"`)、`timeout=30000` 毫秒、`env` 仅放密钥、`cwd` 指向受控目录。 \nHooks 默认:`on_pre_tool_use` 追加上下文并可设 `permissionDecision`ask/allow、必要时改参`on_post_tool_use` 记录结果,`on_error_occurred` 设置 retry/skip。 \n建议/落地:非交互场景配置 `on_permission_request` 仅放行 `kind=\"mcp\"`,否则拒绝;工具名不确定时先不设 `available_tools`,避免误过滤。", "model": "gpt-5.2-codex"}
{"index": 12, "question": "How does MCP Server Integration: JSON-RPC config and SDK hooks relate to building a custom Agent?", "answer": "SDK 源码中,自定义 Agent 通过 `SessionConfig.custom_agents` 定义,`CustomAgentConfig` 支持 `mcp_servers`因此“Agent 绑定 MCP”就是在 agent config 里声明 `mcp_servers`,底层在 `create_session` 时转成 JSONRPC 的 `mcpServers`(来源:本地 SDK 源码 copilot/types.py、copilot/client.py。 \nHooks 是会话级拦截器(`on_pre_tool_use`/`on_post_tool_use` 等),对 MCP 工具同样生效;权限确认需 `on_permission_request` 审批 `kind=\"mcp\"`。 \n建议/落地pipe 中为目标 Agent 绑定 MCP + 审批钩子,示例: \n```python\nsession = await client.create_session({\n \"model\": \"gpt-5\",\n \"custom_agents\": [{\n \"name\": \"search\",\n \"prompt\": \"Use MCP for search.\",\n \"mcp_servers\": {\n \"brave\": {\n \"type\": \"local\",\n \"command\": \"/path/to/brave-mcp\",\n \"args\": [],\n \"env\": {\"BRAVE_API_KEY\": \"YOUR_KEY\"},\n \"tools\": [\"*\"],\n }\n },\n }],\n \"hooks\": {\"on_pre_tool_use\": lambda i, _: {\"additionalContext\": \"mcp\"}},\n \"on_permission_request\": lambda r, _: {\"kind\": \"approved\"},\n})\n```", "model": "gpt-5.2-codex"}
{"index": 13, "question": "Give a concise overview of Agent Manifests: Defining capabilities and permissions programmatically.", "answer": "SDK 没有独立的 Agent Manifest 文件,等价做法是用 `SessionConfig.custom_agents` 描述 agent并用 `tools`/`available_tools`/`excluded_tools` 限定能力。 \n权限通过 `on_permission_request` 与 `hooks`(如 `on_pre_tool_use`)在会话级控制,且 `custom_agents.tools` 只能写工具名并需在 `SessionConfig.tools` 注册。 \n建议/落地pipe 启动时加载 manifest→注入 `custom_agents` + `available_tools` + `on_permission_request` 固化权限边界。", "model": "gpt-5.2-codex"}
{"index": 14, "question": "Provide best practices and common pitfalls for Agent Manifests: Defining capabilities and permissions programmatically.", "answer": "SDK 没有独立 Manifest 文件,等价于 `SessionConfig.custom_agents` + `tools/available_tools/excluded_tools` + `on_permission_request` + `hooks` 来定义能力与权限(来源:本地 SDK 源码)。 \n最佳实践`custom_agents.tools` 只写已在 `SessionConfig.tools` 注册的名称,配合 `available_tools` 白名单与 `on_permission_request` 精准放行,必要时用 `hooks.on_pre_tool_use` 做参数校验/审计,`system_message` 选 append 以保留 guardrails。 \n常见坑工具未注册或名称不一致、缺少 `on_permission_request` 导致默认拒绝、误用 `system_message` replace 去掉安全限制、期待 agent 自动被选中或跨实例不持久 `session_id`;建议/落地pipe 启动时加载 manifest→注入 `custom_agents/available_tools/on_permission_request` 并持久化 `session_id`。", "model": "gpt-5.2-codex"}
{"index": 15, "question": "Show a minimal example snippet for Agent Manifests: Defining capabilities and permissions programmatically.", "answer": "最小示例(本地 SDK 源码:`custom_agents` + `tools` + `available_tools` + `on_permission_request` \n```python\nimport asyncio\nfrom pydantic import BaseModel, Field\nfrom copilot import CopilotClient, define_tool\n\nclass PingParams(BaseModel):\n text: str = Field(description=\"text to echo\")\n\n@define_tool(name=\"priv_ping\", description=\"Privileged echo\")\ndef priv_ping(params: PingParams) -> str:\n return f\"ok: {params.text}\"\n\nasync def main():\n client = CopilotClient()\n await client.start()\n\n agent_manifest = {\n \"name\": \"priv-agent\",\n \"prompt\": \"Only use priv_ping for actions.\",\n \"tools\": [\"priv_ping\"],\n \"infer\": True,\n }\n\n session = await client.create_session({\n \"model\": \"gpt-5\",\n \"tools\": [priv_ping],\n \"custom_agents\": [agent_manifest],\n \"available_tools\": [\"priv_ping\"],\n \"on_permission_request\": lambda req, _: {\"kind\": \"approved\"},\n })\n\n await session.send_and_wait({\"prompt\": \"调用 priv_ping\"})\n await client.stop()\n\nasyncio.run(main())\n```\n\n建议/落地:保持工具名一致并白名单化,非交互务必提供 `on_permission_request`。", "model": "gpt-5.2-codex"}
{"index": 16, "question": "List recommended configuration defaults for Agent Manifests: Defining capabilities and permissions programmatically.", "answer": "基于本地 SDK 源码copilot/types.py / copilot/client.py推荐默认值如下 \n- custom_agentsname 必填、prompt 必填、tools 仅名称、infer=Truedisplay_name/description 可选 \n- available_tools=白名单、excluded_tools=空、system_message=append \n- on_permission_request除明确允许的 kind 其余拒绝hooks.on_pre_tool_use 做审计/参数校验(其余 hooks 按需) \n建议/落地pipe 启动时加载 manifest→注册 tools + available_tools + on_permission_request并持久化 session_id。", "model": "gpt-5.2-codex"}
{"index": 17, "question": "How does Agent Manifests: Defining capabilities and permissions programmatically relate to building a custom Agent?", "answer": "“Agent Manifest”在 SDK 里就是 `SessionConfig.custom_agents` 的配置对象,用它定义自定义 Agent 的 `name/prompt/tools/mcp_servers/infer`,并在同一 session 里用 `tools` 注册实际工具、用 `available_tools/excluded_tools` 限定能力(来源:本地 SDK 源码)。 \n权限不在 manifest 内,而是由会话级 `on_permission_request` 和 hooks如 `on_pre_tool_use`)统一控制,且对所有 Agent 生效。 \n建议/落地pipe 启动时把 manifest 转成 `custom_agents` 并同步注册工具 + 白名单,再配置 `on_permission_request` 作为权限策略。", "model": "gpt-5.2-codex"}
{"index": 18, "question": "Give a concise overview of Headless Auth: Device Code Flow and credential persistence.", "answer": "SDK 仅提供 `CopilotClient` 的 `github_token/use_logged_in_user` 与 `get_auth_status()`,没有内置 deviceflow API源码copilot/client.py、copilot/types.py。 \nHeadless Device Flow 需走 GitHub OAuth Device Flow HTTP 端点获取 tokenhttps://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow将 token 持久化(权限 600并通过 `CopilotClient({\"github_token\": token})` 注入,且与 `cli_url` 互斥。 \n建议/落地:启动时读 token 文件,不存在则执行 device flow 并保存,再启动客户端。\n\n```python\ntoken = load_token() or device_flow() # 调用 device code + access_token 端点\nsave_token(token) # chmod 600\nclient = CopilotClient({\"github_token\": token[\"access_token\"]})\nawait client.start()\n```", "model": "gpt-5.2-codex"}
{"index": 19, "question": "Provide best practices and common pitfalls for Headless Auth: Device Code Flow and credential persistence.", "answer": "SDK 不内置 device flow只能用 GitHub OAuth Device Flow HTTP 换取 token 并通过 CopilotClient({github_token}) 注入,且与 cli_url 互斥源码copilot/client.py、copilot/types.py。 \n最佳实践/坑:- token 文件 chmod 600 或 OS keychain- 记录到期与刷新策略,启动时用 get_auth_status 验证;- 轮询需处理 authorization_pending/slow_down- 未持久化、use_logged_in_user 与 github_token 同时使用或与 cli_url 混用会失败端点说明https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow。 \n建议/落地pipe 启动时读 token 文件,不存在则走 device flow 并保存,失败时回退重新授权。", "model": "gpt-5.2-codex"}
{"index": 20, "question": "Show a minimal example snippet for Headless Auth: Device Code Flow and credential persistence.", "answer": "最小示例:用 GitHub Device Flow端点说明见 https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow拿 token持久化后注入 CopilotClient({github_token}). \n```python\nimport asyncio\nimport json\nimport os\nimport time\nimport urllib.parse\nimport urllib.request\nfrom pathlib import Path\nfrom copilot import CopilotClient\n\nCLIENT_ID = \"YOUR_OAUTH_APP_CLIENT_ID\"\nTOKEN_PATH = Path.home() / \".config\" / \"myapp\" / \"copilot_token.json\"\n\ndef post(url, data):\n body = urllib.parse.urlencode(data).encode()\n req = urllib.request.Request(url, data=body, headers={\"Accept\": \"application/json\"})\n with urllib.request.urlopen(req) as resp:\n return json.loads(resp.read().decode())\n\ndef device_flow():\n code = post(\"https://github.com/login/device/code\", {\"client_id\": CLIENT_ID, \"scope\": \"read:user\"})\n print(f\"Open {code['verification_uri']} and enter {code['user_code']}\")\n interval = int(code.get(\"interval\", 5))\n while True:\n tok = post(\n \"https://github.com/login/oauth/access_token\",\n {\n \"client_id\": CLIENT_ID,\n \"device_code\": code[\"device_code\"],\n \"grant_type\": \"urn:ietf:params:oauth:grant-type:device_code\",\n },\n )\n if \"access_token\" in tok:\n return tok\n if tok.get(\"error\") == \"slow_down\":\n interval += 5\n elif tok.get(\"error\") != \"authorization_pending\":\n raise RuntimeError(tok)\n time.sleep(interval)\n\ndef load_token():\n return json.loads(TOKEN_PATH.read_text()).get(\"access_token\") if TOKEN_PATH.exists() else None\n\ndef save_token(tok):\n TOKEN_PATH.parent.mkdir(parents=True, exist_ok=True)\n TOKEN_PATH.write_text(json.dumps(tok))\n os.chmod(TOKEN_PATH, 0o600)\n\nasync def main():\n access_token = load_token()\n if not access_token:\n tok = device_flow()\n save_token(tok)\n access_token = tok[\"access_token\"]\n\n client = CopilotClient({\"github_token\": access_token})\n await client.start()\n # ... create_session / send ...\n await client.stop()\n\nasyncio.run(main())\n```\n\n建议/落地 \n- 启动先读 token 文件;缺失再 device flow \n- token 文件权限 600 \n- 失效则重新授权", "model": "gpt-5.2-codex"}
{"index": 21, "question": "List recommended configuration defaults for Headless Auth: Device Code Flow and credential persistence.", "answer": "基于本地 SDK 源码(`github_token` 与 `cli_url` 互斥)与 GitHub Device Flow 文档https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow推荐默认值 \n- Token 持久化:`~/.config/<app>/copilot_token.json` 或系统密钥链,文件权限 600 \n- Client 选项:仅 `github_token``use_logged_in_user` 留空/False禁用 `cli_url` \n- 轮询:使用 device code 返回的 `interval`(缺省 5s处理 `authorization_pending`/`slow_down` \n- 启动校验:`get_auth_status()` 失败即重新授权 \n\n建议/落地:启动先读 token 文件 → 无则 device flow → 保存 → `client.start()`", "model": "gpt-5.2-codex"}
{"index": 22, "question": "How does Headless Auth: Device Code Flow and credential persistence relate to building a custom Agent?", "answer": "Headless Auth 只负责拿到并持久化 `github_token`,让 `CopilotClient` 能在无交互环境启动会话并加载 `custom_agents`;因此它是自定义 Agent 能稳定运行的前置条件,而不是 Agent 定义本身(来源:本地 SDK 源码)。 \n建议/落地:在无状态后端启动时读取 token 文件(不存在就走 device flow→ `CopilotClient({\"github_token\": token})` → `create_session(custom_agents=...)`。", "model": "gpt-5.2-codex"}
{"index": 23, "question": "Give a concise overview of Session Replay vs Resume: Handling stateless frontend history.", "answer": "基于本地 SDK 源码copilot/client.py、copilot/session.pyResume 是 `resume_session(session_id)` 保留会话状态Replay 只能由应用重放历史 prompt。优缺点Resume 省 tokens/保工具与 compaction 状态但依赖持久化 session_idReplay 无状态易扩展但成本高且易丢副作用控制。建议/落地:无状态前端请保存 session_id 并优先 Resume失效时回放最近 N 轮。", "model": "gpt-5.2-codex"}
{"index": 24, "question": "Provide best practices and common pitfalls for Session Replay vs Resume: Handling stateless frontend history.", "answer": "最佳实践:在无状态前端每次请求携带 session_id 并优先 `resume_session`,同时恢复 tools/hooks/on_permission_request/system_message 以保持行为一致(来源:本地 SDK 源码)。 \n常见坑Replay 会重复执行工具且耗 tokensResume 未重新注册工具或权限处理器会出现 tool not supported/默认拒绝;会话失效未回退。 \n建议/落地:持久化 chat_id→session_idresume 失败时仅回放最近 N 轮并禁用/幂等化有副作用的工具。", "model": "gpt-5.2-codex"}
{"index": 25, "question": "Show a minimal example snippet for Session Replay vs Resume: Handling stateless frontend history.", "answer": "来源:本地 SDK 源码copilot/client.py, copilot/session.py最小示例如下优先 Resume失败则 Replay。 \n```python\nimport asyncio\nfrom copilot import CopilotClient\n\nasync def handle_request(session_id: str | None, history: list[str], latest: str):\n client = CopilotClient()\n await client.start()\n\n try:\n # Resume: 复用会话状态\n if not session_id:\n raise RuntimeError(\"no session_id\")\n session = await client.resume_session(session_id)\n except Exception:\n # Replay: 逐条重放历史\n session = await client.create_session({\"model\": \"gpt-5\"})\n session_id = session.session_id\n for prompt in history:\n await session.send_and_wait({\"prompt\": prompt})\n\n response = await session.send_and_wait({\"prompt\": latest})\n await client.stop()\n return response.data.content, session_id\n\n# asyncio.run(handle_request(session_id, history, latest_prompt))\n```\n建议/落地:持久化 session_idresume 失败时仅回放最近 N 轮。", "model": "gpt-5.2-codex"}
{"index": 26, "question": "List recommended configuration defaults for Session Replay vs Resume: Handling stateless frontend history.", "answer": "基于本地 SDK 源码推荐默认配置如下Resume / Replay \n- Resume持久化 session_id`resume_session({...})` 传回 tools、hooks、on_permission_request、working_directory、streaming、provider、mcp_servers、custom_agents、skill_directories、disabled_skills`disable_resume` 保持默认 False \n- Replay`create_session({...})` 传回 model、system_message、tools、available_tools/excluded_tools、hooks、on_permission_request、working_directory、provider、mcp_servers、custom_agents、infinite_sessions(默认启用);仅回放最近 N 轮;默认拒绝 write/shell 或用只读白名单", "model": "gpt-5.2-codex"}
{"index": 27, "question": "How does Session Replay vs Resume: Handling stateless frontend history relate to building a custom Agent?", "answer": "Resume 会保留 session_id 关联的 custom_agents/工具状态Replay 需要每次 create_session 时重新注入 custom_agents + tools否则 persona/能力会丢失(来源:本地 SDK 源码。对于无状态前端Resume 更可靠且省 token但要持久化 session_id 并在 resume 时重新注册 tools/hooksReplay 则需回放历史并保证相同 custom_agents 配置。建议/落地pipe 优先 resume_session失败再 replay 最近 N 轮并重建 custom_agents/tools/权限策略。", "model": "gpt-5.2-codex"}
{"index": 28, "question": "Give a concise overview of Advanced Session Hooks: Intercepting and modifying user prompts.", "answer": "拦截/修改用户提示要用 `SessionHooks.on_user_prompt_submitted`(不是 `session.on` 事件订阅),`input.prompt` 可改为 `output.modifiedPrompt` 并附加 `additionalContext`/`suppressOutput`,且需在 `create_session`/`resume_session` 传入 `hooks` 才会触发 `hooks.invoke`(来源:本地 SDK 源码 copilot/types.py、copilot/session.py、copilot/client.py。 \n```python\nfrom copilot import CopilotClient\n\nasync def on_user_prompt_submitted(input, _ctx):\n return {\"modifiedPrompt\": input[\"prompt\"].strip(), \"additionalContext\": \"normalize whitespace\"}\n\nsession = await client.create_session({\n \"model\": \"gpt-5\",\n \"hooks\": {\"on_user_prompt_submitted\": on_user_prompt_submitted},\n})\n```\n建议/落地:在 pipe 入口统一做 prompt 清洗/拼接上下文,并记录修改前后用于审计。", "model": "gpt-5.2-codex"}
{"index": 29, "question": "Provide best practices and common pitfalls for Advanced Session Hooks: Intercepting and modifying user prompts.", "answer": "最佳实践:在 create_session/resume_session 传入 hooks.on_user_prompt_submitted返回 {modifiedPrompt, additionalContext, suppressOutput},修改保持幂等、最小化、可审计(来源:本地 SDK 源码 copilot/types.py、copilot/client.py、copilot/session.py。 \n常见坑忘记传 hooks 或 resume 时未重新传、handler 抛异常会被吞并返回 None、返回非 dict 或过度改写提示导致无效/偏航。 \n建议/落地:在 pipe 入口统一注册该 hook 并记录修改前后,保持 system_message 为 append。", "model": "gpt-5.2-codex"}
{"index": 30, "question": "Show a minimal example snippet for Advanced Session Hooks: Intercepting and modifying user prompts.", "answer": "最小示例(来源:本地 SDK 源码): \n```python\nimport asyncio\nfrom copilot import CopilotClient\n\nasync def on_user_prompt_submitted(hook_input, _ctx):\n return {\n \"modifiedPrompt\": hook_input[\"prompt\"].strip(),\n \"additionalContext\": \"normalized\",\n }\n\nasync def main():\n client = CopilotClient()\n await client.start()\n session = await client.create_session({\n \"model\": \"gpt-5\",\n \"hooks\": {\"on_user_prompt_submitted\": on_user_prompt_submitted},\n })\n await session.send_and_wait({\"prompt\": \" hello \"})\n await client.stop()\n\nasyncio.run(main())\n```\n建议/落地:在 pipe 入口统一注册该 hook并记录修改前后以便审计。", "model": "gpt-5.2-codex"}
{"index": 31, "question": "List recommended configuration defaults for Advanced Session Hooks: Intercepting and modifying user prompts.", "answer": "- 推荐默认值(来源:本地 SDK 源码 copilot/types.py`hooks={\"on_user_prompt_submitted\": handler}`handler 默认返回 `{\"modifiedPrompt\": input[\"prompt\"], \"additionalContext\": \"\", \"suppressOutput\": False}` 或 `None`(不改写)。 \n- `create_session`/`resume_session` 都要传同一 hooks`system_message` 保持 append 以保留 guardrails。 \n- 建议/落地pipe 入口统一注册该 hook 并记录原/改提示用于审计。", "model": "gpt-5.2-codex"}
{"index": 32, "question": "How does Advanced Session Hooks: Intercepting and modifying user prompts relate to building a custom Agent?", "answer": "Hooks 是会话级拦截器,对所有自定义 Agent 都生效;因此它是定义 Agent 行为边界和输入规范化的“外层控制”,而不是 Agent Manifest 本身(来源:本地 SDK 源码 copilot/types.py、copilot/session.py。 \n在自定义 Agent 场景下,`on_user_prompt_submitted` 可统一清洗/增强用户指令,再由 custom_agents 的 prompt 决定具体风格。 \n建议/落地pipe 启动时同时注入 custom_agents + hooks确保 resume 时同样注册 hooks。", "model": "gpt-5.2-codex"}
{"index": 33, "question": "Give a concise overview of Workspace Virtualization: Handling CWD for remote/virtual files.", "answer": "SDK 只有 `SessionConfig.working_directory` 来约束 CWD`attachments` 也要求真实路径,没有“虚拟工作区/FS provider”协议来源本地 SDK 源码 copilot/types.py、copilot/client.py。要处理远程/虚拟文件,需用自定义工具或 MCP 服务器实现 VFS 读写,或把内容落地到 working_directory 下的临时文件再交给模型。建议/落地pipe 固定 working_directory 映射到会话工作区,统一走 vfs_* 工具/MCP且在 on_permission_request 限制 write/shell。", "model": "gpt-5.2-codex"}
{"index": 34, "question": "Provide best practices and common pitfalls for Workspace Virtualization: Handling CWD for remote/virtual files.", "answer": "SDK 没有虚拟工作区/FS provider仅支持 `SessionConfig.working_directory` 与真实路径 `attachments`(来源:本地 SDK 源码),因此远程/虚拟文件需落地或用自定义工具/MCP 提供内容。 \n最佳实践固定 working_directory 到可控目录/会话 workspaceVFS 工具分块返回,配合 `available_tools`/`on_permission_request` 限权;常见坑:直接传虚拟路径/URL、working_directory 不存在、resume 未重注册工具/权限、一次性返回过大内容导致上下文截断。 \n建议/落地pipe 入口做 remote→临时文件或 vfs_* 映射,并持久化路径索引。", "model": "gpt-5.2-codex"}
{"index": 35, "question": "Show a minimal example snippet for Workspace Virtualization: Handling CWD for remote/virtual files.", "answer": "SDK 仅提供 `SessionConfig.working_directory` 与真实路径附件,没有虚拟 FS/provider来源本地 SDK 源码 copilot/types.py。最小做法是把远程内容落地到 CWD 下再作为附件传入: \n```python\nimport asyncio\nimport pathlib\nfrom copilot import CopilotClient\n\nasync def main():\n client = CopilotClient()\n await client.start()\n\n workdir = \"/tmp/owui\"\n pathlib.Path(workdir).mkdir(parents=True, exist_ok=True)\n remote_content = \"...\"\n path = f\"{workdir}/remote.txt\"\n pathlib.Path(path).write_text(remote_content)\n\n session = await client.create_session({\"model\": \"gpt-5\", \"working_directory\": workdir})\n await session.send_and_wait({\n \"prompt\": \"Summarize remote.txt\",\n \"attachments\": [{\"type\": \"file\", \"path\": path}],\n })\n await client.stop()\n\nasyncio.run(main())\n```\n建议/落地pipe 入口固定 working_directory落地临时文件后立即处理并用 `on_permission_request` 限制 write/shell。", "model": "gpt-5.2-codex"}
{"index": 36, "question": "List recommended configuration defaults for Workspace Virtualization: Handling CWD for remote/virtual files.", "answer": "推荐默认值(来源:本地 SDK 源码 copilot/types.py \n- `working_directory`: 指向可控的会话目录(如 session.workspace_path 或 /tmp/<app>/<session_id> \n- `available_tools`: 只允许 vfs_* 或只读工具,避免任意 shell/write \n- `on_permission_request`: 默认拒绝 write/shell仅放行 read/mcp \n- `attachments`: 仅真实路径文件;远程内容先落地 \n\n建议/落地pipe 入口统一映射 remote→临时文件或 VFS 工具,并持久化映射表。", "model": "gpt-5.2-codex"}
{"index": 37, "question": "How does Workspace Virtualization: Handling CWD for remote/virtual files relate to building a custom Agent?", "answer": "SDK 没有虚拟文件系统协议custom agent 只能依赖 `working_directory` 和真实路径附件,因此处理远程/虚拟文件需通过自定义工具/MCP 并在 agent prompt/available_tools 中强制使用(来源:本地 SDK 源码 copilot/types.py、copilot/client.py。 \n这使“工作区虚拟化”成为 agent 能力与权限设计的一部分,需要 `available_tools`/`on_permission_request`/hooks 来限定访问范围。 \n建议/落地pipe 中固定 working_directory 到会话目录,注入 vfs_* 工具白名单并在 prompt 明确使用 VFS。", "model": "gpt-5.2-codex"}
{"index": 38, "question": "Give a concise overview of Error Recovery: Handling session disconnects and re-auth.", "answer": "本地 SDK 源码中没有显式“重连”API断线通常表现为请求异常或 `SessionEventType.SESSION_ERROR`,需要 `stop()/force_stop()` 后重新 `start()` 并 `resume_session`(失败则 replay来源本地 SDK 源码)。 \n认证可用 `get_auth_status()` 检测,未认证时重新创建 `CopilotClient({\"github_token\": token})`;若 `cli_url` 连接外部服务器则认证由外部处理(来源:本地 SDK 源码)。\n\n```python\nimport asyncio\nfrom copilot import CopilotClient\nfrom copilot.generated.session_events import SessionEventType\n\nasync def safe_send(session_id, prompt, token):\n client = CopilotClient({\"github_token\": token})\n await client.start()\n try:\n session = await (client.resume_session(session_id) if session_id else client.create_session({\"model\": \"gpt-5\"}))\n session.on(lambda ev: ev.type == SessionEventType.SESSION_ERROR and print(ev.data.message))\n resp = await session.send_and_wait({\"prompt\": prompt})\n return resp, session.session_id\n except Exception:\n await client.stop()\n client = CopilotClient({\"github_token\": token})\n await client.start()\n session = await client.create_session({\"model\": \"gpt-5\"})\n resp = await session.send_and_wait({\"prompt\": prompt})\n return resp, session.session_id\n finally:\n await client.stop()\n```\n\n建议/落地:持久化 session_id 与 token优先 resume失败再回放最近 N 轮。", "model": "gpt-5.2-codex"}
{"index": 39, "question": "Provide best practices and common pitfalls for Error Recovery: Handling session disconnects and re-auth.", "answer": "最佳实践(基于本地 SDK 源码):监听 `SESSION_ERROR`/捕获请求异常后 `stop()/force_stop()`→重新 `start()`→`resume_session`(失败再 replay启动时用 `get_auth_status()` 校验并在失效时重建 `CopilotClient({\"github_token\": ...})`,同时在 resume 时重新注册 tools/hooks/权限处理器。 \n常见坑把 `cli_url` 与 `github_token` 混用、忘记重注册工具导致 `tool not supported`、缺少 `on_permission_request` 导致默认拒绝、以及不回退到 replay 造成会话中断。 \n建议/落地:无状态后端持久化 `session_id+token`、实现幂等的重试/回放策略并记录错误上下文用于告警。", "model": "gpt-5.2-codex"}
{"index": 40, "question": "Show a minimal example snippet for Error Recovery: Handling session disconnects and re-auth.", "answer": "最小示例(本地 SDK 源码copilot/client.py、copilot/session.py \n```python\nimport asyncio\nfrom copilot import CopilotClient\n\nasync def send_with_recovery(session_id, prompt, token):\n client = CopilotClient({\"github_token\": token})\n await client.start()\n try:\n session = await (client.resume_session(session_id) if session_id else client.create_session({\"model\": \"gpt-5\"}))\n resp = await session.send_and_wait({\"prompt\": prompt})\n return resp.data.content, session.session_id\n except Exception:\n await client.stop()\n client = CopilotClient({\"github_token\": token})\n await client.start()\n session = await client.create_session({\"model\": \"gpt-5\"})\n resp = await session.send_and_wait({\"prompt\": prompt})\n return resp.data.content, session.session_id\n finally:\n await client.stop()\n```\n建议/落地:持久化 token+session_idresume 失败再重建并回放必要历史。", "model": "gpt-5.2-codex"}
{"index": 41, "question": "List recommended configuration defaults for Error Recovery: Handling session disconnects and re-auth.", "answer": "推荐默认值(字段来源:本地 SDK 源码CopilotClientOptions—auto_start=True、log_level='info'、use_stdio=True非 cli_url、github_token=持久化 tokenSessionConfig/ResumeSessionConfig—hooks.on_error_occurred=handler、on_permission_request=非交互审批、working_directory 固定、mcp_servers/custom_agents/streaming 与首次一致、可固定 config_dir 并持久化 session_id。 \n常见坑cli_url 与 github_token 互斥、resume 时未重传 tools/hooks 导致 tool not supported 或默认拒绝。 \n建议/落地pipe 保存 token+session_id断线后先 resume_session失败再回放最近 N 轮。", "model": "gpt-5.2-codex"}
{"index": 42, "question": "How does Error Recovery: Handling session disconnects and re-auth relate to building a custom Agent?", "answer": "错误恢复与自定义 Agent 强相关Agent 定义与工具/权限处理器都是 session 级配置,断线或重认证后必须重建客户端并重新注入 `custom_agents`、`tools`、`hooks`、`on_permission_request`,否则能力/人格会丢失(来源:本地 SDK 源码)。 \n建议/落地:持久化 session_id+token重连优先 `resume_session` 并重新注册上述配置,失败再回放最近 N 轮。", "model": "gpt-5.2-codex"}
{"index": 43, "question": "Give a concise overview of Confirmation Events: programmatic handling of 'confirmation_required'.", "answer": "SDK 源码里没有独立的 `confirmation_required` 事件;权限确认是 JSONRPC `permission.request`,通过 `SessionConfig.on_permission_request` 回调处理(缺失或异常会默认拒绝)。 \n`session.on()` 只能用于审计/关联工具事件(如 `tool.user_requested`、`tool.execution_start`),不能替代确认逻辑。", "model": "gpt-5.2-codex"}
{"index": 44, "question": "Provide best practices and common pitfalls for Confirmation Events: programmatic handling of 'confirmation_required'.", "answer": "SDK 源码未定义 `confirmation_required` 事件,确认流程必须用 `SessionConfig.on_permission_request` 处理,缺失或异常会默认拒绝(来源:本地 SDK 源码)。 \n最佳实践/坑: \n- ✅ 对 `kind` 建立白名单策略(如仅允许 `read/mcp`),记录决策并用 `session.on` 仅做审计关联 `tool.user_requested` \n- ✅ `resume_session` 时重新注册 `on_permission_request`/hooks \n- ❌ 误以为 `session.on()` 可完成确认、返回非规范 dict 或抛异常导致全部拒绝", "model": "gpt-5.2-codex"}
{"index": 45, "question": "Show a minimal example snippet for Confirmation Events: programmatic handling of 'confirmation_required'.", "answer": "SDK 里没有 `confirmation_required` 事件,需用 `SessionConfig.on_permission_request` 处理权限确认。 \n最小示例 \n```python\nfrom copilot import CopilotClient\n\nclient = CopilotClient()\nawait client.start()\n\nsession = await client.create_session({\n \"model\": \"gpt-5\",\n \"on_permission_request\": lambda req, _: {\"kind\": \"approved\"},\n})\n```\n建议/落地:在 pipe 中统一注册 on_permission_request并在 resume_session 时重复注册。", "model": "gpt-5.2-codex"}
{"index": 46, "question": "List recommended configuration defaults for Confirmation Events: programmatic handling of 'confirmation_required'.", "answer": "推荐默认值SDK 无 confirmation_required 事件,改用 on_permission_request来源本地 SDK 源码):- 对 kind 建白名单(常见只放行 read/mcp- 未命中默认返回 denied-by-rules 并给 rules - resume_session 时重注册 on_permission_request/hooks。 \n建议/落地pipe 启动即注入 on_permission_request 并记录审批日志。", "model": "gpt-5.2-codex"}
{"index": 47, "question": "How does Confirmation Events: programmatic handling of 'confirmation_required' relate to building a custom Agent?", "answer": "SDK 没有 `confirmation_required` 事件,权限确认由 `create_session`/`resume_session` 的 `on_permission_request` 处理,且对同一 session 的所有 custom agent 生效(来源:本地 SDK 源码)。 \n建议/落地:在 pipe 启动时注入 `on_permission_request` 白名单策略并在 resume 时重注册,以约束高权限工具。", "model": "gpt-5.2-codex"}
{"index": 48, "question": "Give a concise overview of Skills: Conflict resolution and precedence defaults.", "answer": "在本地 SDK 源码中Skills 只通过 SessionConfig 的 `skill_directories` 与 `disabled_skills` 暴露,未定义冲突解析/优先级规则,因此行为取决于 CLI 运行时实现(来源:本地 SDK 源码 copilot/types.py。 \n为可控性建议确保技能名唯一、仅配置单一目录或由你自行按顺序拼接目录并用 `disabled_skills` 明确屏蔽同名/不应启用的技能。 \n建议/落地:在无状态后端每次 `create_session`/`resume_session` 时显式传入同一份 `skill_directories` 与 `disabled_skills`,避免因默认加载顺序差异导致行为漂移。", "model": "gpt-5.2-codex"}
{"index": 49, "question": "Provide best practices and common pitfalls for Skills: Conflict resolution and precedence defaults.", "answer": "SDK 源码仅暴露 SessionConfig/ResumeSessionConfig 的 `skill_directories` 和 `disabled_skills`,未定义冲突解析或优先级,行为由 CLI 侧决定(来源:本地 SDK 源码 copilot/types.py。 \n最佳实践技能名全局唯一、只配置单一目录或显式排序并用 `disabled_skills` 禁用冲突项;常见坑是依赖默认加载顺序或 resume 未重传这两个字段导致行为漂移。 \n建议/落地:在 pipe 启动与 resume 时统一注入同一份 `skill_directories`/`disabled_skills` 并记录变更。", "model": "gpt-5.2-codex"}
{"index": 50, "question": "Show a minimal example snippet for Skills: Conflict resolution and precedence defaults.", "answer": "SDK 里技能只通过 `skill_directories` 与 `disabled_skills` 配置,冲突/优先级未在源码定义,需自行避免同名。 \n最小示例 \n```python\nsession = await client.create_session({\n \"model\": \"gpt-5\",\n \"skill_directories\": [\"/opt/skills\"],\n \"disabled_skills\": [\"legacy_skill\"],\n})\n``` \n建议技能名唯一并在 `resume_session` 时重传这两项。", "model": "gpt-5.2-codex"}
{"index": 51, "question": "List recommended configuration defaults for Skills: Conflict resolution and precedence defaults.", "answer": "推荐默认值(基于 SDK 仅暴露 `skill_directories`/`disabled_skills` \n- `skill_directories`: 明确设置为单一目录(或你自己控制顺序的列表),不要依赖 CLI 默认搜索顺序 \n- `disabled_skills`: 默认空列表;如存在同名/旧技能则显式禁用 \n- Resume/Replay始终复用同一份配置以避免“隐式优先级”漂移", "model": "gpt-5.2-codex"}
{"index": 52, "question": "How does Skills: Conflict resolution and precedence defaults relate to building a custom Agent?", "answer": "Skills 是会话级配置(`skill_directories`/`disabled_skills`),会影响所有自定义 Agent 的可用能力与提示行为SDK 不定义冲突优先级,因此需要你在构建自定义 Agent 时确保技能命名唯一并显式禁用冲突项(来源:本地 SDK 源码)。 \n建议/落地:为 custom agent 固定同一份 skills 配置,并在 resume 时重复传入,避免行为漂移。", "model": "gpt-5.2-codex"}
{"index": 53, "question": "Give a concise overview of Debugging: Tracing JSON-RPC traffic in the SDK.", "answer": "本地 SDK 源码里 JsonRpcClient 没有内置 JSONRPC trace只能通过 `CopilotClient({\"log_level\":\"debug\"})` 打开 CLI 侧日志(非原始包)(来源:本地 SDK 源码)。 \n要抓实际 JSONRPC 流量,可 monkeypatch `JsonRpcClient._send_message`/`_read_message` 或包装 stdio 进行 tee最小示例如下。 \n建议/落地:在 pipe 中开启 patch 并对日志做脱敏与采样。 \n```python\nfrom copilot.jsonrpc import JsonRpcClient\n\norig_send = JsonRpcClient._send_message\nasync def _send(self, message):\n print(\">>\", message)\n return await orig_send(self, message)\nJsonRpcClient._send_message = _send\n\norig_read = JsonRpcClient._read_message\ndef _read(self):\n msg = orig_read(self)\n if msg:\n print(\"<<\", msg)\n return msg\nJsonRpcClient._read_message = _read\n```", "model": "gpt-5.2-codex"}
{"index": 54, "question": "Provide best practices and common pitfalls for Debugging: Tracing JSON-RPC traffic in the SDK.", "answer": "源码里 JsonRpcClient 没有内置 trace只能用 `CopilotClient({\"log_level\":\"debug\"})` 查看 CLI 侧日志或 monkeypatch `JsonRpcClient._send_message/_read_message` 做 tee来源本地 SDK 源码)。 \n最佳实践仅在调试启用、对 github_token/API key/内容脱敏、采样/限流、只读不改消息并注意线程安全;常见坑:记录完整 prompt/工具参数导致泄露,或同步打印阻塞 IO 造成超时/断连。", "model": "gpt-5.2-codex"}
{"index": 55, "question": "Show a minimal example snippet for Debugging: Tracing JSON-RPC traffic in the SDK.", "answer": "最小示例monkeypatch `JsonRpcClient` 打印收发): \n```python\nfrom copilot.jsonrpc import JsonRpcClient\n\norig_send = JsonRpcClient._send_message\nasync def _send(self, msg):\n print(\">>\", msg)\n return await orig_send(self, msg)\nJsonRpcClient._send_message = _send\n\norig_read = JsonRpcClient._read_message\ndef _read(self):\n msg = orig_read(self)\n if msg:\n print(\"<<\", msg)\n return msg\nJsonRpcClient._read_message = _read\n```\n建议仅在调试启用并脱敏日志。", "model": "gpt-5.2-codex"}
{"index": 56, "question": "List recommended configuration defaults for Debugging: Tracing JSON-RPC traffic in the SDK.", "answer": "推荐默认值(基于 SDK 源码):- 生产环境关闭 trace- 调试时 `CopilotClient({\"log_level\":\"debug\"})`- 如需 JSONRPC 报文,采用临时 monkeypatch 并启用脱敏/采样;- 不修改消息内容,避免线程阻塞。 \n建议/落地pipe 中通过环境变量开关调试与采样率。", "model": "gpt-5.2-codex"}
{"index": 57, "question": "How does Debugging: Tracing JSON-RPC traffic in the SDK relate to building a custom Agent?", "answer": "JSONRPC trace 能看到 custom_agents 在 `session.create/session.resume` 的实际 payload以及后续 `tool.call`/`permission.request`/`hooks.invoke` 是否按预期触发,从而验证自定义 Agent 的能力与权限边界(来源:本地 SDK 源码)。 \n建议/落地:仅在开发环境开启并脱敏日志;用它核对 customAgents/tools/权限决策与 MCP 配置。", "model": "gpt-5.2-codex"}
{"index": 58, "question": "Give a concise overview of Billing & Policies: How seat management affects SDK features.", "answer": "- 访问与政策SDK 依赖 Copilot CLI需有效 Copilot 订阅且组织/企业未禁用 CLI否则无法使用https://raw.githubusercontent.com/github/copilot-sdk/main/README.mdhttps://raw.githubusercontent.com/github/copilot-cli/main/README.md \n- 计费与认证:每次 prompt 计入 Copilot premium requests 配额PAT 需 Copilot Requests 权限https://raw.githubusercontent.com/github/copilot-sdk/main/README.mdhttps://raw.githubusercontent.com/github/copilot-cli/main/README.md\n\n建议/落地:启动先 `get_auth_status()`,失败时提示分配席位/启用 CLI 或更新 token", "model": "gpt-5.2-codex"}
{"index": 59, "question": "Provide best practices and common pitfalls for Billing & Policies: How seat management affects SDK features.", "answer": "最佳实践:确认用户/组织席位允许 Copilot CLI/SDK 使用,启动前用 `get_auth_status()` 预检,使用 PAT 时确保具备 Copilot Requests 权限https://raw.githubusercontent.com/github/copilot-sdk/main/README.mdhttps://raw.githubusercontent.com/github/copilot-cli/main/README.md。 \n常见坑未分配席位或组织/企业策略禁用 CLI 导致 SDK 失效、PAT 权限不足或 premium requests 用尽引发认证/配额错误https://raw.githubusercontent.com/github/copilot-sdk/main/README.mdhttps://raw.githubusercontent.com/github/copilot-cli/main/README.md。 \n建议/落地:在 pipe 启动时做席位与鉴权检测并给出明确提示/自助链接,记录每次请求计费与配额告警阈值以便限流。", "model": "gpt-5.2-codex"}
{"index": 60, "question": "Show a minimal example snippet for Billing & Policies: How seat management affects SDK features.", "answer": "基于 SDK README最小示例是在启动时用 `get_auth_status()` 检查是否已分配席位/可用认证https://raw.githubusercontent.com/github/copilot-sdk/main/README.md。 \n```python\nimport asyncio\nfrom copilot import CopilotClient\n\nasync def main():\n client = CopilotClient()\n await client.start()\n\n auth = await client.get_auth_status()\n if not auth.isAuthenticated:\n raise RuntimeError(\"Copilot 未认证/未分配席位,请先登录或分配权限\")\n\n # ...正常创建 session...\n await client.stop()\n\nasyncio.run(main())\n```\n建议/落地pipe 启动先做该检查,失败时提示分配席位或更新 PAT 权限。", "model": "gpt-5.2-codex"}
{"index": 61, "question": "List recommended configuration defaults for Billing & Policies: How seat management affects SDK features.", "answer": "推荐默认值CopilotClient 用 `use_logged_in_user=True`、`auto_start=True`、`log_level=\"info\"`;如用 PAT 则用 `github_token` 注入并确保具备 Copilot Requests 权限,且启动前 `get_auth_status()` 失败即按席位/策略限制处理来源https://raw.githubusercontent.com/github/copilot-sdk/main/README.md https://raw.githubusercontent.com/github/copilot-cli/main/README.md。 \n建议/落地pipe 启动先做 auth 预检并给出续订/分配席位提示,避免会话中途才触发权限或配额错误。", "model": "gpt-5.2-codex"}
{"index": 62, "question": "How does Billing & Policies: How seat management affects SDK features relate to building a custom Agent?", "answer": "席位与组织策略决定 Copilot CLI/SDK 是否可用,因此自定义 Agent 只是同一会话上的配置,前提仍是有有效订阅与允许的策略,否则 Agent 无法运行https://raw.githubusercontent.com/github/copilot-sdk/main/README.md https://raw.githubusercontent.com/github/copilot-cli/main/README.md。 \n建议/落地:在创建自定义 Agent 前先 `get_auth_status()` 预检并对配额/政策限制做降级提示。", "model": "gpt-5.2-codex"}
{"index": 63, "question": "How to configure MCP servers in ~/.copilot/config.json for SDK usage?", "answer": "我在 CLI 文档里未看到 `~/.copilot/config.json` 的 MCP 配置结构CLI 仅提供 `/mcp add|edit|delete|show` 命令管理配置来源Copilot CLI 文档)。 \n因此建议用 CLI 写入配置,并在 SDK 中用 `config_dir` 指向该配置目录;或直接在 SDK 的 `create_session(mcp_servers=...)` 传入。 \n如果你确认存在 `~/.copilot/config.json` 的具体 schema请提供示例或路径我可按你的文件格式给出精确配置。", "model": "gpt-5.2-codex"}
{"index": 64, "question": "What CLI environment variables force 'Agent' mode vs 'Generic' mode?", "answer": "CLI 文档未提到任何“通过环境变量强制 Agent 模式/Generic 模式”的开关Copilot CLI 文档)。 \n已知与模式相关的是命令/参数,例如 `--agent` 选择自定义 agent或交互内 `/agent` 切换;但不是环境变量。 \n如果你指的是内部实验开关请提供具体变量名或版本线索我可继续追索。", "model": "gpt-5.2-codex"}
{"index": 65, "question": "Explain the 'confirmation' flow in CLI and how it maps to SDK events.", "answer": "- CLI工具权限弹窗确认可用 `/allow-all` 或 `/yolo` 自动批准。(源: https://raw.githubusercontent.com/github/copilot-cli/main/README.md \n- SDK对应 JSONRPC `permission.request`→`on_permission_request` 返回 `approved/denied-*``session.on()` 只用于审计 tool.execution_*,无独立 confirmation 事件。(源: 本地 SDK 源码 copilot/client.py、copilot/session.py、copilot/types.py\n\n示例\n```python\nfrom copilot import CopilotClient\nfrom copilot.generated.session_events import SessionEventType\n\nclient = CopilotClient()\nawait client.start()\nsession = await client.create_session({\n \"model\": \"gpt-5\",\n \"on_permission_request\": lambda req, _: {\"kind\": \"approved\"},\n})\nsession.on(lambda ev: ev.type == SessionEventType.TOOL_EXECUTION_START and print(ev.data.tool_name))\n```\n\n建议/落地:非交互模式实现 `on_permission_request` 并记录 `tool.execution_*` 事件。", "model": "gpt-5.2-codex"}
{"index": 66, "question": "Does the CLI support 'dry-run' permission checks for tools?", "answer": "CLI 文档未提到“dry-run”权限检查只有正常确认与 `/allow-all`、`/yolo` 自动批准https://raw.githubusercontent.com/github/copilot-cli/main/README.md。SDK 侧仅有 `permission.request`→`on_permission_request` 与 `hooks.on_pre_tool_use` 的拦截/拒绝(本地 SDK 源码),不提供不执行的试运行;建议/落地pipe 中用 `on_permission_request` 记录/拒绝高风险工具并在 hooks 做审计。", "model": "gpt-5.2-codex"}
{"index": 67, "question": "What are the undocumented requirements for 'workspace' context updates?", "answer": "CLI 文档与本地 SDK 源码都未提到“workspace context updates”的未公开要求SDK 只暴露 `working_directory` 与 `session.workspace_path`,以及 `attachments` 真实路径(来源:本地 SDK 源码)。 \n如果你指的是 CLI 的“工作区/会话文件”更新规则,请提供具体页面或日志片段,我可以进一步定位。", "model": "gpt-5.2-codex"}
{"index": 68, "question": "How does the CLI handle 'device code' re-authentication automatically?", "answer": "CLI 文档未说明“自动 device code 重新认证”的具体机制;只提到未登录时使用 `/login`PAT 可通过 `GH_TOKEN/GITHUB_TOKEN` 提供Copilot CLI 文档)。因此无法确认是否有自动 re-auth 或缓存刷新策略。 \n若需无交互场景请使用 SDK 的 `github_token` 注入并自行实现 device flow 轮询与持久化。", "model": "gpt-5.2-codex"}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,186 @@
"""
Test multi-rule permission control pattern (VSCode-style)
Tests ordered rule matching like VSCode's chat.tools.terminal.autoApprove
SAFE VERSION: Uses harmless commands (echo/ls) only. No rm, no git.
"""
import argparse
import asyncio
import json
import logging
import re
from typing import Any, Dict, List, Tuple
from copilot import CopilotClient
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger(__name__)
def build_multi_rule_handler(rules_json: str):
"""
Build permission handler with ordered rules (VSCode-style)
"""
try:
rules = json.loads(rules_json) if rules_json else {}
except json.JSONDecodeError as e:
logger.error("Invalid rules JSON: %s", e)
rules = {}
async def on_permission_request(request: Dict[str, Any], context: Dict[str, str]):
kind = request.get("kind")
command = request.get("fullCommandText", "") or request.get("command", "")
# Always approve read and url
if kind in ("read", "url"):
return {"kind": "approved"}
# For shell commands, apply ordered rules
if kind == "shell" and command:
for pattern, approved in rules.items():
try:
if re.match(pattern, command):
if approved:
logger.info(
"✅ Approved (rule match): pattern=%r command=%r",
pattern,
command,
)
return {"kind": "approved"}
else:
logger.warning(
"❌ Denied (rule match): pattern=%r command=%r",
pattern,
command,
)
return {
"kind": "denied-by-rules",
"rules": [
{"kind": "multi-rule-deny", "pattern": pattern}
],
}
except re.error as exc:
logger.error("Invalid pattern %r: %s", pattern, exc)
continue
# Default deny for shell without matching rule
logger.warning("❌ Denied (no matching rule): command=%r", command)
return {"kind": "denied-by-rules", "rules": [{"kind": "no-rule-match"}]}
return on_permission_request
async def run_test(model: str, rules_json: str, prompt: str) -> Tuple[bool, str]:
"""Run a single test and return (approved, response)"""
try:
client = CopilotClient()
await client.start()
session = await client.create_session(
{
"model": model,
"on_permission_request": build_multi_rule_handler(rules_json),
}
)
# Set a short timeout
try:
response = await asyncio.wait_for(
session.send_and_wait({"prompt": prompt}), timeout=15.0
)
except asyncio.TimeoutError:
logger.error("Test Timed Out")
return (False, "Timeout")
finally:
await client.stop()
content = response.data.content
# Heuristics to detect denial in response
denied_keywords = [
"不允许",
"无法",
"对不起",
"Sorry",
"can't",
"cannot",
"not have permission",
"denied",
]
is_denied = any(kw in content for kw in denied_keywords)
return (not is_denied, content)
except Exception as e:
logger.error("Test failed: %s", e)
return (False, str(e))
async def main():
parser = argparse.ArgumentParser()
parser.add_argument("--model", default="gpt-4.1", help="Model ID")
args = parser.parse_args()
# LOGIC TEST RULES
# 1. Deny "echo secret" explicitly (Specific Deny)
# 2. Allow "echo" anything else (General Allow)
# 3. Allow "ls" (General Allow)
# 4. Deny everything else (Default Deny)
logic_test_rules = {
"^echo\\s+secret": False, # Higher priority: Deny specific echo
"^echo": True, # Lower priority: Allow general echo
"^ls": True, # Allow ls
".*": False, # Deny everything else (e.g. whoami)
}
rules_json = json.dumps(logic_test_rules)
test_cases = [
# 1. Matches Rule 2 (^echo) -> Should be Approved
("Allowed: Normal Echo", "请执行: echo 'hello world'", True),
# 2. Matches Rule 3 (^ls) -> Should be Approved
("Allowed: LS", "请执行: ls -la", True),
# 3. Matches Rule 1 (^echo\s+secret) -> Should be DENIED
# This proves the ORDER matters. If it matched Rule 2 first, it would be allowed.
("Denied: Restricted Echo", "请执行: echo secret data", False),
# 4. Matches Rule 4 (.*) -> Should be DENIED
("Denied: Unknown Command", "请执行: whoami", False),
]
logger.info("=" * 80)
logger.info("Safe Multi-Rule Logic Test (Proving Precedence)")
logger.info("Rules: %s", json.dumps(logic_test_rules, indent=2))
logger.info("=" * 80)
results = []
for i, (name, prompt, expected) in enumerate(test_cases, 1):
logger.info("\n[Test %d/%d] %s", i, len(test_cases), name)
logger.info(" Prompt: %s", prompt)
approved, response = await run_test(args.model, rules_json, prompt)
passed = approved == expected
status = "✅ PASS" if passed else "❌ FAIL"
results.append((name, passed))
logger.info(
" Expected: %s, Got: %s - %s",
"Approved" if expected else "Denied",
"Approved" if approved else "Denied",
status,
)
logger.info(" Response: %s", response[:100].replace("\n", " "))
# Summary
logger.info("\n" + "=" * 80)
logger.info("Test Summary")
logger.info("=" * 80)
passed_count = sum(1 for _, passed in results if passed)
for name, passed in results:
logger.info("%s %s", "" if passed else "", name)
logger.info("Total: %d/%d tests passed", passed_count, len(results))
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,202 @@
"""
Comprehensive Permission Control Test Suite
Tests all permission control scenarios for GitHub Copilot SDK
"""
import argparse
import asyncio
import logging
import re
from typing import Any, Dict, List, Tuple
from copilot import CopilotClient
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger(__name__)
def build_permission_handler(allow_all: bool, allow_shell: bool, pattern: str):
async def on_permission_request(request: Dict[str, Any], context: Dict[str, str]):
kind = request.get("kind")
# Shell requests use 'fullCommandText' not 'command'
command = request.get("fullCommandText", "") or request.get("command", "")
if allow_all:
logger.info("✅ Approved (allow-all): kind=%s command=%r", kind, command)
return {"kind": "approved"}
if kind in ("read", "url"):
logger.info("✅ Approved (safe): kind=%s", kind)
return {"kind": "approved"}
if kind == "shell":
if allow_shell:
logger.info("✅ Approved (allow-shell): command=%r", command)
return {"kind": "approved"}
if pattern and command:
try:
if re.match(pattern, command):
logger.info(
"✅ Approved (regex match): pattern=%r command=%r",
pattern,
command,
)
return {"kind": "approved"}
except re.error as exc:
logger.error("Invalid regex pattern: %s (%s)", pattern, exc)
logger.warning("❌ Denied: kind=%s command=%r", kind, command)
return {"kind": "denied-by-rules", "rules": [{"kind": "test-suite"}]}
return on_permission_request
async def run_test(
model: str, allow_all: bool, allow_shell: bool, pattern: str, prompt: str
) -> Tuple[bool, str]:
"""Run a single test and return (success, response)"""
try:
client = CopilotClient()
await client.start()
session = await client.create_session(
{
"model": model,
"on_permission_request": build_permission_handler(
allow_all=allow_all,
allow_shell=allow_shell,
pattern=pattern,
),
}
)
response = await session.send_and_wait({"prompt": prompt})
await client.stop()
content = response.data.content
# Check if response indicates success or denial
denied_keywords = [
"不允许",
"无法",
"对不起",
"Sorry",
"can't",
"cannot",
"not have permission",
]
is_denied = any(kw in content for kw in denied_keywords)
return (not is_denied, content)
except Exception as e:
logger.error("Test failed with exception: %s", e)
return (False, str(e))
async def main():
parser = argparse.ArgumentParser(
description="Comprehensive permission control test suite."
)
parser.add_argument("--model", default="gpt-4.1", help="Model ID for testing.")
args = parser.parse_args()
# Test cases: (name, allow_all, allow_shell, pattern, prompt, expected_approved)
test_cases = [
("Default Deny Shell", False, False, "", "请执行: ls -la", False),
("Allow All", True, False, "", "请执行: ls -la", True),
("Allow Shell", False, True, "", "请执行: pwd", True),
("Regex Match: ^ls", False, False, "^ls", "请执行: ls -la", True),
("Regex No Match: ^ls vs pwd", False, False, "^ls", "请执行: pwd", False),
(
"Regex Complex: ^(ls|pwd|echo)",
False,
False,
"^(ls|pwd|echo)",
"请执行: pwd",
True,
),
(
"Regex Complex No Match: git",
False,
False,
"^(ls|pwd|echo)",
"请执行: git status",
False,
),
(
"Read Permission (Always Allow)",
False,
False,
"",
"Read the file: README.md",
True,
),
]
results = []
logger.info("=" * 80)
logger.info("Starting Comprehensive Permission Control Test Suite")
logger.info("Model: %s", args.model)
logger.info("=" * 80)
for i, (name, allow_all, allow_shell, pattern, prompt, expected) in enumerate(
test_cases, 1
):
logger.info("\n[Test %d/%d] %s", i, len(test_cases), name)
logger.info(
" Config: allow_all=%s, allow_shell=%s, pattern=%r",
allow_all,
allow_shell,
pattern,
)
logger.info(" Prompt: %s", prompt)
approved, response = await run_test(
args.model, allow_all, allow_shell, pattern, prompt
)
passed = approved == expected
status = "✅ PASS" if passed else "❌ FAIL"
results.append((name, passed))
logger.info(
" Expected: %s, Got: %s - %s",
"Approved" if expected else "Denied",
"Approved" if approved else "Denied",
status,
)
logger.info(
" Response: %s",
response[:100] + "..." if len(response) > 100 else response,
)
# Summary
logger.info("\n" + "=" * 80)
logger.info("Test Summary")
logger.info("=" * 80)
passed_count = sum(1 for _, passed in results if passed)
total_count = len(results)
for name, passed in results:
logger.info("%s %s", "" if passed else "", name)
logger.info("-" * 80)
logger.info(
"Total: %d/%d tests passed (%.1f%%)",
passed_count,
total_count,
100 * passed_count / total_count,
)
if passed_count == total_count:
logger.info("🎉 All tests passed!")
else:
logger.warning("⚠️ Some tests failed. Please review the logs.")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,90 @@
import argparse
import asyncio
import logging
import re
from typing import Any, Dict
from copilot import CopilotClient
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger(__name__)
def build_permission_handler(allow_all: bool, allow_shell: bool, pattern: str):
async def on_permission_request(request: Dict[str, Any], context: Dict[str, str]):
kind = request.get("kind")
# Shell requests use 'fullCommandText' not 'command'
command = request.get("fullCommandText", "") or request.get("command", "")
logger.info("permission.request FULL: %s", request)
logger.info("permission.request kind=%s command=%r", kind, command)
if allow_all:
return {"kind": "approved"}
if kind in ("read", "url"):
return {"kind": "approved"}
if kind == "shell":
if allow_shell:
return {"kind": "approved"}
if pattern and command:
try:
if re.match(pattern, command):
return {"kind": "approved"}
except re.error as exc:
logger.error("Invalid regex pattern: %s (%s)", pattern, exc)
return {"kind": "denied-by-rules", "rules": [{"kind": "debug-shell-pattern"}]}
return on_permission_request
async def main():
parser = argparse.ArgumentParser(
description="Test shell permission regex with GitHub Copilot SDK."
)
parser.add_argument(
"--pattern", default="", help="Regex pattern for auto-approving shell commands."
)
parser.add_argument(
"--allow-shell", action="store_true", help="Auto-approve all shell commands."
)
parser.add_argument(
"--allow-all", action="store_true", help="Auto-approve all permission requests."
)
parser.add_argument(
"--prompt",
default="请执行: ls -la",
help="Prompt to trigger a shell tool request.",
)
parser.add_argument("--model", default="gpt-5-mini", help="Model ID for testing.")
args = parser.parse_args()
client = CopilotClient()
await client.start()
session = await client.create_session(
{
"model": args.model,
"on_permission_request": build_permission_handler(
allow_all=args.allow_all,
allow_shell=args.allow_shell,
pattern=args.pattern,
),
}
)
logger.info("Sending prompt: %s", args.prompt)
response = await session.send_and_wait({"prompt": args.prompt})
logger.info("Response: %s", response.data.content)
await client.stop()
if __name__ == "__main__":
asyncio.run(main())

47
plugins/debug/mcp_test.py Normal file
View File

@@ -0,0 +1,47 @@
import asyncio
import os
from copilot import CopilotClient
async def main():
token = os.getenv("GH_TOKEN") or os.getenv("GITHUB_TOKEN")
if not token:
print(
"Error: GH_TOKEN (or GITHUB_TOKEN) environment variable not set. Please export GH_TOKEN=... before running."
)
return
client = CopilotClient()
await client.start()
async def on_permission_request(request, _ctx):
if request.get("kind") == "mcp":
return {"kind": "approved"}
return {"kind": "approved"}
session = await client.create_session(
{
"model": "gpt-5-mini",
"mcp_servers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": {"Authorization": f"Bearer {token}"},
"tools": ["*"],
}
}
}
)
result = await session.send_and_wait(
{
"prompt": "Use GitHub MCP tools to find the owner of the 'awesome-openwebui' repository.",
},timeout=1000
)
print(result.data.content)
await client.stop()
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,53 @@
# GitHub Copilot SDK Files Filter
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.1.2 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
This is a dedicated **companion filter plugin** designed specifically for the [GitHub Copilot SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4).
Its core mission is to **protect user-uploaded files from being "pre-processed" by the OpenWebUI core system, ensuring that the Copilot Agent receives the raw files for autonomous analysis.**
## 🎯 Why is this needed?
In OpenWebUI's default workflow, when you upload a file (e.g., PDF, Excel, Python script), OpenWebUI automatically initiates a **RAG (Retrieval-Augmented Generation)** process: parsing the file, vectorizing it, extracting text, and injecting it into the prompt.
While useful for standard models, this is often disruptive for a **Copilot SDK Agent**:
1. **Agent Needs Raw Files**: The Agent may need to run Python code to read an Excel file or analyze a full directory structure, not chopped-up text fragments.
2. **Context Pollution**: Large amounts of text injected by RAG consume tokens and can confuse the Agent about "where the file is."
3. **Control & Performance**: Bypassing the extraction step speeds up the response and gives the Agent full autonomy over how to handle the data.
**This plugin acts as a "bodyguard" to solve these issues.**
## 🚀 How it Works
When you select a Copilot model (name containing `copilot_sdk`) in OpenWebUI and send a file:
1. **Intercept**: This plugin runs with high priority (Priority 0), before RAG and other filters.
2. **Relocate**: Detecting a Copilot model, it moves the `files` list from the request to a secure custom field `copilot_files`.
3. **Hide**: It clears the original `files` field.
4. **Pass**: The OpenWebUI core sees an empty `files` list and **does not trigger RAG**.
5. **Deliver**: The subsequent Copilot SDK Pipe plugin checks `copilot_files`, retrieves file information, and automatically copies them into the Agent's isolated workspace.
## 📦 Installation & Configuration
### 1. Installation
Import this plugin on the OpenWebUI **Functions** page.
### 2. Enable
Ensure this Filter is enabled globally or in chat settings.
### 3. Configuration (Valves)
Default settings work for most users:
| Parameter | Description | Default |
| :--- | :--- | :--- |
| **priority** | Execution priority. **Must be lower than OpenWebUI RAG priority**. | `0` |
| **target_model_keyword** | Keyword to identify Copilot models for interception. | `copilot_sdk` |
## ⚠️ Important Notes
* **Must be used with Copilot SDK Pipe**: If you install this plugin without the main Pipe plugin, uploaded files will simply "disappear" (as no subsequent plugin will look for them).
* **Gemini Filter Compatibility**: Fully compatible with the Gemini Multimodal Filter. Just ensure priorities don't conflict.

View File

@@ -0,0 +1,51 @@
# GitHub Copilot SDK 文件过滤器
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.1.2 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
这是一个专门为 [GitHub Copilot SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) 设计的**伴侣过滤器插件**。
它的核心使命是:**保护用户上传的文件不被 OpenWebUI 核心系统“抢先处理”,确保 Copilot Agent 能够接收到原始文件并进行自主分析。**
## 🎯 为什么需要它?
在 OpenWebUI 的默认流程中,当你上传一个文件(如 PDF、Excel、Python 脚本OpenWebUI 会自动启动 **RAG检索增强生成** 流程:解析文件、向量化、提取文本并注入到提示词中。
虽然这对普通模型很有用,但对于 **Copilot SDK Agent** 来说,这往往是干扰:
1. **Agent 需要原始文件**Agent 可能需要运行 Python 代码读取 Excel或者分析完整的代码结构而不是被切碎的文本片段。
2. **上下文污染**RAG 注入的大量文本会消耗 Token且容易让 Agent 混淆“文件在哪里”。
3. **控制权与性能**:绕过提取步骤可以加快响应速度,并赋予 Agent 处理数据的完全自主权。
**本插件就是为了解决这个问题而生的“保镖”。**
## 🚀 功能原理
当你在 OpenWebUI 中选择了一个 Copilot 模型(名称包含 `copilot_sdk`)并发送文件时:
1. **拦截 (Intercept)**本插件会以极高的优先级Priority 5运行先于 RAG 和其他过滤器。
2. **搬运 (Relocate)**:它检测到模型是 Copilot便将请求中的 `files`(文件列表)移动到一个安全的自定义字段 `copilot_files` 中。
3. **隐身 (Hide)**:它清空原始的 `files` 字段。
4. **放行 (Pass)**OpenWebUI 核心看到 `files` 为空,便**不会触发 RAG**。
5. **交付 (Deliver)**:后续的 Copilot SDK Pipe 插件会检查 `copilot_files`,从中获取文件信息,并自动将其复制到 Agent 的独立工作区中。
## 📦 安装与配置
### 1. 安装
在 OpenWebUI 的 **Functions** 页面导入此插件。
### 2. 启用
确保在全局或对话设置中启用了此 Filter。
### 3. 配置 (Valves)
| 参数 | 说明 | 默认值 |
| :--- | :--- | :--- |
| **priority** | 过滤器的执行优先级。**必须小于 OpenWebUI RAG 的优先级**。 | `5` |
| **target_model_keyword** | 用户识别 Copilot 模型的关键词。只有包含此关键词的模型才会触发。 | `copilot_sdk` |
## ⚠️ 注意事项
* **必须配合 Copilot SDK Pipe 使用**:如果你没有安装主 Pipe 插件,本插件将导致上传的文件“凭空消失”。
* **Gemini Filter 兼容性**:已完美兼容 Gemini 多模态过滤器。只要优先级设置正确,它们可以共存互不干扰。

View File

@@ -0,0 +1,67 @@
"""
title: GitHub Copilot SDK Files Filter
id: github_copilot_sdk_files_filter
author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
version: 0.1.2
openwebui_id: 403a62ee-a596-45e7-be65-fab9cc249dd6
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.
"""
from pydantic import BaseModel, Field
from typing import Optional, Callable, Awaitable
class Filter:
class Valves(BaseModel):
priority: int = Field(
default=0,
description="Priority level. Must be lower than RAG processors to intercept files effectively.",
)
target_model_keyword: str = Field(
default="copilot_sdk",
description="Keyword to identify Copilot models (e.g., 'copilot_sdk').",
)
def __init__(self):
self.valves = self.Valves()
async def inlet(
self,
body: dict,
__user__: Optional[dict] = None,
__model__: Optional[dict] = None,
__metadata__: Optional[dict] = None,
__event_emitter__: Callable[[dict], Awaitable[None]] = None,
) -> dict:
# Determine the actual model ID
base_model_id = None
if __model__:
if "openai" in __model__:
base_model_id = __model__["openai"].get("id")
else:
base_model_id = __model__.get("info", {}).get("base_model_id")
current_model = base_model_id if base_model_id else body.get("model", "")
# Check if it's a Copilot model
if self.valves.target_model_keyword.lower() in current_model.lower():
# If files exist, move them to 'copilot_files' and clear 'files'
# This prevents OpenWebUI from triggering RAG on these files
if "files" in body and body["files"]:
file_count = len(body["files"])
if __event_emitter__:
await __event_emitter__(
{
"type": "status",
"data": {
"description": f"Managed {file_count} files for Copilot (RAG Bypassed)",
"done": True,
},
}
)
body["copilot_files"] = body["files"]
body["files"] = []
return body

View File

@@ -0,0 +1,66 @@
"""
title: GitHub Copilot SDK 文件过滤器(GitHub Copilot SDK Files Filter)
id: github_copilot_sdk_files_filter
author: Fu-Jie
author_url: https://github.com/Fu-Jie/awesome-openwebui
funding_url: https://github.com/open-webui
version: 0.1.2
description: 一个专门的过滤器,用于绕过 OpenWebUI 默认的 RAG 机制,针对 GitHub Copilot SDK 模型。它将上传的文件移动到安全位置 ('copilot_files'),以便 Copilot Pipe 可以原生处理它们而不受干扰。
"""
from pydantic import BaseModel, Field
from typing import Optional, Callable, Awaitable
class Filter:
class Valves(BaseModel):
priority: int = Field(
default=0,
description="优先级。必须低于 RAG 处理器的优先级,以便有效拦截文件。",
)
target_model_keyword: str = Field(
default="copilot_sdk",
description="用于识别 Copilot 模型的关键词(例如 'copilot_sdk')。",
)
def __init__(self):
self.valves = self.Valves()
async def inlet(
self,
body: dict,
__user__: Optional[dict] = None,
__model__: Optional[dict] = None,
__metadata__: Optional[dict] = None,
__event_emitter__: Callable[[dict], Awaitable[None]] = None,
) -> dict:
# Determine the actual model ID
base_model_id = None
if __model__:
if "openai" in __model__:
base_model_id = __model__["openai"].get("id")
else:
base_model_id = __model__.get("info", {}).get("base_model_id")
current_model = base_model_id if base_model_id else body.get("model", "")
# Check if it's a Copilot model
if self.valves.target_model_keyword.lower() in current_model.lower():
# If files exist, move them to 'copilot_files' and clear 'files'
# This prevents OpenWebUI from triggering RAG on these files
if "files" in body and body["files"]:
file_count = len(body["files"])
if __event_emitter__:
await __event_emitter__(
{
"type": "status",
"data": {
"description": f"已为 Copilot 管理 {file_count} 个文件 (已绕过 RAG)",
"done": True,
},
}
)
body["copilot_files"] = body["files"]
body["files"] = []
return body

View File

@@ -3,6 +3,7 @@ title: Gemini 多模态过滤器(含字幕增强)
author: Gemini Adapter
author_url: https://github.com/Fu-Jie
funding_url: https://github.com/Fu-Jie/awesome-openwebui
version: 0.3.2
description: >
一个强大的过滤器,为 OpenWebUI 中的任何模型提供多模态能力PDF、Office、图片、音频、视频等。
@@ -492,6 +493,13 @@ class Filter:
print(
f"🤖 Checking model: {current_model} (Target: {self.valves.target_model_keyword})"
)
# 0. SKIP COPILOT MODELS
# If the model is a Copilot Pipe model, we must NOT process files here.
# Copilot Pipe has its own file handling logic (copy to workspace).
if "copilot_sdk" in current_model.lower():
print(f"⏩ Skipping Gemini Filter for Copilot model: {current_model}")
return body
# Check if model matches target keyword
is_target_model = (

View File

@@ -0,0 +1,73 @@
# GitHub Copilot SDK Pipe - Internal Architecture & Knowledge
This document details the core runtime mechanisms, file layout, and deep integration logic of the GitHub Copilot SDK plugin within the OpenWebUI environment.
## 1. Core Environmental Context
### 1.1 Filesystem Layout
| Path | Description | Permissions |
| :--- | :--- | :--- |
| `/app/backend` | OpenWebUI backend Python source code | Read-Only |
| `/app/build` | OpenWebUI frontend assets (Artifacts renderer location) | Read-Only |
| `/root/.copilot/` | SDK core configuration and state storage | **Full Control** |
| `/app/backend/data/copilot_workspace/` | Plugin-specific persistent workspace | **Read/Write** |
### 1.2 Identity Mapping Mechanism
* **Session ID Binding**: The plugin strictly maps OpenWebUI's `Chat ID` to the Copilot SDK's `Session ID`.
* **Outcome**: Every chat session has its own isolated physical directory: `/root/.copilot/session-state/{chat_id}/`.
## 2. TODO List Persistence (TODO Intelligence)
### 2.1 Data Source
TODO data is **not** stored in a standalone database table for basic operations but is captured from the session event stream via the `update_todo` tool:
* **Storage File**: `/root/.copilot/session-state/{chat_id}/events.jsonl`
* **Detection**: Scans for the latest `tool.execution_complete` event where `toolName` is `update_todo`.
### 2.2 Data Format (NDJSON)
```json
{
"type": "tool.execution_complete",
"data": {
"toolName": "update_todo",
"result": {
"detailedContent": "TODO List content...",
"toolTelemetry": { "metrics": { "total_items": 3 } }
}
}
}
```
## 3. Toolchain Integration
The plugin harmonizes three distinct tool systems:
1. **Copilot Native**: Built-in capabilities like `bash`, `edit`, and `task`.
2. **OpenWebUI Ecosystem**: Dynamically mounts local Python tools and built-in `Web Search` via `get_tools`.
3. **MCP (Model Context Protocol)**: External capability extensions via servers like `GDMap` or `Pandoc`.
## 4. Security & Permissions
### 4.1 Admin Mode (God Mode)
When `__user__['role'] == 'admin'`:
* `ADMIN_EXTENSIONS` are enabled.
* Access to `DATABASE_URL` via environment variables is permitted.
* `bash` can be used to diagnose internal state within `/root/.copilot/`.
### 4.2 Regular User Mode
* `USER_RESTRICTIONS` are strictly enforced.
* Probing environment variables or database credentials is prohibited.
* `bash` activity is strictly confined to the isolated workspace.
## 5. Common Maintenance
* **Reset Session**: Delete the `/root/.copilot/session-state/{chat_id}` directory.
* **Clear Cache**: Disable `ENABLE_TOOL_CACHE` in Valves.
* **View Logs**: Check latest logs under `/root/.copilot/logs/`.

View File

@@ -0,0 +1,70 @@
# GitHub Copilot SDK Pipe - 内部架构与通用知识 (Internal Knowledge)
本文档记录了 GitHub Copilot SDK 插件在 OpenWebUI 环境下的核心运行机制、文件布局及深度集成逻辑。
## 1. 核心环境上下文 (Environment)
### 1.1 文件系统布局
| 路径 | 说明 | 权限 |
| :--- | :--- | :--- |
| `/app/backend` | OpenWebUI 后端 Python 源码 | 只读 |
| `/app/build` | OpenWebUI 前端静态资源 (Artifacts 渲染器位置) | 只读 |
| `/root/.copilot/` | SDK 核心配置与状态存储 | **完全控制** |
| `/app/backend/data/copilot_workspace/` | 插件指定的持久化工作区 | **读写自如** |
### 1.2 身份映射机制
* **Session ID 绑定**: 插件强制将 OpenWebUI 的 `Chat ID` 映射为 Copilot SDK 的 `Session ID`
* **结果**: 每个对话窗口都有独立的物理存储目录:`/root/.copilot/session-state/{chat_id}/`
## 2. TODO List 存储机制 (TODO Intelligence)
### 2.1 数据源
TODO 数据**并不**仅存储在独立数据库,而是通过 `update_todo` 工具写入会话事件流:
* **文件**: `/root/.copilot/session-state/{chat_id}/events.jsonl`
* **识别**: 查找类型为 `tool.execution_complete``toolName``update_todo` 的最新 JSON 行。
### 2.2 数据格式 (NDJSON)
```json
{
"type": "tool.execution_complete",
"data": {
"toolName": "update_todo",
"result": { "detailedContent": "TODO List内容...", "toolTelemetry": { "metrics": { "total_items": 39 } } }
}
}
```
## 3. 工具体系 (Toolchain)
插件整合了三套工具系统:
1. **Copilot Native**: SDK 内置的 `bash`, `edit`, `task` 等。
2. **OpenWebUI Ecosystem**: 通过 `get_tools` 挂载的本地 Python 脚本和内置 `Web Search`
3. **MCP (Model Context Protocol)**: 通过 `GDMap`, `Pandoc` 等服务器扩展的外部能力。
## 4. 安全与权限 (Security)
### 4.1 管理员模式 (God Mode)
`__user__['role'] == 'admin'` 时:
* 启用 `ADMIN_EXTENSIONS`
* 允许通过环境变量获取 `DATABASE_URL`
* 允许使用 `bash` 诊断 `/root/.copilot/` 内部状态。
### 4.2 普通用户模式
* 启用 `USER_RESTRICTIONS`
* 严禁探测环境变量和数据库。
* 限制 `bash` 只能在工作区内活动。
## 5. 常见维护操作
* **重置会话**: 删除 `/root/.copilot/session-state/{chat_id}` 目录。
* **清理缓存**: 在 Valves 中关闭 `ENABLE_TOOL_CACHE`
* **查看日志**: 检查 `/root/.copilot/logs/` 下的最新日志。

View File

@@ -1,103 +1,118 @@
# GitHub Copilot SDK Pipe for OpenWebUI
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.3.0 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.6.0 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/open-webui) that allows you to use GitHub Copilot models (such as `gpt-5`, `gpt-5-mini`, `claude-sonnet-4.5`) directly within OpenWebUI. It is built upon the official [GitHub Copilot SDK for Python](https://github.com/github/copilot-sdk), providing a native integration experience.
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**.
## 🚀 What's New (v0.3.0) - The Power of "Unified Ecosystem"
> [!IMPORTANT]
> **Essential Companion**
> To unlock file handling and data analysis capabilities, you must install the [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6).
* **🔌 Zero-Config Tool Bridge**: Automatically transforms your existing OpenWebUI Functions (Tools) into Copilot-compatible tools. **Copilot now has total access to your entire WebUI toolset!**
* **🔗 Dynamic MCP Discovery**: Seamlessly connects to MCP servers defined in **Admin Settings -> Connections**. No configuration files required—it just works.
* **⚡ High-Performance Async Engine**: Background CLI updates and optimized event-driven streaming ensure lightning-fast responses without UI lag.
* **🛡️ Robust Interoperability**: Advanced sanitization and dynamic Pydantic model generation ensure smooth integration even with complex third-party tools.
> [!TIP]
> **No Subscription Required for BYOK**
> If you are using your own API keys (BYOK mode with OpenAI/Anthropic), **you do NOT need a GitHub Copilot subscription.** A subscription is only required to access GitHub's official models.
---
## ✨ v0.6.0 Updates (What's New)
- **👥 User & Chat Management**: Physical management architecture (`user_id/chat_id`) for absolute resource independence.
- **🤖 Empowering Agent Autonomy**: Automatic synchronization of raw files to the workspace, enabling direct Python-based analysis of Excel/CSV.
- **🔧 OpenAPI & External Tool Fixes**: Full support for tools mounted via OpenAPI servers.
- **📊 Cost Control**: Enhanced **Billing Multiplier Limits** (`MAX_MULTIPLIER`, e.g., set to 0 for free models only) and **Model Keyword Filtering** (`EXCLUDE_KEYWORDS`) for precise cost management.
- **🧠 Persistent TODO Lists**: Database-backed task tracking that persists across sessions.
---
## ✨ Key Capabilities
* **🌉 The Ultimate Bridge**: The first and only plugin that creates a seamless bridge between **OpenWebUI Tools** and **GitHub Copilot SDK**.
* **🚀 Official & Native**: Built directly on the official Python SDK, providing the most stable and authentic Copilot experience.
* **🌊 Advanced Streaming (Thought Process)**: Supports full model reasoning/thinking display with typewriter effects.
* **🖼 Intelligent Multimodal**: Full support for images and attachments, enabling Copilot to "see" your workspace.
* **🛠️ Effortless Setup**: Automatic CLI detection, version enforcement, and dependency management.
* **🔑 Dual-Layer Security**: Supports secure OAuth flow for Chat and standard PAT for extended MCP capabilities.
- **🔑 Flexible Auth & BYOK**: Official Copilot subscriptions (PAT) or Bring Your Own Key (OpenAI/Anthropic).
- **🔌 Universal Tool Protocol**: Native support for **MCP (Model Context Protocol)**, OpenAPI, and OpenWebUI built-in tools.
- **🛡️ Sandbox Workspace Isolation**: Strict per-session sandboxing for data privacy and security.
- ** Infinite Session Management**: Smart context window management with automatic compaction for indefinite conversation capability.
- **🧠 Deep Database Integration**: Real-time persistence of TOD·O lists for long-running workflows.
- **🌊 Advanced Streaming**: Full support for thinking process/Chain of Thought visualization.
- **🖼️ Intelligent Multimodal**: Vision capabilities and raw file analysis support.
- **⚡ Interactive Artifacts**: Automatically renders HTML/JS apps generated by the agent directly in the chat interface.
## Installation & Configuration
---
### 1) Import Function
## ⚙️ Core Configuration (Valves)
1. Open OpenWebUI.
2. Go to **Workspace** -> **Functions**.
3. Click **+** (Create Function).
4. Paste the content of `github_copilot_sdk.py` (or `github_copilot_sdk_cn.py` for Chinese) completely.
5. Save.
### 1. Administrator Settings (Base)
### 2) Configure Valves (Settings)
Administrators define the default behavior for all users in the function settings.
Find "GitHub Copilot" in the function list and click the **⚙️ (Valves)** icon to configure:
| Parameter | Description | Default |
| Valve | Default | Description |
| :--- | :--- | :--- |
| **GH_TOKEN** | **(Required)** GitHub Access Token (PAT or OAuth Token). Access to Chat. | - |
| **DEBUG** | Whether to enable debug logs (output to browser console). | `False` |
| **LOG_LEVEL** | Copilot CLI log level: none, error, warning, info, debug, all. | `error` |
| **SHOW_THINKING** | Show model reasoning/thinking process (requires streaming + model support). | `True` |
| **COPILOT_CLI_VERSION** | Specific Copilot CLI version to install/enforce. | `0.0.405` |
| **EXCLUDE_KEYWORDS** | Exclude models containing these keywords (comma separated). | - |
| **WORKSPACE_DIR** | Restricted workspace directory for file operations. | - |
| **INFINITE_SESSION** | Enable Infinite Sessions (automatic context compaction). | `True` |
| **COMPACTION_THRESHOLD** | Background compaction threshold (0.0-1.0). | `0.8` |
| **BUFFER_THRESHOLD** | Buffer exhaustion threshold (0.0-1.0). | `0.95` |
| **TIMEOUT** | Timeout for each stream chunk (seconds). | `300` |
| **CUSTOM_ENV_VARS** | Custom environment variables (JSON format). | - |
| **REASONING_EFFORT** | Reasoning effort level: low, medium, high. `xhigh` is supported for some models. | `medium` |
| **ENFORCE_FORMATTING** | Add formatting instructions to system prompt for better readability. | `True` |
| **ENABLE_MCP_SERVER** | Enable Direct MCP Client connection (Recommended). | `True` |
| **ENABLE_OPENWEBUI_TOOLS** | Enable OpenWebUI Tools (includes defined and server tools). | `True` |
| `GH_TOKEN` | `""` | Global GitHub Token (Requires 'Copilot Requests' permission). |
| `ENABLE_OPENWEBUI_TOOLS` | `True` | Enable OpenWebUI Tools (includes defined Tools and Built-in Tools). |
| `ENABLE_OPENAPI_SERVER` | `True` | Enable OpenAPI Tool Server connection. |
| `ENABLE_MCP_SERVER` | `True` | Enable Direct MCP Client connection (Recommended). |
| `REASONING_EFFORT` | `medium` | Reasoning effort level: low, medium, high. |
| `SHOW_THINKING` | `True` | Show model reasoning/thinking process. |
| `INFINITE_SESSION` | `True` | Enable Infinite Sessions (automatic context compaction). |
| `MAX_MULTIPLIER` | `1.0` | Max allowed billing multiplier (0x for free models only). |
| `EXCLUDE_KEYWORDS` | `""` | Exclude models containing these keywords (comma separated). |
| `TIMEOUT` | `300` | Timeout for each stream chunk (seconds). |
| `BYOK_TYPE` | `openai` | BYOK Provider Type: `openai`, `anthropic`. |
| `BYOK_BASE_URL` | `""` | BYOK Base URL (e.g., <https://api.openai.com/v1>). |
| `BYOK_MODELS` | `""` | BYOK Model List (comma separated). Leave empty to fetch from API. |
| `CUSTOM_ENV_VARS` | `""` | Custom environment variables (JSON format). |
| `DEBUG` | `False` | Enable this to see detailed logs in your browser console. |
#### User Valves (per-user overrides)
### 2. User Settings (Individual Overrides)
These optional settings can be set per user (overrides global Valves):
Standard users can override these settings in their individual Profile/Function settings.
| Parameter | Description | Default |
| :--- | :--- | :--- |
| **GH_TOKEN** | Personal GitHub Token (overrides global setting). | - |
| **REASONING_EFFORT** | Reasoning effort level (low/medium/high/xhigh). | - |
| **DEBUG** | Enable technical debug logs. | `False` |
| **SHOW_THINKING** | Show model reasoning/thinking process. | `True` |
| **ENABLE_OPENWEBUI_TOOLS** | Enable OpenWebUI Tools (overrides global). | `True` |
| **ENABLE_MCP_SERVER** | Enable MCP server loading (overrides global). | `True` |
| **ENFORCE_FORMATTING** | Enforce formatting guidelines (overrides global). | `True` |
| Valve | Description |
| :--- | :--- |
| `GH_TOKEN` | Use your personal GitHub Token. |
| `REASONING_EFFORT` | Individual reasoning effort preference. |
| `SHOW_THINKING` | Show model reasoning/thinking process. |
| `MAX_MULTIPLIER` | Maximum allowed billing multiplier override. |
| `EXCLUDE_KEYWORDS` | Exclude models containing these keywords. |
| `BYOK_API_KEY` | Use your personal OpenAI/Anthropic API Key. |
---
## 🎯 Use Cases (What can you do?)
- **📁 Fully Autonomous DevOps**: Agent analyzes code, runs tests, and applies patches within its isolated sandbox.
- **📊 Deep Data Auditing**: Directly process raw Excel/CSV data via Python (bypassing RAG) and generate visual reports.
- **📝 Long-Task Management**: Automatically decomposes complex requests and persists TOD·O progress across sessions.
---
## ⭐ Support
If this plugin has been useful, a star on [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) is a big motivation for me. Thank you for the support.
If this plugin has been useful, a **Star** on [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) would be a great motivation for me. Thank you!
### Get Token
---
To use GitHub Copilot, you need a GitHub Personal Access Token (PAT) with appropriate permissions.
## 🚀 Installation & Configuration
**Steps to generate your token:**
### 1) Import Function
1. Open OpenWebUI, go to **Workspace** -> **Functions**.
2. Click **+** (Create Function), paste the content of `github_copilot_sdk.py`.
3. Save and ensure it is enabled.
### 2) Get Token
1. Visit [GitHub Token Settings](https://github.com/settings/tokens?type=beta).
2. Click **Generate new token (fine-grained)**.
3. **Repository access**: Select **Public Repositories** (simplest) or **All repositories**.
4. **Permissions**:
* If you chose **All repositories**, you must click **Account permissions**.
* Find **Copilot Requests**, and select **Access**.
5. Generate and copy the Token.
2. Create **Fine-grained token**, granting **Account permissions** -> **Copilot Requests** access.
3. Paste the generated Token into the `GH_TOKEN` field in Valves.
## 📋 Dependencies
---
This Pipe will automatically attempt to install the following dependencies:
## 📋 Troubleshooting & Dependencies
* `github-copilot-sdk` (Python package)
* `github-copilot-cli` (Binary file, installed via official script)
- **Agent ignores files?**: Ensure the Files Filter is enabled, otherwise RAG will interfere with raw binaries.
- **No progress bar?**: The bar only appears when the Agent uses the `update_todo` tool.
- **Dependencies**: This Pipe automatically installs `github-copilot-sdk` (Python) and `github-copilot-cli` (Binary).
## Troubleshooting ❓
* **Images not recognized**:
* Ensure `MODEL_ID` is a model that supports multimodal input.
* **Thinking not shown**:
* Ensure **streaming is enabled** and the selected model supports reasoning output.
---
## Changelog

View File

@@ -1,104 +1,123 @@
# GitHub Copilot SDK 官方管道
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.3.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
**作者:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **版本:** 0.6.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **许可证:** MIT
这是一个用于 [OpenWebUI](https://github.com/open-webui/open-webui) 的高级 Pipe 函数,允许你直接在 OpenWebUI 中使用 GitHub Copilot 模型(如 `gpt-5`, `gpt-5-mini`, `claude-sonnet-4.5`)。它基于官方 [GitHub Copilot SDK for Python](https://github.com/github/copilot-sdk) 构建,提供了原生级的集成体验。
这是一个用于 [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 交互体验。
## 🚀 最新特性 (v0.3.0) - “统一生态”的力量
> [!IMPORTANT]
> **核心伴侣组件**
> 如需启用文件处理与数据分析能力,请务必安装 [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)。
* **🔌 零配置工具桥接 (Unified Tool Bridge)**: 自动将您现有的 OpenWebUI Functions (工具) 转换为 Copilot 兼容工具。**Copilot 现在可以无缝调用您手头所有的 WebUI 工具!**
* **🔗 动态 MCP 自动发现**: 直接联动 OpenWebUI **管理面板 -> 连接**。无需编写任何配置文件,即插即用,瞬间扩展 Copilot 能力边界。
* **⚡ 高性能异步引擎**: 异步 CLI 更新检查与高度优化的事件驱动流式处理,确保对话毫秒级响应
* **🛡️ 卓越的兼容性**: 独创的动态 Pydantic 模型生成技术,确保复杂工具参数在 Copilot 端也能得到精准验证。
> [!TIP]
> **BYOK 模式无需订阅**
> 如果您使用自带的 API Key (BYOK 模式对接 OpenAI/Anthropic)**您不需要 GitHub Copilot 官方订阅**。只有在访问 GitHub 官方模型时才需要订阅
## ✨ 核心能力
---
* **🌉 强大的生态桥接**: 首个且唯一完美打通 **OpenWebUI Tools****GitHub Copilot SDK** 的插件。
* **🚀 官方原生产体验**: 基于官方 Python SDK 构建,提供最稳定、最纯正的 Copilot 交互体验。
* **🌊 深度推理展示**: 完整支持模型思考过程 (Thinking Process) 的流式渲染。
* **🖼️ 智能多模态**: 支持图像识别与附件上传,让 Copilot 拥有视觉能力。
* **🛠️ 极简部署流程**: 自动检测环境、自动下载 CLI、自动管理依赖全自动化开箱即用。
* **🔑 安全认证体系**: 完美支持 OAuth 授权与 PAT 模式,兼顾便捷与安全性。
## ✨ 0.6.0 更新内容 (What's New)
## 安装与配置
- **👥 多用户与会话管理**: 采用 `user_id/chat_id` 的物理隔离架构,确保资源独立与稳健管理。
- **🤖 赋予 Agent 文件自主权**: 自动将上传的文件同步至物理工作区,支持 Python 直接分析 Excel/CSV。
- **🔧 OpenAPI & 外部工具修复**: 完美支持通过 OpenAPI 服务器挂载的工具调用。
- **📊 计费与成本控制**: 增强的**计费倍率限制** (`MAX_MULTIPLIER`,例如设为 0 即仅限免费模型) 和**模型关键词过滤** (`EXCLUDE_KEYWORDS`),实现更精准的成本管控。
- **🧠 数据库持久化 TODO**: 任务进度跨会话保存Agent 拥有更持久的任务记忆。
### 1. 导入函数
---
1. 打开 OpenWebUI。
2. 进入 **Workspace** -> **Functions**
3. 点击 **+** (创建函数)。
4.`github_copilot_sdk_cn.py` 的内容完整粘贴进去。
5. 保存。
## ✨ 核心能力 (Key Capabilities)
### 2. 配置 Valves (设置)
- **🔑 灵活鉴权与 BYOK**: 支持 GitHub Copilot 官方订阅 (PAT) 或自带 Key (OpenAI/Anthropic)。
- **🔌 通用工具协议**: 原生支持 **MCP (Model Context Protocol)**、OpenAPI 以及 OpenWebUI 内置工具。
- **🛡️ 物理级工作区隔离**: 强制执行严格的用户特定沙箱,确保数据隐私与文件安全。
- **♾️ 无限会话管理**: 智能上下文窗口管理与自动压缩算法,支持无限时长的对话交互。
- **🧠 深度数据库集成**: 实时持久化 TOD·O 列表到 UI 进度条。
- **🌊 深度推理展示**: 完整支持模型思考过程 (Thinking Process) 的流式渲染。
- **🖼️ 智能多模态**: 完整支持图像识别与附件上传分析。
- **⚡ 交互式伪影 (Artifacts)**: 自动渲染 Agent 生成的 HTML/JS 应用程序,直接在聊天界面交互。
在函数列表中找到 "GitHub Copilot",点击 **⚙️ (Valves)** 图标进行配置:
---
| 参数 | 说明 | 默认值 |
## ⚙️ 核心配置参数 (Valves)
### 1. 管理员配置 (基础设置)
管理员可在函数设置中定义全局默认行为。
| 参数 | 默认值 | 说明 |
| :--- | :--- | :--- |
| **GH_TOKEN** | **(必填)** GitHub 访问令牌 (PAT 或 OAuth Token)。用于聊天。 | - |
| **DEBUG** | 是否开启调试日志(输出到浏览器控制台)。 | `False` |
| **LOG_LEVEL** | Copilot CLI 日志级别: none, error, warning, info, debug, all。 | `error` |
| **SHOW_THINKING** | 是否显示模型推理/思考过程(需开启流式 + 模型支持)。 | `True` |
| **COPILOT_CLI_VERSION** | 指定安装/强制使用的 Copilot CLI 版本。 | `0.0.405` |
| **EXCLUDE_KEYWORDS** | 排除包含这些关键词的模型(逗号分隔)。 | - |
| **WORKSPACE_DIR** | 文件操作的受限工作区目录。 | - |
| **INFINITE_SESSION** | 启用无限会话(自动上下文压缩)。 | `True` |
| **COMPACTION_THRESHOLD** | 后台压缩阈值 (0.0-1.0)。 | `0.8` |
| **BUFFER_THRESHOLD** | 缓冲区耗尽阈值 (0.0-1.0)。 | `0.95` |
| **TIMEOUT** | 每个流式分块超时(秒)。 | `300` |
| **CUSTOM_ENV_VARS** | 自定义环境变量 (JSON 格式)。 | - |
| **REASONING_EFFORT** | 推理强度级别: low, medium, high. `xhigh` 仅部分模型支持。 | `medium` |
| **ENFORCE_FORMATTING** | 在系统提示词中添加格式化指导。 | `True` |
| **ENABLE_MCP_SERVER** | 启用直接 MCP 客户端连接 (建议)。 | `True` |
| **ENABLE_OPENWEBUI_TOOLS** | 启用 OpenWebUI 工具 (包括自定义和服务器工具)。 | `True` |
| `GH_TOKEN` | `""` | 全局 GitHub Token (需具备 'Copilot Requests' 权限)。 |
| `ENABLE_OPENWEBUI_TOOLS` | `True` | 启用 OpenWebUI 工具 (包括定义工具和内置工具)。 |
| `ENABLE_OPENAPI_SERVER` | `True` | 启用 OpenAPI 工具服务器连接。 |
| `ENABLE_MCP_SERVER` | `True` | 启用直接 MCP 客户端连接 (推荐)。 |
| `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` | 开启此项以在前端控制台输出详细调试日志。 |
#### 用户 Valves按用户覆盖
### 2. 用户配置 (个人覆盖)
以下设置可按用户单独配置(覆盖全局 Valves
普通用户可在各自的个人设置中根据需要覆盖以下参数。
| 参数 | 说明 | 默认值 |
| :--- | :--- | :--- |
| **GH_TOKEN** | 个人 GitHub Token(覆盖全局设置)。 | - |
| **REASONING_EFFORT** | 推理强度级别low/medium/high/xhigh。 | - |
| **DEBUG** | 是否启用技术调试日志。 | `False` |
| **SHOW_THINKING** | 是否显示思考过程。 | `True` |
| **ENABLE_OPENWEBUI_TOOLS** | 启用 OpenWebUI 工具(覆盖全局设置)。 | `True` |
| **ENABLE_MCP_SERVER** | 启用动态 MCP 服务器加载(覆盖全局设置)。 | `True` |
| **ENFORCE_FORMATTING** | 强制启用格式化指导(覆盖全局设置)。 | `True` |
| 参数 | 说明 |
| :--- | :--- |
| `GH_TOKEN` | 使用个人 GitHub Token |
| `REASONING_EFFORT`| 个人偏好的推理强度。 |
| `SHOW_THINKING` | 显示模型推理/思考过程。 |
| `MAX_MULTIPLIER` | 最大允许的模型计费倍率覆盖。 |
| `EXCLUDE_KEYWORDS` | 排除包含这些关键字的模型。 |
| `BYOK_API_KEY` | 使用个人的 OpenAI/Anthropic API Key。 |
## ⭐ 支持
---
如果这个插件对你有帮助,欢迎到 [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) 点个 Star这将是我持续改进的动力感谢支持。
## 🎯 典型应用场景 (Use Cases)
### 获取 Token
- **📁 全自主仓库维护**: Agent 在隔离工作区内自动分析代码、运行测试并应用补丁。
- **📊 深度财务数据审计**: 直接通过 Python 加载 Excel/CSV 原始数据(绕过 RAG生成图表并实时预览。
- **📝 长任务项目管理**: 自动拆解复杂任务并持久化 TOD·O 进度,跨会话跟踪执行状态。
要使用 GitHub Copilot您需要一个具有适当权限的 GitHub 个人访问令牌 (PAT)。
---
**获取步骤:**
## ⭐ 支持与交流 (Support)
1. 访问 [GitHub 令牌设置](https://github.com/settings/tokens?type=beta)
2. 点击 **Generate new token (fine-grained)**
3. **Repository access**: 选择 **Public Repositories** (最简单) 或 **All repositories**
4. **Permissions**:
* 如果您选择了 **All repositories**,则必须点击 **Account permissions**
* 找到 **Copilot Requests**,选择 **Access**
5. 生成并复制令牌。
如果这个插件对您有所帮助,请在 [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) 项目上点个 **Star** 💫,这是对我最大的鼓励
## 📋 依赖说明
---
该 Pipe 会自动尝试安装以下依赖(如果环境中缺失):
## 🚀 安装与配置 (Installation)
* `github-copilot-sdk` (Python 包)
* `github-copilot-cli` (二进制文件,通过官方脚本安装)
### 1) 导入函数
## 故障排除 (Troubleshooting) ❓
1. 打开 OpenWebUI前往 **工作区** -> **函数**
2. 点击 **+** (创建函数),完整粘贴 `github_copilot_sdk_cn.py` 的内容。
3. 点击保存并确保已启用。
* **图片及多模态使用说明**
* 确保 `MODEL_ID` 是支持多模态的模型。
* **看不到思考过程**
* 确认已开启**流式输出**,且所选模型支持推理输出。
### 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` 配置项中。
完整历史请查看 GitHub 项目: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
### 3) 配套插件 (强烈推荐)
为了获得最佳的文件处理体验,请安装 [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)。
---
## 📋 常见问题与依赖 (Troubleshooting)
- **Agent 无法识别文件?**: 请确保已安装并启用了 Files Filter 插件,否则原始文件会被 RAG 干扰。
- **看不到 TODO 进度条?**: 进度条仅在 Agent 使用 `update_todo` 工具(通常是处理复杂任务)时出现。
- **依赖安装**: 本管道会自动尝试安装 `github-copilot-sdk` (Python 包) 和 `github-copilot-cli` (官方二进制)。
---
## 更新日志 (Changelog)
完整历史记录请见 GitHub: [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)

View File

@@ -0,0 +1,424 @@
#!/usr/bin/env python3
"""
Copilot SDK System Message Test Script
Tests whether system_message is properly applied during session.resume
This script verifies the bug hypothesis:
- session.resume with system_message config may not reliably update the system prompt
Test scenarios:
1. Create a new session with a custom system message
2. Resume the same session with a DIFFERENT system message
3. Ask the model to describe its current system instructions
Requirements:
- github-copilot-sdk>=0.1.23
"""
import asyncio
import os
import sys
import time
from copilot import CopilotClient
from copilot.types import SessionConfig
from copilot.generated.session_events import SessionEventType
# Test system messages
SYSTEM_MSG_A = """You are a helpful assistant named "ALPHA".
When asked about your name or identity, you MUST respond: "I am ALPHA, the first assistant."
Always start your responses with "[ALPHA]:" prefix.
"""
SYSTEM_MSG_B = """You are a helpful assistant named "BETA".
When asked about your name or identity, you MUST respond: "I am BETA, the second assistant."
Always start your responses with "[BETA]:" prefix.
"""
async def send_and_get_response(session, prompt: str) -> str:
"""Send a message and collect the full response using event subscription."""
full_response = ""
response_complete = asyncio.Event()
def event_handler(event):
nonlocal full_response
if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
delta = getattr(event.data, "content", "") or ""
print(delta, end="", flush=True)
full_response += delta
elif event.type == SessionEventType.ASSISTANT_MESSAGE:
# Final complete message
content = getattr(event.data, "content", "") or ""
if content and not full_response:
full_response = content
print(content, end="", flush=True)
elif event.type == SessionEventType.SESSION_IDLE:
response_complete.set()
elif event.type == SessionEventType.ASSISTANT_TURN_END:
response_complete.set()
# Subscribe to events
unsubscribe = session.on(event_handler)
try:
# Send the message
await session.send({"prompt": prompt, "mode": "immediate"})
# Wait for completion (with timeout)
await asyncio.wait_for(response_complete.wait(), timeout=120)
print() # newline after completion
finally:
unsubscribe()
return full_response
async def test_new_session_system_message(client: CopilotClient):
"""Test 1: New session with system message A"""
print("\n" + "=" * 60)
print("TEST 1: New Session with System Message A (ALPHA)")
print("=" * 60)
session_config = SessionConfig(
session_id="test-session-001",
model="gpt-5-mini",
streaming=True,
system_message={
"mode": "replace",
"content": SYSTEM_MSG_A,
},
)
session = await client.create_session(config=session_config)
print(f"✅ Created new session: {session.session_id}")
print("\n📤 Asking: 'What is your name?'")
print("📥 Response: ", end="")
response = await send_and_get_response(session, "What is your name?")
if "ALPHA" in response:
print("✅ SUCCESS: Model correctly identified as ALPHA")
else:
print("⚠️ WARNING: Model did NOT identify as ALPHA")
return session
async def test_resume_session_with_new_system_message(
client: CopilotClient, session_id: str
):
"""Test 2: Resume session with DIFFERENT system message B"""
print("\n" + "=" * 60)
print("TEST 2: Resume Session with System Message B (BETA)")
print("=" * 60)
resume_config = {
"model": "gpt-5-mini",
"streaming": True,
"system_message": {
"mode": "replace",
"content": SYSTEM_MSG_B,
},
}
print(f"📋 Resume config includes system_message with mode='replace'")
print(f"📋 New system_message content: BETA identity")
session = await client.resume_session(session_id, resume_config)
print(f"✅ Resumed session: {session.session_id}")
print("\n📤 Asking: 'What is your name now? Did your identity change?'")
print("📥 Response: ", end="")
response = await send_and_get_response(
session, "What is your name now? Did your identity change?"
)
if "BETA" in response:
print("✅ SUCCESS: System message was updated to BETA")
return True
elif "ALPHA" in response:
print("❌ BUG CONFIRMED: System message was NOT updated (still ALPHA)")
return False
else:
print("⚠️ INCONCLUSIVE: Model response doesn't clearly indicate identity")
return None
async def test_resume_without_system_message(client: CopilotClient, session_id: str):
"""Test 3: Resume session without specifying system_message"""
print("\n" + "=" * 60)
print("TEST 3: Resume Session WITHOUT System Message")
print("=" * 60)
resume_config = {
"model": "gpt-4o",
"streaming": True,
# No system_message specified
}
session = await client.resume_session(session_id, resume_config)
print(f"✅ Resumed session: {session.session_id}")
print("\n📤 Asking: 'What is your name? Tell me your current identity.'")
print("📥 Response: ", end="")
response = await send_and_get_response(
session, "What is your name? Tell me your current identity."
)
if "ALPHA" in response:
print(
" Without system_message: Model still remembers ALPHA from original session"
)
elif "BETA" in response:
print(" Without system_message: Model remembers BETA from Test 2")
else:
print(" Model identity unclear")
async def main():
print("=" * 60)
print("🧪 Copilot SDK System Message Resume Test")
print("=" * 60)
print(f"Time: {time.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Testing with SDK from: {CopilotClient.__module__}")
# Create client with explicit CLI path if provided
cli_path = os.environ.get("COPILOT_CLI_PATH")
client_config = {"log_level": "info"}
if cli_path:
client_config["cli_path"] = cli_path
client = CopilotClient(client_config)
try:
await client.start()
print("✅ Client started successfully")
# Test 1: Create new session with system message A
session = await test_new_session_system_message(client)
session_id = session.session_id
# Wait a bit before resuming
print("\n⏳ Waiting 2 seconds before resume test...")
await asyncio.sleep(2)
# Test 2: Resume with different system message B
bug_confirmed = await test_resume_session_with_new_system_message(
client, session_id
)
# Test 3: Resume without system message
await test_resume_without_system_message(client, session_id)
# Summary
print("\n" + "=" * 60)
print("📊 TEST SUMMARY (Native Copilot)")
print("=" * 60)
if bug_confirmed is False:
print(
"❌ BUG CONFIRMED: session.resume does NOT apply system_message updates"
)
print(" The system message from create_session persists even when")
print(" resume_session specifies a different system_message.")
print("\n WORKAROUND: Inject system context into user prompt instead.")
elif bug_confirmed is True:
print("✅ NO BUG: session.resume correctly updates system_message")
else:
print("⚠️ INCONCLUSIVE: Could not determine if bug exists")
except Exception as e:
print(f"❌ Error: {e}")
import traceback
traceback.print_exc()
finally:
await client.stop()
print("\n✅ Client stopped")
# =============================================================================
# BYOK OpenAI Test
# =============================================================================
async def test_byok_new_session(client: CopilotClient, provider_config: dict):
"""BYOK Test 1: New session with BYOK provider and system message A"""
print("\n" + "=" * 60)
print("BYOK TEST 1: New Session with BYOK Provider + System Message A (ALPHA)")
print("=" * 60)
print(
f"📋 Provider: {provider_config.get('type')} @ {provider_config.get('base_url')}"
)
session_config = SessionConfig(
session_id="byok-test-session-001",
model="gpt-4o", # or your model name
streaming=True,
provider=provider_config,
system_message={
"mode": "replace",
"content": SYSTEM_MSG_A,
},
)
session = await client.create_session(config=session_config)
print(f"✅ Created BYOK session: {session.session_id}")
print("\n📤 Asking: 'What is your name?'")
print("📥 Response: ", end="")
response = await send_and_get_response(session, "What is your name?")
if "ALPHA" in response:
print("✅ SUCCESS: Model correctly identified as ALPHA")
else:
print("⚠️ WARNING: Model did NOT identify as ALPHA")
return session
async def test_byok_resume_with_new_system_message(
client: CopilotClient, session_id: str, provider_config: dict
):
"""BYOK Test 2: Resume BYOK session with DIFFERENT system message B"""
print("\n" + "=" * 60)
print("BYOK TEST 2: Resume BYOK Session with System Message B (BETA)")
print("=" * 60)
resume_config = {
"model": "gpt-4o",
"streaming": True,
"provider": provider_config,
"system_message": {
"mode": "replace",
"content": SYSTEM_MSG_B,
},
}
print(f"📋 Resume config includes system_message with mode='replace'")
print(f"📋 New system_message content: BETA identity")
print(
f"📋 Provider: {provider_config.get('type')} @ {provider_config.get('base_url')}"
)
session = await client.resume_session(session_id, resume_config)
print(f"✅ Resumed BYOK session: {session.session_id}")
print("\n📤 Asking: 'What is your name now? Did your identity change?'")
print("📥 Response: ", end="")
response = await send_and_get_response(
session, "What is your name now? Did your identity change?"
)
if "BETA" in response:
print("✅ SUCCESS: System message was updated to BETA")
return True
elif "ALPHA" in response:
print("❌ BUG CONFIRMED: System message was NOT updated (still ALPHA)")
return False
else:
print("⚠️ INCONCLUSIVE: Model response doesn't clearly indicate identity")
return None
async def main_byok():
"""Run BYOK-specific tests"""
print("=" * 60)
print("🧪 Copilot SDK BYOK System Message Resume Test")
print("=" * 60)
print(f"Time: {time.strftime('%Y-%m-%d %H:%M:%S')}")
# Get BYOK configuration from environment
byok_api_key = os.environ.get("BYOK_API_KEY") or os.environ.get("OPENAI_API_KEY")
byok_base_url = os.environ.get("BYOK_BASE_URL", "https://api.openai.com/v1")
byok_model = os.environ.get("BYOK_MODEL", "gpt-4o")
if not byok_api_key:
print(
"❌ Error: Please set BYOK_API_KEY or OPENAI_API_KEY environment variable"
)
print(" export BYOK_API_KEY='your_api_key'")
print(" export BYOK_BASE_URL='https://api.openai.com/v1' # optional")
print(" export BYOK_MODEL='gpt-4o' # optional")
return
provider_config = {
"type": "openai",
"base_url": byok_base_url,
"api_key": byok_api_key,
}
print(f"📋 BYOK Provider: openai @ {byok_base_url}")
print(f"📋 BYOK Model: {byok_model}")
# Create client
cli_path = os.environ.get("COPILOT_CLI_PATH")
client_config = {"log_level": "info"}
if cli_path:
client_config["cli_path"] = cli_path
client = CopilotClient(client_config)
try:
await client.start()
print("✅ Client started successfully")
# BYOK Test 1: Create new session with BYOK provider
session = await test_byok_new_session(client, provider_config)
session_id = session.session_id
# Wait a bit before resuming
print("\n⏳ Waiting 2 seconds before resume test...")
await asyncio.sleep(2)
# BYOK Test 2: Resume with different system message B
bug_confirmed = await test_byok_resume_with_new_system_message(
client, session_id, provider_config
)
# Summary
print("\n" + "=" * 60)
print("📊 BYOK TEST SUMMARY")
print("=" * 60)
if bug_confirmed is False:
print(
"❌ BYOK BUG CONFIRMED: session.resume does NOT apply system_message updates"
)
print(" In BYOK mode, the system message from create_session persists")
print(" even when resume_session specifies a different system_message.")
print("\n WORKAROUND: Inject system context into user prompt instead.")
elif bug_confirmed is True:
print("✅ BYOK NO BUG: session.resume correctly updates system_message")
else:
print("⚠️ BYOK INCONCLUSIVE: Could not determine if bug exists")
except Exception as e:
print(f"❌ Error: {e}")
import traceback
traceback.print_exc()
finally:
await client.stop()
print("\n✅ Client stopped")
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Copilot SDK System Message Resume Test"
)
parser.add_argument(
"--byok",
action="store_true",
help="Run BYOK (Bring Your Own Key) test instead of native Copilot test",
)
args = parser.parse_args()
if args.byok:
print("Running BYOK test mode...")
asyncio.run(main_byok())
else:
print("Running native Copilot test mode...")
print("(Use --byok flag for BYOK provider test)")
asyncio.run(main())

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,103 @@
import os
import sys
import json
import asyncio
from typing import List, Dict
# 1. 尝试导入 OpenWebUI 环境
try:
from open_webui.models.models import Models, ModelForm, ModelMeta, ModelParams
from open_webui.internal.db import get_session
except ImportError:
print("❌ 错误: 无法导入 OpenWebUI 模块。请确保在 OpenWebUI 环境(如 conda ai中运行此脚本。")
sys.exit(1)
# 2. 导入 Copilot SDK
try:
from copilot import CopilotClient
except ImportError:
print("❌ 错误: 无法导入 copilot SDK。请运行: pip install github-copilot-sdk==0.1.23")
sys.exit(1)
async def fetch_real_models() -> List[Dict]:
gh_token = os.environ.get("GH_TOKEN")
if not gh_token:
print("❌ 错误: 未设置 GH_TOKEN 环境变量。")
return []
client = CopilotClient()
try:
await client.start()
raw_models = await client.list_models()
processed = []
for m in raw_models:
m_id = getattr(m, "id", str(m))
# 提取倍率
billing = getattr(m, "billing", {})
if not isinstance(billing, dict): billing = vars(billing)
multiplier = billing.get("multiplier", 1)
# 提取能力
cap = getattr(m, "capabilities", None)
vision = False
reasoning = False
if cap:
supports = getattr(cap, "supports", {})
if not isinstance(supports, dict): supports = vars(supports)
vision = supports.get("vision", False)
reasoning = supports.get("reasoning_effort", False)
processed.append({
"id": m_id,
"name": f"GitHub Copilot ({m_id})",
"vision": vision,
"reasoning": reasoning,
"multiplier": multiplier
})
return processed
except Exception as e:
print(f"❌ 获取模型失败: {e}")
return []
finally:
await client.stop()
async def sync_to_db():
models = await fetch_real_models()
if not models: return
print(f"🔄 发现 {len(models)} 个 Copilot 模型,正在同步到工作区...")
# 默认管理员 ID
admin_user_id = "00000000-0000-0000-0000-000000000000"
for m in models:
# 对应插件中的 ID 格式
full_id = f"copilot-{m['id']}"
existing = Models.get_model_by_id(full_id)
if existing:
print(f"⚠️ 跳过: {full_id} (已存在)")
continue
form_data = ModelForm(
id=full_id,
base_model_id=None,
name=m['name'],
meta=ModelMeta(
description=f"GitHub Copilot 官方模型。倍率: {m['multiplier']}x。支持推理: {m['reasoning']}",
capabilities={
"vision": m['vision'],
"reasoning": m['reasoning']
}
),
params=ModelParams()
)
try:
if Models.insert_new_model(form_data, admin_user_id):
print(f"✅ 成功同步: {m['name']}")
except Exception as e:
print(f"❌ 同步 {m['id']} 失败: {e}")
if __name__ == "__main__":
asyncio.run(sync_to_db())

View File

@@ -0,0 +1,111 @@
import asyncio
import os
import json
import time
from typing import Any
from datetime import datetime
from copilot import CopilotClient
# 自定义 JSON 编码器,处理 datetime 等对象
class SDKEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
if hasattr(obj, "to_dict"):
return obj.to_dict()
try:
return super().default(obj)
except TypeError:
return str(obj)
async def run_debug_test(client, model_id, effort, prompt):
print(f"\n" + "🔍" * 15)
print(f"DEBUGGING: {model_id} | {effort.upper()}")
print("🔍" * 15)
session_config = {
"model": model_id,
"reasoning_effort": effort
}
queue = asyncio.Queue()
done = asyncio.Event()
SENTINEL = object()
def handler(event):
try:
etype = getattr(event, "type", "unknown")
if hasattr(etype, "value"): etype = etype.value
raw_data = {}
# 优先尝试 SDK 自带的 to_dict
if hasattr(event, "to_dict"):
raw_data = event.to_dict()
elif hasattr(event, "data"):
data_obj = event.data
if isinstance(data_obj, dict):
raw_data = data_obj
elif hasattr(data_obj, "to_dict"):
raw_data = data_obj.to_dict()
else:
raw_data = {k: v for k, v in vars(data_obj).items() if not k.startswith('_')}
else:
raw_data = {k: v for k, v in vars(event).items() if not k.startswith('_')}
queue.put_nowait((etype, raw_data))
if etype in ["session.idle", "session.error"]:
done.set()
queue.put_nowait(SENTINEL)
except Exception as e:
print(f"Handler Error: {e}")
try:
session = await client.create_session(config=session_config)
unsubscribe = session.on(handler)
print(f"Sending prompt...")
asyncio.create_task(session.send({"prompt": prompt, "mode": "immediate"}))
event_count = 0
while True:
try:
item = await asyncio.wait_for(queue.get(), timeout=180)
if item is SENTINEL: break
etype, data = item
event_count += 1
# 打印所有事件,不进行过滤,这样我们可以看到完整的生命周期
print(f"\n[#{event_count} EVENT: {etype}]")
print(json.dumps(data, indent=2, ensure_ascii=False, cls=SDKEncoder))
except asyncio.TimeoutError:
print("\n⚠️ Timeout: No events for 180s")
break
unsubscribe()
await session.destroy()
except Exception as e:
print(f"❌ Session Failed: {e}")
async def main():
gh_token = os.environ.get("GH_TOKEN")
if not gh_token:
print("❌ Error: GH_TOKEN not set")
return
client = CopilotClient()
await client.start()
# 使用一个简单但需要思考的问题
prompt = "123 * 456 等于多少?请给出思考过程。"
target_model = "gpt-5-mini"
await run_debug_test(client, target_model, "high", prompt)
await client.stop()
if __name__ == "__main__":
asyncio.run(main())