Compare commits
41 Commits
v2026.02.0
...
v0.6.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a5261d184 | ||
|
|
8cdc7723d2 | ||
|
|
a167f51026 | ||
|
|
03ff69f9e0 | ||
|
|
62c69a9a41 | ||
|
|
9bf2a5d2a2 | ||
|
|
13aed46c05 | ||
|
|
02a1668979 | ||
|
|
9760ccb243 | ||
|
|
e2c705fe15 | ||
|
|
37c7bf73c0 | ||
|
|
447e791ab6 | ||
|
|
cde685a364 | ||
|
|
b00206b063 | ||
|
|
e0a838e512 | ||
|
|
b9f4f5f1d6 | ||
|
|
c1afd1fa23 | ||
|
|
eaeaadaf12 | ||
|
|
ee70c6629e | ||
|
|
692971c93c | ||
|
|
e17818bf6c | ||
|
|
c5c9fd9d57 | ||
|
|
5c9875d390 | ||
|
|
9185f88d40 | ||
|
|
1fcad993ea | ||
|
|
1594ea3a20 | ||
|
|
ee681ddad9 | ||
|
|
881597fd51 | ||
|
|
5772b1c65f | ||
|
|
3d4b4b96e8 | ||
|
|
409d2f663f | ||
|
|
de9948a5b0 | ||
|
|
0a51b4cfca | ||
|
|
f8eb096300 | ||
|
|
f6369a1591 | ||
|
|
81634f57fa | ||
|
|
70dc62aaab | ||
|
|
8762c05e04 | ||
|
|
db52ac1041 | ||
|
|
795ac34cd9 | ||
|
|
89a12a4fe8 |
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -46,6 +46,16 @@
|
||||
"ideas"
|
||||
]
|
||||
}
|
||||
,
|
||||
{
|
||||
"login": "abaroni",
|
||||
"name": "Alessandro Baroni",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/21365486?v=4",
|
||||
"profile": "https://github.com/abaroni",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
"skipCi": true,
|
||||
|
||||
3
.github/copilot-instructions.md
vendored
3
.github/copilot-instructions.md
vendored
@@ -889,6 +889,9 @@ Filter 实例是**单例 (Singleton)**。
|
||||
- [ ] 使用 logging 而非 print
|
||||
- [ ] 测试双语界面
|
||||
- [ ] **一致性检查**: 确保文档、代码、README 同步
|
||||
- [ ] **README 结构**:
|
||||
- **Key Capabilities** (英文) / **核心功能** (中文): 必须包含所有核心功能
|
||||
- **What's New** (英文) / **最新更新** (中文): 仅包含最新版本的变更信息
|
||||
|
||||
### 2. 🔄 一致性维护 (Consistency Maintenance)
|
||||
|
||||
|
||||
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -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
|
||||
|
||||
|
||||
86
README.md
86
README.md
@@ -1,48 +1,72 @@
|
||||
# OpenWebUI Extras
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
English | [中文](./README_CN.md)
|
||||
|
||||
A collection of enhancements, plugins, and prompts for [OpenWebUI](https://github.com/open-webui/open-webui), developed and curated for personal use to extend functionality and improve experience.
|
||||
|
||||
<!-- STATS_START -->
|
||||
## 📊 Community Stats
|
||||
|
||||
> 🕐 Auto-updated: 2026-02-07 09:38
|
||||
> 🕐 Auto-updated: 2026-02-09 23:25
|
||||
|
||||
| 👤 Author | 👥 Followers | ⭐ Points | 🏆 Contributions |
|
||||
|:---:|:---:|:---:|:---:|
|
||||
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **203** | **246** | **40** |
|
||||
| :---: | :---: | :---: | :---: |
|
||||
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **212** | **261** | **44** |
|
||||
|
||||
| 📝 Posts | ⬇️ Downloads | 👁️ Views | 👍 Upvotes | 💾 Saves |
|
||||
|:---:|:---:|:---:|:---:|:---:|
|
||||
| **20** | **3675** | **43326** | **213** | **253** |
|
||||
| :---: | :---: | :---: | :---: | :---: |
|
||||
| **21** | **3962** | **46159** | **223** | **267** |
|
||||
|
||||
### 🔥 Top 6 Popular Plugins
|
||||
|
||||
> 🕐 Auto-updated: 2026-02-07 09:38
|
||||
> 🕐 Auto-updated: 2026-02-09 23:25
|
||||
|
||||
| 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 | 922 | 8131 | 2026-01-28 |
|
||||
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.5.0 | 670 | 6311 | 2026-01-30 |
|
||||
| 🥉 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.4 | 370 | 2881 | 2026-02-07 |
|
||||
| 4️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.2.2 | 355 | 3627 | 2026-01-28 |
|
||||
| 5️⃣ | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 339 | 1611 | 2026-01-29 |
|
||||
| 6️⃣ | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | 1.2.4 | 313 | 4483 | 2026-01-29 |
|
||||
|
||||
*See full stats in [Community Stats Report](./docs/community-stats.md)*
|
||||
<!-- STATS_END -->
|
||||
|
||||
## 🌟 Star Features
|
||||
|
||||
### 1. [GitHub Copilot SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) [](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4)
|
||||
|
||||
**The ultimate Agent for OpenWebUI.** Supports native code execution (Python/Pandas), raw file analysis, and interactive artifacts.
|
||||
> [!TIP]
|
||||
> **No GitHub Copilot subscription required!** Supports **BYOK (Bring Your Own Key)** mode using your own OpenAI/Anthropic API keys.
|
||||
|
||||
### 2. [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) [](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a)
|
||||
|
||||
**Experience interactive thinking.** Seamlessly transforms complex chat sessions into structured, clickable mind maps for better visual modeling and rapid idea extraction.
|
||||
|
||||
### 3. [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) [](https://openwebui.com/posts/smart_infographic_ad6f0c7f)
|
||||
|
||||
**Professional data storytelling.** Converts raw information into sleek, boardroom-ready infographics powered by AntV, perfect for summarizing long-form content instantly.
|
||||
|
||||
### 4. [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) [](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315)
|
||||
|
||||
**High-fidelity reporting.** Export conversation history into professionally formatted Word documents with preserved headers, code blocks, and math formulas.
|
||||
|
||||
### 5. [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) [](https://openwebui.com/posts/async_context_compression_b1655bc8)
|
||||
|
||||
**Maximize your context window.** Intelligently compresses chat history using LLM logic to save tokens and costs while maintaining a high-quality reasoning chain.
|
||||
|
||||
## 📦 Project Contents
|
||||
|
||||
### 🧩 Plugins
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
<details>
|
||||
<summary><b>🧩 Plugins (Actions, Filters, Pipes, Pipelines)</b></summary>
|
||||
|
||||
Located in the `plugins/` directory, containing Python-based enhancements:
|
||||
|
||||
#### Actions
|
||||
### Actions
|
||||
|
||||
- **Smart Mind Map** (`smart-mind-map`): Generates interactive mind maps from text.
|
||||
- **Smart Infographic** (`infographic`): Transforms text into professional infographics using AntV.
|
||||
@@ -51,27 +75,36 @@ Located in the `plugins/` directory, containing Python-based enhancements:
|
||||
- **Export to Excel** (`export_to_excel`): Exports chat history to Excel files.
|
||||
- **Export to Word** (`export_to_docx`): Exports chat history to Word documents.
|
||||
|
||||
#### Filters
|
||||
### Filters
|
||||
|
||||
- **GitHub Copilot SDK Files Filter** (`github_copilot_sdk_files_filter`): Essential companion for Copilot SDK. Bypasses RAG to ensure full file accessibility for Agents.
|
||||
- **Web Gemini Multimodal Filter** (`web_gemini_multimodel_filter`): Adds multimodal capabilities (PDF, Video, Office) to any model with intelligent routing.
|
||||
- **Async Context Compression** (`async-context-compression`): Optimizes token usage via context compression.
|
||||
- **Context Enhancement** (`context_enhancement_filter`): Enhances chat context.
|
||||
- **Folder Memory** (`folder-memory`): Automatically extracts project rules from conversations and injects them into the folder's system prompt.
|
||||
- **Markdown Normalizer** (`markdown_normalizer`): Fixes common Markdown formatting issues in LLM outputs.
|
||||
|
||||
#### Pipes
|
||||
### Pipes
|
||||
|
||||
- **GitHub Copilot SDK** (`github-copilot-sdk`): Official GitHub Copilot SDK integration. Supports dynamic models, multi-turn conversation, streaming, multimodal input, and infinite sessions.
|
||||
- **GitHub Copilot SDK** (`github-copilot-sdk`): Official GitHub Copilot SDK integration. Supports dynamic models (GPT-4o, Claude 3.5, o1), multi-turn conversation, streaming, and infinite sessions.
|
||||
|
||||
#### Pipelines
|
||||
### Pipelines
|
||||
|
||||
- **MoE Prompt Refiner** (`moe_prompt_refiner`): Refines prompts for Mixture of Experts (MoE) summary requests to generate high-quality comprehensive reports.
|
||||
|
||||
### 🎯 Prompts
|
||||
</details>
|
||||
<!-- markdownlint-enable MD033 -->
|
||||
|
||||
Located in the `prompts/` directory, containing fine-tuned System Prompts:
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
<details>
|
||||
<summary><b>🎯 Prompts (System Prompts for various roles)</b></summary>
|
||||
|
||||
- **Coding**: Programming assistance prompts.
|
||||
- **Marketing**: Marketing and copywriting prompts.
|
||||
System Prompts are managed in the `docs/prompts/` directory:
|
||||
|
||||
- **[Prompt Library](./docs/prompts/library.md)**: A curated collection of fine-tuned prompts for Coding, Marketing, and Analysis.
|
||||
|
||||
</details>
|
||||
<!-- markdownlint-enable MD033 -->
|
||||
|
||||
## 🛠️ Extensions
|
||||
|
||||
@@ -135,6 +168,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://trade.xyz/?ref=BZ1RJRXWO"><img src="https://avatars.githubusercontent.com/u/7317522?v=4?s=100" width="100px;" alt="Raxxoor"/><br /><sub><b>Raxxoor</b></sub></a><br /><a href="https://github.com/Fu-Jie/awesome-openwebui/issues?q=author%3Adhaern" title="Bug reports">🐛</a> <a href="#ideas-dhaern" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/i-iooi-i"><img src="https://avatars.githubusercontent.com/u/1827701?v=4?s=100" width="100px;" alt="ZOLO"/><br /><sub><b>ZOLO</b></sub></a><br /><a href="https://github.com/Fu-Jie/awesome-openwebui/issues?q=author%3Ai-iooi-i" title="Bug reports">🐛</a> <a href="#ideas-i-iooi-i" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://perso.crans.org/grande/"><img src="https://avatars.githubusercontent.com/u/469017?v=4?s=100" width="100px;" alt="Johan Grande"/><br /><sub><b>Johan Grande</b></sub></a><br /><a href="#ideas-nahoj" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/abaroni"><img src="https://avatars.githubusercontent.com/u/21365486?v=4?s=100" width="100px;" alt="Alessandro Baroni"/><br /><sub><b>Alessandro Baroni</b></sub></a><br /><a href="#ideas-abaroni" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
118
README_CN.md
118
README_CN.md
@@ -7,39 +7,65 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
|
||||
<!-- STATS_START -->
|
||||
## 📊 社区统计
|
||||
|
||||
> 🕐 自动更新于 2026-02-07 09:38
|
||||
> 🕐 自动更新于 2026-02-09 23:25
|
||||
|
||||
| 👤 作者 | 👥 粉丝 | ⭐ 积分 | 🏆 贡献 |
|
||||
|:---:|:---:|:---:|:---:|
|
||||
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **203** | **246** | **40** |
|
||||
| :---: | :---: | :---: | :---: |
|
||||
| [Fu-Jie](https://openwebui.com/u/Fu-Jie) | **212** | **261** | **44** |
|
||||
|
||||
| 📝 发布 | ⬇️ 下载 | 👁️ 浏览 | 👍 点赞 | 💾 收藏 |
|
||||
|:---:|:---:|:---:|:---:|:---:|
|
||||
| **20** | **3675** | **43326** | **213** | **253** |
|
||||
| :---: | :---: | :---: | :---: | :---: |
|
||||
| **21** | **3962** | **46159** | **223** | **267** |
|
||||
|
||||
### 🔥 热门插件 Top 6
|
||||
|
||||
> 🕐 自动更新于 2026-02-07 09:38
|
||||
> 🕐 自动更新于 2026-02-09 23:25
|
||||
|
||||
| 排名 | 插件 | 版本 | 下载 | 浏览 | 更新日期 |
|
||||
|:---:|------|:---:|:---:|:---:|:---:|
|
||||
| 🥇 | [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 | 922 | 8131 | 2026-01-28 |
|
||||
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | 1.5.0 | 670 | 6311 | 2026-01-30 |
|
||||
| 🥉 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | 0.4.4 | 370 | 2881 | 2026-02-07 |
|
||||
| 4️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | 1.2.2 | 355 | 3627 | 2026-01-28 |
|
||||
| 5️⃣ | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | 0.3.7 | 339 | 1611 | 2026-01-29 |
|
||||
| 6️⃣ | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | 1.2.4 | 313 | 4483 | 2026-01-29 |
|
||||
|
||||
*完整统计请查看 [社区统计报告](./docs/community-stats.zh.md)*
|
||||
<!-- STATS_END -->
|
||||
|
||||
## 🌟 精选功能
|
||||
|
||||
### 1. [GitHub Copilot SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) [](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4)
|
||||
|
||||
**OpenWebUI 终极 Agent 增强。** 支持原生代码执行(Python/Pandas)、原始文件直接分析以及交互式 Artifacts。
|
||||
> [!TIP]
|
||||
> **无需 GitHub Copilot 订阅!** 支持 **BYOK (Bring Your Own Key)** 模式,使用你自己的 OpenAI/Anthropic API Key。
|
||||
|
||||
### 2. [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) [](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a)
|
||||
|
||||
**体验浸入式思维。** 将复杂的对话瞬间转化为结构化、可点击的交互式思维导图,助力知识建模与逻辑提取。
|
||||
|
||||
### 3. [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) [](https://openwebui.com/posts/smart_infographic_ad6f0c7f)
|
||||
|
||||
**专业数据叙事。** 将零散信息转化为精美的信息图表(由 AntV 驱动),一键生成学术/汇报级的可视化总结。
|
||||
|
||||
### 4. [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) [](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315)
|
||||
|
||||
**高保真文档导出。** 将对话历史导出为格式完美的 Word 文档,完美保留标题、代码块、LaTeX 公式及 Mermaid 流程图。
|
||||
|
||||
### 5. [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) [](https://openwebui.com/posts/async_context_compression_b1655bc8)
|
||||
|
||||
**挑战 Token 極限。** 采用多专家异步压缩逻辑,在保持高吞吐量推理链的同时,大幅降低 Token 消耗。
|
||||
|
||||
## 📦 项目内容
|
||||
|
||||
### 🧩 插件 (Plugins)
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
<details>
|
||||
<summary><b>🧩 插件 (Actions, Filters, Pipes, Pipelines)</b></summary>
|
||||
|
||||
位于 `plugins/` 目录,包含各类 Python 编写的功能增强插件:
|
||||
|
||||
#### Actions (交互增强)
|
||||
### Actions (交互增强)
|
||||
|
||||
- **Smart Mind Map** (`smart-mind-map`): 智能分析文本并生成交互式思维导图。
|
||||
- **Smart Infographic** (`infographic`): 基于 AntV 的智能信息图生成工具。
|
||||
@@ -48,33 +74,36 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
|
||||
- **Export to Excel** (`export_to_excel`): 将对话内容导出为 Excel 文件。
|
||||
- **Export to Word** (`export_to_docx`): 将对话内容导出为 Word 文档。
|
||||
|
||||
#### Filters (消息处理)
|
||||
### Filters (消息处理)
|
||||
|
||||
- **GitHub Copilot SDK Files Filter** (`github_copilot_sdk_files_filter`): Copilot SDK 必备搭档。绕过 RAG,确保 Agent 能真正看到你的每一个文件。
|
||||
- **Web Gemini Multimodal Filter** (`web_gemini_multimodel_filter`): 为任意模型提供多模态能力(PDF、Office、视频等),支持智能路由。
|
||||
- **Async Context Compression** (`async-context-compression`): 异步上下文压缩,优化 Token 使用。
|
||||
- **Context Enhancement** (`context_enhancement_filter`): 上下文增强过滤器。
|
||||
- **Folder Memory** (`folder-memory`): 自动从对话中提取项目规则并注入到文件夹系统提示词中。
|
||||
- **Gemini Manifold Companion** (`gemini_manifold_companion`): Gemini Manifold 配套增强。
|
||||
- **Gemini Multimodal Filter** (`web_gemini_multimodel_filter`): 为任意模型提供多模态能力(PDF、Office、视频等),支持智能路由和字幕精修。
|
||||
- **Markdown Normalizer** (`markdown_normalizer`): 修复 LLM 输出中常见的 Markdown 格式问题。
|
||||
- **Multi-Model Context Merger** (`multi_model_context_merger`): 自动合并并注入多模型回答的上下文。
|
||||
|
||||
#### Pipes (模型管道)
|
||||
### Pipes (模型管道)
|
||||
|
||||
- **GitHub Copilot SDK** (`github-copilot-sdk`): GitHub Copilot SDK 官方集成。支持动态模型、多轮对话、流式输出、图片输入及无限会话。
|
||||
- **Gemini Manifold** (`gemini_mainfold`): 集成 Gemini 模型的管道。
|
||||
|
||||
#### Pipelines (工作流管道)
|
||||
### Pipelines (工作流管道)
|
||||
|
||||
- **MoE Prompt Refiner** (`moe_prompt_refiner`): 优化多模型 (MoE) 汇总请求的提示词,生成高质量的综合报告。
|
||||
|
||||
### 🎯 提示词 (Prompts)
|
||||
</details>
|
||||
<!-- markdownlint-enable MD033 -->
|
||||
|
||||
位于 `prompts/` 目录,包含精心调优的 System Prompts:
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
<details>
|
||||
<summary><b>🎯 提示词 (Prompts - 多角色系统提示词)</b></summary>
|
||||
|
||||
- **Coding**: 编程辅助类提示词。
|
||||
- **Marketing**: 营销文案类提示词。
|
||||
位于 `docs/prompts/` 目录,包含精心调优的提示词集合:
|
||||
|
||||
每个提示词都独立保存为 Markdown 文件,可直接在 OpenWebUI 中使用。
|
||||
- **[Prompt Library](./docs/prompts/library.md)**: 编程、翻译、分析及营销等全领域提示词精选。
|
||||
|
||||
</details>
|
||||
<!-- markdownlint-enable MD033 -->
|
||||
|
||||
## 🛠️ 扩展 (Extensions)
|
||||
|
||||
@@ -84,6 +113,10 @@ Open WebUI 的前端增强扩展:
|
||||
|
||||
## 📖 开发文档
|
||||
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
<details>
|
||||
<summary><b>📚 官方开发与运营指南</b></summary>
|
||||
|
||||
位于 `docs/zh/` 目录:
|
||||
|
||||
- **[插件开发权威指南](./docs/zh/plugin_development_guide.md)** - 整合了入门教程、核心 SDK 详解及最佳实践的系统化指南。 ⭐
|
||||
@@ -91,36 +124,11 @@ Open WebUI 的前端增强扩展:
|
||||
|
||||
更多示例请查看 `docs/examples/` 目录。
|
||||
|
||||
</details>
|
||||
<!-- markdownlint-enable MD033 -->
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
本项目是一个资源集合,无需安装 Python 环境。你只需要下载对应的文件并导入到你的 OpenWebUI 实例中即可。
|
||||
|
||||
### 使用提示词 (Prompts)
|
||||
|
||||
1. 在 `/prompts` 目录中浏览并选择你感兴趣的提示词文件 (`.md`)。
|
||||
2. 复制文件内容。
|
||||
3. 在 OpenWebUI 聊天界面中,点击输入框上方的 "Prompt" 按钮。
|
||||
4. 粘贴内容并保存。
|
||||
|
||||
### 使用插件 (Plugins)
|
||||
|
||||
1. **从 OpenWebUI 社区安装 (推荐)**:
|
||||
- 访问我的主页: [Fu-Jie's Profile](https://openwebui.com/u/Fu-Jie)
|
||||
- 浏览插件列表,选择你喜欢的插件。
|
||||
- 点击 "Get" 按钮,将其直接导入到你的 OpenWebUI 实例中。
|
||||
|
||||
2. **手动安装**:
|
||||
- 在 `/plugins` 目录中浏览并下载你需要的插件文件 (`.py`)。
|
||||
- 打开 OpenWebUI 的 **管理员面板 (Admin Panel)** -> **设置 (Settings)** -> **插件 (Plugins)**。
|
||||
- 点击上传按钮,选择刚才下载的 `.py` 文件。
|
||||
- 上传成功后,刷新页面,你就可以在聊天设置或工具栏中启用该插件了。
|
||||
|
||||
### 贡献代码
|
||||
|
||||
如果你有优质的提示词或插件想要分享:
|
||||
|
||||
1. Fork 本仓库。
|
||||
2. 将你的文件添加到对应的 `prompts/` 或 `plugins/` 目录。
|
||||
3. 提交 Pull Request。
|
||||
|
||||
[贡献指南](./CONTRIBUTING_CN.md) | [更新日志](./CHANGELOG.md)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "downloads",
|
||||
"message": "3.7k",
|
||||
"message": "4.0k",
|
||||
"color": "blue",
|
||||
"namedLogo": "openwebui"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "followers",
|
||||
"message": "203",
|
||||
"message": "212",
|
||||
"color": "blue"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "plugins",
|
||||
"message": "20",
|
||||
"message": "21",
|
||||
"color": "green"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "points",
|
||||
"message": "246",
|
||||
"message": "261",
|
||||
"color": "orange"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "upvotes",
|
||||
"message": "213",
|
||||
"message": "223",
|
||||
"color": "brightgreen"
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"total_posts": 20,
|
||||
"total_downloads": 3675,
|
||||
"total_views": 43326,
|
||||
"total_upvotes": 213,
|
||||
"total_posts": 21,
|
||||
"total_downloads": 3962,
|
||||
"total_views": 46159,
|
||||
"total_upvotes": 223,
|
||||
"total_downvotes": 2,
|
||||
"total_saves": 253,
|
||||
"total_comments": 46,
|
||||
"total_saves": 267,
|
||||
"total_comments": 51,
|
||||
"by_type": {
|
||||
"action": 15,
|
||||
"unknown": 4,
|
||||
"filter": 1
|
||||
"filter": 2,
|
||||
"unknown": 4
|
||||
},
|
||||
"posts": [
|
||||
{
|
||||
@@ -19,10 +19,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": 922,
|
||||
"views": 8131,
|
||||
"upvotes": 22,
|
||||
"saves": 50,
|
||||
"comments": 13,
|
||||
"created_at": "2025-12-30",
|
||||
"updated_at": "2026-01-28",
|
||||
@@ -35,47 +35,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": 670,
|
||||
"views": 6311,
|
||||
"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": 370,
|
||||
"views": 2881,
|
||||
"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 +67,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": 355,
|
||||
"views": 3627,
|
||||
"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": 339,
|
||||
"views": 1611,
|
||||
"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 +99,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": 313,
|
||||
"views": 4483,
|
||||
"upvotes": 17,
|
||||
"saves": 27,
|
||||
"saves": 28,
|
||||
"comments": 5,
|
||||
"created_at": "2026-01-12",
|
||||
"updated_at": "2026-01-29",
|
||||
@@ -115,8 +115,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": 219,
|
||||
"views": 3321,
|
||||
"upvotes": 13,
|
||||
"saves": 14,
|
||||
"comments": 2,
|
||||
@@ -131,10 +131,10 @@
|
||||
"version": "",
|
||||
"author": "",
|
||||
"description": "",
|
||||
"downloads": 141,
|
||||
"views": 2180,
|
||||
"upvotes": 8,
|
||||
"saves": 3,
|
||||
"downloads": 180,
|
||||
"views": 2530,
|
||||
"upvotes": 9,
|
||||
"saves": 5,
|
||||
"comments": 0,
|
||||
"created_at": "2026-01-28",
|
||||
"updated_at": "2026-01-28",
|
||||
@@ -147,8 +147,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": 146,
|
||||
"views": 1232,
|
||||
"upvotes": 6,
|
||||
"saves": 11,
|
||||
"comments": 0,
|
||||
@@ -157,36 +157,36 @@
|
||||
"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": 124,
|
||||
"views": 2171,
|
||||
"upvotes": 13,
|
||||
"saves": 6,
|
||||
"saves": 7,
|
||||
"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": "智能信息图",
|
||||
"slug": "智能信息图_e04a48ff",
|
||||
"title": "GitHub Copilot Official SDK Pipe",
|
||||
"slug": "github_copilot_official_sdk_pipe_ce96f7b4",
|
||||
"type": "action",
|
||||
"version": "1.5.0",
|
||||
"version": "0.6.0",
|
||||
"author": "Fu-Jie",
|
||||
"description": "基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。",
|
||||
"downloads": 56,
|
||||
"views": 1003,
|
||||
"upvotes": 10,
|
||||
"saves": 1,
|
||||
"comments": 0,
|
||||
"created_at": "2025-12-28",
|
||||
"updated_at": "2026-01-29",
|
||||
"url": "https://openwebui.com/posts/智能信息图_e04a48ff"
|
||||
"description": "Integrate GitHub Copilot SDK. Supports dynamic models, multi-turn conversation, streaming, multimodal input, infinite sessions, and frontend debug logging.",
|
||||
"downloads": 83,
|
||||
"views": 2162,
|
||||
"upvotes": 13,
|
||||
"saves": 7,
|
||||
"comments": 4,
|
||||
"created_at": "2026-01-26",
|
||||
"updated_at": "2026-02-09",
|
||||
"url": "https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4"
|
||||
},
|
||||
{
|
||||
"title": "📂 Folder Memory – Auto-Evolving Project Context",
|
||||
@@ -195,8 +195,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": 59,
|
||||
"views": 1264,
|
||||
"upvotes": 6,
|
||||
"saves": 8,
|
||||
"comments": 0,
|
||||
@@ -205,20 +205,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",
|
||||
"title": "智能信息图",
|
||||
"slug": "智能信息图_e04a48ff",
|
||||
"type": "action",
|
||||
"version": "0.2.3",
|
||||
"version": "1.5.0",
|
||||
"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",
|
||||
"description": "基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。",
|
||||
"downloads": 58,
|
||||
"views": 1040,
|
||||
"upvotes": 10,
|
||||
"saves": 1,
|
||||
"comments": 0,
|
||||
"created_at": "2025-12-28",
|
||||
"updated_at": "2026-01-29",
|
||||
"url": "https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4"
|
||||
"url": "https://openwebui.com/posts/智能信息图_e04a48ff"
|
||||
},
|
||||
{
|
||||
"title": "思维导图",
|
||||
@@ -228,7 +228,7 @@
|
||||
"author": "Fu-Jie",
|
||||
"description": "智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。",
|
||||
"downloads": 37,
|
||||
"views": 563,
|
||||
"views": 577,
|
||||
"upvotes": 6,
|
||||
"saves": 2,
|
||||
"comments": 0,
|
||||
@@ -243,10 +243,10 @@
|
||||
"version": "1.2.2",
|
||||
"author": "Fu-Jie",
|
||||
"description": "通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。",
|
||||
"downloads": 31,
|
||||
"views": 624,
|
||||
"downloads": 33,
|
||||
"views": 650,
|
||||
"upvotes": 7,
|
||||
"saves": 4,
|
||||
"saves": 5,
|
||||
"comments": 0,
|
||||
"created_at": "2025-11-08",
|
||||
"updated_at": "2026-01-28",
|
||||
@@ -260,7 +260,7 @@
|
||||
"author": "Fu-Jie",
|
||||
"description": "快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类。",
|
||||
"downloads": 27,
|
||||
"views": 659,
|
||||
"views": 675,
|
||||
"upvotes": 8,
|
||||
"saves": 1,
|
||||
"comments": 0,
|
||||
@@ -275,8 +275,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": "Fu-Jie",
|
||||
"description": "全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。",
|
||||
"downloads": 17,
|
||||
"views": 400,
|
||||
"downloads": 24,
|
||||
"views": 423,
|
||||
"upvotes": 5,
|
||||
"saves": 1,
|
||||
"comments": 0,
|
||||
@@ -284,6 +284,22 @@
|
||||
"updated_at": "2026-01-08",
|
||||
"url": "https://openwebui.com/posts/精读_99830b0f"
|
||||
},
|
||||
{
|
||||
"title": "GitHub Copilot SDK Files Filter",
|
||||
"slug": "github_copilot_sdk_files_filter_403a62ee",
|
||||
"type": "filter",
|
||||
"version": "0.1.2",
|
||||
"author": "Fu-Jie",
|
||||
"description": "A specialized filter to bypass OpenWebUI's default RAG for GitHub Copilot SDK models. It moves uploaded files to a safe location ('copilot_files') so the Copilot Pipe can process them natively without interference.",
|
||||
"downloads": 3,
|
||||
"views": 47,
|
||||
"upvotes": 1,
|
||||
"saves": 0,
|
||||
"comments": 0,
|
||||
"created_at": "2026-02-09",
|
||||
"updated_at": "2026-02-09",
|
||||
"url": "https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee"
|
||||
},
|
||||
{
|
||||
"title": "🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager",
|
||||
"slug": "open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e",
|
||||
@@ -292,10 +308,10 @@
|
||||
"author": "",
|
||||
"description": "",
|
||||
"downloads": 0,
|
||||
"views": 1421,
|
||||
"upvotes": 11,
|
||||
"saves": 16,
|
||||
"comments": 7,
|
||||
"views": 1468,
|
||||
"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 +324,7 @@
|
||||
"author": "",
|
||||
"description": "",
|
||||
"downloads": 0,
|
||||
"views": 134,
|
||||
"views": 140,
|
||||
"upvotes": 2,
|
||||
"saves": 0,
|
||||
"comments": 0,
|
||||
@@ -324,9 +340,9 @@
|
||||
"author": "",
|
||||
"description": "",
|
||||
"downloads": 0,
|
||||
"views": 1391,
|
||||
"views": 1415,
|
||||
"upvotes": 14,
|
||||
"saves": 9,
|
||||
"saves": 10,
|
||||
"comments": 2,
|
||||
"created_at": "2026-01-10",
|
||||
"updated_at": "2026-01-10",
|
||||
@@ -338,11 +354,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": 212,
|
||||
"following": 4,
|
||||
"total_points": 246,
|
||||
"post_points": 211,
|
||||
"comment_points": 35,
|
||||
"contributions": 40
|
||||
"total_points": 261,
|
||||
"post_points": 221,
|
||||
"comment_points": 40,
|
||||
"contributions": 44
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,46 @@
|
||||
# 📊 OpenWebUI Community Stats Report
|
||||
|
||||
> 📅 Updated: 2026-02-07 09:38
|
||||
> 📅 Updated: 2026-02-09 23:25
|
||||
|
||||
## 📈 Overview
|
||||
|
||||
| Metric | Value |
|
||||
|------|------|
|
||||
| 📝 Total Posts | 20 |
|
||||
| ⬇️ Total Downloads | 3675 |
|
||||
| 👁️ Total Views | 43326 |
|
||||
| 👍 Total Upvotes | 213 |
|
||||
| 💾 Total Saves | 253 |
|
||||
| 💬 Total Comments | 46 |
|
||||
| 📝 Total Posts | 21 |
|
||||
| ⬇️ Total Downloads | 3962 |
|
||||
| 👁️ Total Views | 46159 |
|
||||
| 👍 Total Upvotes | 223 |
|
||||
| 💾 Total Saves | 267 |
|
||||
| 💬 Total Comments | 51 |
|
||||
|
||||
## 📂 By Type
|
||||
|
||||
- **action**: 15
|
||||
- **filter**: 2
|
||||
- **unknown**: 4
|
||||
- **filter**: 1
|
||||
|
||||
## 📋 Posts List
|
||||
|
||||
| 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 | 922 | 8131 | 22 | 50 | 2026-01-28 |
|
||||
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.5.0 | 670 | 6311 | 24 | 34 | 2026-01-30 |
|
||||
| 3 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.4 | 370 | 2881 | 14 | 26 | 2026-02-07 |
|
||||
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.2.2 | 355 | 3627 | 14 | 33 | 2026-01-28 |
|
||||
| 5 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 339 | 1611 | 7 | 6 | 2026-01-29 |
|
||||
| 6 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.4 | 313 | 4483 | 17 | 28 | 2026-01-29 |
|
||||
| 7 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 219 | 3321 | 13 | 14 | 2026-01-28 |
|
||||
| 8 | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | unknown | | 180 | 2530 | 9 | 5 | 2026-01-28 |
|
||||
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 146 | 1232 | 6 | 11 | 2026-01-08 |
|
||||
| 10 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.4 | 124 | 2171 | 13 | 7 | 2026-02-07 |
|
||||
| 11 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | action | 0.6.0 | 83 | 2162 | 13 | 7 | 2026-02-09 |
|
||||
| 12 | [📂 Folder Memory – Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | 0.1.0 | 59 | 1264 | 6 | 8 | 2026-01-20 |
|
||||
| 13 | [智能信息图](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.5.0 | 58 | 1040 | 10 | 1 | 2026-01-29 |
|
||||
| 14 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.2 | 37 | 577 | 6 | 2 | 2026-01-28 |
|
||||
| 15 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.2.2 | 33 | 650 | 7 | 5 | 2026-01-28 |
|
||||
| 16 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 27 | 675 | 8 | 1 | 2026-01-28 |
|
||||
| 17 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 24 | 423 | 5 | 1 | 2026-01-08 |
|
||||
| 18 | [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee) | filter | 0.1.2 | 3 | 47 | 1 | 0 | 2026-02-09 |
|
||||
| 19 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | unknown | | 0 | 1468 | 12 | 18 | 2026-01-28 |
|
||||
| 20 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 140 | 2 | 0 | 2026-01-14 |
|
||||
| 21 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 1415 | 14 | 10 | 2026-01-10 |
|
||||
|
||||
@@ -1,45 +1,46 @@
|
||||
# 📊 OpenWebUI 社区统计报告
|
||||
|
||||
> 📅 更新时间: 2026-02-07 09:38
|
||||
> 📅 更新时间: 2026-02-09 23:25
|
||||
|
||||
## 📈 总览
|
||||
|
||||
| 指标 | 数值 |
|
||||
|------|------|
|
||||
| 📝 发布数量 | 20 |
|
||||
| ⬇️ 总下载量 | 3675 |
|
||||
| 👁️ 总浏览量 | 43326 |
|
||||
| 👍 总点赞数 | 213 |
|
||||
| 💾 总收藏数 | 253 |
|
||||
| 💬 总评论数 | 46 |
|
||||
| 📝 发布数量 | 21 |
|
||||
| ⬇️ 总下载量 | 3962 |
|
||||
| 👁️ 总浏览量 | 46159 |
|
||||
| 👍 总点赞数 | 223 |
|
||||
| 💾 总收藏数 | 267 |
|
||||
| 💬 总评论数 | 51 |
|
||||
|
||||
## 📂 按类型分类
|
||||
|
||||
- **action**: 15
|
||||
- **filter**: 2
|
||||
- **unknown**: 4
|
||||
- **filter**: 1
|
||||
|
||||
## 📋 发布列表
|
||||
|
||||
| 排名 | 标题 | 类型 | 版本 | 下载 | 浏览 | 点赞 | 收藏 | 更新日期 |
|
||||
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| 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 | 922 | 8131 | 22 | 50 | 2026-01-28 |
|
||||
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | 1.5.0 | 670 | 6311 | 24 | 34 | 2026-01-30 |
|
||||
| 3 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | 0.4.4 | 370 | 2881 | 14 | 26 | 2026-02-07 |
|
||||
| 4 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | action | 1.2.2 | 355 | 3627 | 14 | 33 | 2026-01-28 |
|
||||
| 5 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | 0.3.7 | 339 | 1611 | 7 | 6 | 2026-01-29 |
|
||||
| 6 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | action | 1.2.4 | 313 | 4483 | 17 | 28 | 2026-01-29 |
|
||||
| 7 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | 0.2.4 | 219 | 3321 | 13 | 14 | 2026-01-28 |
|
||||
| 8 | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | unknown | | 180 | 2530 | 9 | 5 | 2026-01-28 |
|
||||
| 9 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | 1.0.0 | 146 | 1232 | 6 | 11 | 2026-01-08 |
|
||||
| 10 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | 0.4.4 | 124 | 2171 | 13 | 7 | 2026-02-07 |
|
||||
| 11 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | action | 0.6.0 | 83 | 2162 | 13 | 7 | 2026-02-09 |
|
||||
| 12 | [📂 Folder Memory – Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | 0.1.0 | 59 | 1264 | 6 | 8 | 2026-01-20 |
|
||||
| 13 | [智能信息图](https://openwebui.com/posts/智能信息图_e04a48ff) | action | 1.5.0 | 58 | 1040 | 10 | 1 | 2026-01-29 |
|
||||
| 14 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | 0.9.2 | 37 | 577 | 6 | 2 | 2026-01-28 |
|
||||
| 15 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | 1.2.2 | 33 | 650 | 7 | 5 | 2026-01-28 |
|
||||
| 16 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | 0.2.4 | 27 | 675 | 8 | 1 | 2026-01-28 |
|
||||
| 17 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | 1.0.0 | 24 | 423 | 5 | 1 | 2026-01-08 |
|
||||
| 18 | [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee) | filter | 0.1.2 | 3 | 47 | 1 | 0 | 2026-02-09 |
|
||||
| 19 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | unknown | | 0 | 1468 | 12 | 18 | 2026-01-28 |
|
||||
| 20 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | unknown | | 0 | 140 | 2 | 0 | 2026-01-14 |
|
||||
| 21 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | unknown | | 0 | 1415 | 14 | 10 | 2026-01-10 |
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -17,27 +17,27 @@ Actions are interactive plugins that:
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-brain:{ .lg .middle } **Smart Mind Map**
|
||||
- :material-brain:{ .lg .middle } **Smart Mind Map**
|
||||
|
||||
---
|
||||
|
||||
Intelligently analyzes text content and generates interactive mind maps with beautiful visualizations.
|
||||
|
||||
**Version:** 0.9.1
|
||||
**Version:** 0.9.2
|
||||
|
||||
[:octicons-arrow-right-24: Documentation](smart-mind-map.md)
|
||||
|
||||
- :material-chart-bar:{ .lg .middle } **Smart Infographic**
|
||||
- :material-chart-bar:{ .lg .middle } **Smart Infographic**
|
||||
|
||||
---
|
||||
|
||||
Transform text into professional infographics using AntV visualization engine with various templates.
|
||||
|
||||
**Version:** 1.4.9
|
||||
**Version:** 1.5.0
|
||||
|
||||
[: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>
|
||||
|
||||
---
|
||||
|
||||
@@ -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>
|
||||
|
||||
---
|
||||
|
||||
53
docs/plugins/filters/github-copilot-sdk-files-filter.md
Normal file
53
docs/plugins/filters/github-copilot-sdk-files-filter.md
Normal 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.
|
||||
53
docs/plugins/filters/github-copilot-sdk-files-filter.zh.md
Normal file
53
docs/plugins/filters/github-copilot-sdk-files-filter.zh.md
Normal 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`,以便文件自动搬运功能正常工作。
|
||||
@@ -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>
|
||||
|
||||
---
|
||||
|
||||
@@ -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>
|
||||
|
||||
---
|
||||
|
||||
80
docs/plugins/pipes/github-copilot-sdk-deep-dive.md
Normal file
80
docs/plugins/pipes/github-copilot-sdk-deep-dive.md
Normal 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.
|
||||
80
docs/plugins/pipes/github-copilot-sdk-deep-dive.zh.md
Normal file
80
docs/plugins/pipes/github-copilot-sdk-deep-dive.zh.md
Normal 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 输出,以获得更好的执行稳定性。
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,支持配置文档标题的对齐方式(左对齐、居中、右对齐)。
|
||||
|
||||
## ✨ 核心特性
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 📊 Export to Excel
|
||||
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.3.6 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie/awesome-openwebui) | **Version:** 0.3.7 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui) | **License:** MIT
|
||||
|
||||
Export chat history to an Excel (.xlsx) file directly from the chat interface.
|
||||
|
||||
|
||||
98
plugins/debug/copilot_sdk_research/GUIDE_ERRATA.md
Normal file
98
plugins/debug/copilot_sdk_research/GUIDE_ERRATA.md
Normal 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
|
||||
# ✅ 正确:使用 fullCommandText(shell)或 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 权限与确认章节
|
||||
173
plugins/debug/copilot_sdk_research/PERMISSION_TEST_REPORT.md
Normal file
173
plugins/debug/copilot_sdk_research/PERMISSION_TEST_REPORT.md
Normal 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
|
||||
**审核状态**: ✅ 已验证
|
||||
@@ -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_token,CLI 会自动读取环境变量
|
||||
})
|
||||
|
||||
# 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。
|
||||
620
plugins/debug/copilot_sdk_research/copilot_sdk_guide.py
Normal file
620
plugins/debug/copilot_sdk_research/copilot_sdk_guide.py
Normal 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()
|
||||
2
plugins/debug/copilot_sdk_research/dummy_test_file.txt
Normal file
2
plugins/debug/copilot_sdk_research/dummy_test_file.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
This is a dummy test file for verifying permission controls.
|
||||
You can delete this file safely.
|
||||
26
plugins/debug/copilot_sdk_research/inspect_permissions.py
Normal file
26
plugins/debug/copilot_sdk_research/inspect_permissions.py
Normal 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")
|
||||
@@ -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` 事件,权限确认由 JSON‑RPC `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 本身没有 device‑flow 登录 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方案 B:MCP 服务器(本地 stdio,VFS 由你实现)\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 传入(映射到 JSON‑RPC 的 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` 进入 JSON‑RPC `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 JSON‑RPC 配置,来源:本地 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` 时转成 JSON‑RPC 的 `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_agents:name 必填、prompt 必填、tools 仅名称、infer=True,display_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()`,没有内置 device‑flow API(源码:copilot/client.py、copilot/types.py)。 \nHeadless Device Flow 需走 GitHub OAuth Device Flow HTTP 端点获取 token(https://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.py),Resume 是 `resume_session(session_id)` 保留会话状态,Replay 只能由应用重放历史 prompt。优缺点:Resume 省 tokens/保工具与 compaction 状态但依赖持久化 session_id;Replay 无状态易扩展但成本高且易丢副作用控制。建议/落地:无状态前端请保存 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 会重复执行工具且耗 tokens;Resume 未重新注册工具或权限处理器会出现 tool not supported/默认拒绝;会话失效未回退。 \n建议/落地:持久化 chat_id→session_id,resume 失败时仅回放最近 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_id,resume 失败时仅回放最近 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/hooks;Replay 则需回放历史并保证相同 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 到可控目录/会话 workspace,VFS 工具分块返回,配合 `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_id,resume 失败再重建并回放必要历史。", "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=持久化 token;SessionConfig/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` 事件;权限确认是 JSON‑RPC `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 没有内置 JSON‑RPC trace,只能通过 `CopilotClient({\"log_level\":\"debug\"})` 打开 CLI 侧日志(非原始包)(来源:本地 SDK 源码)。 \n要抓实际 JSON‑RPC 流量,可 monkey‑patch `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 侧日志或 monkey‑patch `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": "最小示例(monkey‑patch `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\"})`;- 如需 JSON‑RPC 报文,采用临时 monkey‑patch 并启用脱敏/采样;- 不修改消息内容,避免线程阻塞。 \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": "JSON‑RPC 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.md,https://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.md,https://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.md,https://raw.githubusercontent.com/github/copilot-cli/main/README.md)。 \n常见坑:未分配席位或组织/企业策略禁用 CLI 导致 SDK 失效、PAT 权限不足或 premium requests 用尽引发认证/配额错误(源:https://raw.githubusercontent.com/github/copilot-sdk/main/README.md,https://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:对应 JSON‑RPC `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
186
plugins/debug/copilot_sdk_research/test_multi_rule_permission.py
Normal file
186
plugins/debug/copilot_sdk_research/test_multi_rule_permission.py
Normal 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())
|
||||
@@ -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())
|
||||
@@ -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
47
plugins/debug/mcp_test.py
Normal 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())
|
||||
53
plugins/filters/github_copilot_sdk_files_filter/README.md
Normal file
53
plugins/filters/github_copilot_sdk_files_filter/README.md
Normal 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.
|
||||
51
plugins/filters/github_copilot_sdk_files_filter/README_CN.md
Normal file
51
plugins/filters/github_copilot_sdk_files_filter/README_CN.md
Normal 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 多模态过滤器。只要优先级设置正确,它们可以共存互不干扰。
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 = (
|
||||
|
||||
73
plugins/pipes/github-copilot-sdk/INTERNAL_KNOWLEDGE.md
Normal file
73
plugins/pipes/github-copilot-sdk/INTERNAL_KNOWLEDGE.md
Normal 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/`.
|
||||
70
plugins/pipes/github-copilot-sdk/INTERNAL_KNOWLEDGE_CN.md
Normal file
70
plugins/pipes/github-copilot-sdk/INTERNAL_KNOWLEDGE_CN.md
Normal 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/` 下的最新日志。
|
||||
@@ -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.1 | **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.1 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
|
||||
|
||||
|
||||
@@ -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.1 | **项目:** [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.1 更新内容 (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)
|
||||
|
||||
@@ -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
103
plugins/pipes/github-copilot-sdk/scripts/sync_to_workspace.py
Normal file
103
plugins/pipes/github-copilot-sdk/scripts/sync_to_workspace.py
Normal 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())
|
||||
111
plugins/pipes/github-copilot-sdk/scripts/test_sdk.py
Normal file
111
plugins/pipes/github-copilot-sdk/scripts/test_sdk.py
Normal 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())
|
||||
@@ -445,7 +445,7 @@ class OpenWebUIStats:
|
||||
username = user.get("username", "")
|
||||
profile_url = user.get("profile_url", "")
|
||||
lines.append(t["author_header"])
|
||||
lines.append("|:---:|:---:|:---:|:---:|")
|
||||
lines.append("| :---: | :---: | :---: | :---: |")
|
||||
lines.append(
|
||||
f"| [{username}]({profile_url}) | **{user.get('followers', 0)}** | "
|
||||
f"**{user.get('total_points', 0)}** | **{user.get('contributions', 0)}** |"
|
||||
@@ -454,7 +454,7 @@ class OpenWebUIStats:
|
||||
|
||||
# 统计徽章表格
|
||||
lines.append(t["header"])
|
||||
lines.append("|:---:|:---:|:---:|:---:|:---:|")
|
||||
lines.append("| :---: | :---: | :---: | :---: | :---: |")
|
||||
lines.append(
|
||||
f"| **{stats['total_posts']}** | **{stats['total_downloads']}** | "
|
||||
f"**{stats['total_views']}** | **{stats['total_upvotes']}** | **{stats['total_saves']}** |"
|
||||
@@ -467,7 +467,7 @@ class OpenWebUIStats:
|
||||
lines.append(t["top6_updated"])
|
||||
lines.append("")
|
||||
lines.append(t["top6_header"])
|
||||
lines.append("|:---:|------|:---:|:---:|:---:|:---:|")
|
||||
lines.append("| :---: | :--- | :---: | :---: | :---: | :---: |")
|
||||
|
||||
medals = ["🥇", "🥈", "🥉", "4️⃣", "5️⃣", "6️⃣"]
|
||||
for i, post in enumerate(top_plugins):
|
||||
|
||||
Reference in New Issue
Block a user