Compare commits
6 Commits
v2026.02.2
...
v2026.02.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db33f44cbc | ||
|
|
5b6dddd517 | ||
|
|
c8ba7c99af | ||
|
|
81279845e2 | ||
|
|
6b39531fbc | ||
|
|
377534e6c9 |
147
.agent/rules/antigravity.md
Normal file
147
.agent/rules/antigravity.md
Normal file
@@ -0,0 +1,147 @@
|
||||
---
|
||||
description: >
|
||||
Antigravity development mode rules. Apply when the user requests high-speed iteration,
|
||||
a quick prototype, or says "反重力开发" / "antigravity mode".
|
||||
globs: "plugins/**/*.py"
|
||||
always_on: false
|
||||
---
|
||||
# Antigravity Development Mode
|
||||
|
||||
> High-speed delivery + strict reversibility. Every decision must keep both roll-forward and rollback feasible.
|
||||
|
||||
---
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Small, isolated edits** — one logical change per operation; no mixing refactor + feature.
|
||||
2. **Deterministic interfaces** — function signatures and return shapes must not change without an explicit contract update.
|
||||
3. **Multi-level fallback** — every I/O path has a degraded alternative (e.g., S3 → local → DB).
|
||||
4. **Reversible by default** — every file write, API call, or schema change must have an undo path recorded or be idempotent.
|
||||
|
||||
---
|
||||
|
||||
## Mandatory Safety Patterns
|
||||
|
||||
### 1. Timeout Guards on All Frontend Calls
|
||||
|
||||
Any `__event_call__` or `__event_emitter__` JS execution MUST be wrapped:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
try:
|
||||
result = await asyncio.wait_for(
|
||||
__event_call__({"type": "execute", "data": {"code": js_code}}),
|
||||
timeout=2.0
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning("Frontend JS execution timed out; falling back.")
|
||||
result = fallback_value
|
||||
except Exception as e:
|
||||
logger.error(f"Frontend call failed: {e}", exc_info=True)
|
||||
result = fallback_value
|
||||
```
|
||||
|
||||
JS side must also guard internally:
|
||||
|
||||
```javascript
|
||||
try {
|
||||
return (localStorage.getItem('locale') || navigator.language || 'en-US');
|
||||
} catch (e) {
|
||||
return 'en-US';
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Path Sandbox Validation
|
||||
|
||||
Resolve every workspace path and verify it stays inside the repo root:
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
def _validate_workspace_path(path: str, workspace_root: str) -> str:
|
||||
resolved = os.path.realpath(os.path.abspath(path))
|
||||
root = os.path.realpath(workspace_root)
|
||||
if not resolved.startswith(root + os.sep) and resolved != root:
|
||||
raise PermissionError(f"Path escape detected: {resolved} is outside {root}")
|
||||
return resolved
|
||||
```
|
||||
|
||||
### 3. Dual-Channel Upload Fallback
|
||||
|
||||
Always try API first; fall back to DB/local on failure:
|
||||
|
||||
```python
|
||||
async def _upload_file(self, filename: str, content: bytes) -> str:
|
||||
# Channel 1: API upload (S3-compatible)
|
||||
try:
|
||||
url = await self._api_upload(filename, content)
|
||||
if url:
|
||||
return url
|
||||
except Exception as e:
|
||||
logger.warning(f"API upload failed: {e}; falling back to local.")
|
||||
|
||||
# Channel 2: Local file + DB registration
|
||||
return await self._local_db_upload(filename, content)
|
||||
```
|
||||
|
||||
### 4. Progressive Status Reporting
|
||||
|
||||
For tasks > 3 seconds, emit staged updates:
|
||||
|
||||
```python
|
||||
await self._emit_status(emitter, "正在分析内容...", done=False)
|
||||
# ... phase 1 ...
|
||||
await self._emit_status(emitter, "正在生成输出...", done=False)
|
||||
# ... phase 2 ...
|
||||
await self._emit_status(emitter, "完成", done=True)
|
||||
```
|
||||
|
||||
Always emit `done=True` on completion and `notification(error)` on failure.
|
||||
|
||||
### 5. Emitter Guard
|
||||
|
||||
Check before every emit to prevent crashes on missing emitter:
|
||||
|
||||
```python
|
||||
if emitter and self.valves.SHOW_STATUS:
|
||||
await emitter({"type": "status", "data": {"description": msg, "done": done}})
|
||||
```
|
||||
|
||||
### 6. Exception Surface Rule
|
||||
|
||||
Never swallow exceptions silently. Every `except` block must:
|
||||
|
||||
- Log to backend: `logger.error(f"...: {e}", exc_info=True)`
|
||||
- Notify user: `await self._emit_notification(emitter, f"处理失败: {str(e)}", "error")`
|
||||
|
||||
---
|
||||
|
||||
## Edit Discipline
|
||||
|
||||
| ✅ DO | ❌ DO NOT |
|
||||
|-------|-----------|
|
||||
| One function / one Valve / one method per edit | Mix multiple unrelated changes in one operation |
|
||||
| Validate input at the function boundary | Assume upstream data is well-formed |
|
||||
| Return early on invalid state | Nest complex logic beyond 3 levels |
|
||||
| Check fallback availability before primary path | Assume primary path always succeeds |
|
||||
| Log before and after expensive I/O | Skip logging for "obvious" operations |
|
||||
|
||||
---
|
||||
|
||||
## Rollback Checklist
|
||||
|
||||
Before completing an antigravity operation, confirm:
|
||||
|
||||
- [ ] No global state mutated on `self` (filter singleton rule)
|
||||
- [ ] File writes are atomic or can be recreated
|
||||
- [ ] Database changes are idempotent (safe to re-run)
|
||||
- [ ] Timeout guards are in place for all async calls to external systems
|
||||
- [ ] The user can observe progress through status/notification events
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- Full engineering spec: `.github/copilot-instructions.md` → Section: **Antigravity Development Mode**
|
||||
- Design document: `docs/development/copilot-engineering-plan.md` → Section 5
|
||||
44
.gemini/skills/README.md
Normal file
44
.gemini/skills/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Agent Skills Index
|
||||
|
||||
This folder contains reusable Agent Skills for GitHub Copilot / VS Code custom agent workflows.
|
||||
|
||||
## Available Skills
|
||||
|
||||
- **community-announcer**
|
||||
- Purpose: Generate community announcement content and related assets.
|
||||
- Entry: `community-announcer/SKILL.md`
|
||||
|
||||
- **doc-mirror-sync**
|
||||
- Purpose: Sync mirrored documentation content and helper scripts.
|
||||
- Entry: `doc-mirror-sync/SKILL.md`
|
||||
|
||||
- **gh-issue-replier**
|
||||
- Purpose: Draft standardized issue replies with templates.
|
||||
- Entry: `gh-issue-replier/SKILL.md`
|
||||
|
||||
- **gh-issue-scheduler**
|
||||
- Purpose: Schedule and discover unanswered issues for follow-up.
|
||||
- Entry: `gh-issue-scheduler/SKILL.md`
|
||||
|
||||
- **i18n-validator**
|
||||
- Purpose: Validate translation key consistency across i18n dictionaries.
|
||||
- Entry: `i18n-validator/SKILL.md`
|
||||
|
||||
- **plugin-scaffolder**
|
||||
- Purpose: Scaffold OpenWebUI plugin boilerplate with repository standards.
|
||||
- Entry: `plugin-scaffolder/SKILL.md`
|
||||
|
||||
- **version-bumper**
|
||||
- Purpose: Assist with semantic version bumping workflows.
|
||||
- Entry: `version-bumper/SKILL.md`
|
||||
|
||||
- **xlsx-single-file**
|
||||
- Purpose: Single-file spreadsheet operations workflow without LibreOffice.
|
||||
- Entry: `xlsx-single-file/SKILL.md`
|
||||
|
||||
## Notes
|
||||
|
||||
- Skill definitions follow the expected location pattern:
|
||||
- `.github/skills/<skill-name>/SKILL.md`
|
||||
- Each skill may include optional `assets/`, `references/`, and `scripts/` folders.
|
||||
- This directory mirrors `.gemini/skills` for compatibility.
|
||||
23
.gemini/skills/community-announcer/SKILL.md
Normal file
23
.gemini/skills/community-announcer/SKILL.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: community-announcer
|
||||
description: Drafts engaging English and Chinese update announcements for the OpenWebUI Community and other social platforms. Use when a new version is released.
|
||||
---
|
||||
|
||||
# Community Announcer
|
||||
|
||||
## Overview
|
||||
Automates the drafting of high-impact update announcements.
|
||||
|
||||
## Workflow
|
||||
1. **Source Intel**: Read the latest version's `What's New` section from `README.md`.
|
||||
2. **Drafting**: Create two versions:
|
||||
- **Community Post**: Professional, structured, technical.
|
||||
- **Catchy Short**: For Discord/Twitter, use emojis and bullet points.
|
||||
3. **Multi-language**: Generate BOTH English and Chinese versions automatically.
|
||||
|
||||
## Announcement Structure (Recommended)
|
||||
- **Headline**: "Update vX.X.X - [Main Feature]"
|
||||
- **Introduction**: Brief context.
|
||||
- **Key Highlights**: Bulleted list of fixes/features.
|
||||
- **Action**: "Download from [Market Link]"
|
||||
- **Closing**: Thanks and Star request.
|
||||
14
.gemini/skills/doc-mirror-sync/SKILL.md
Normal file
14
.gemini/skills/doc-mirror-sync/SKILL.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: doc-mirror-sync
|
||||
description: Automatically synchronizes plugin READMEs to the official documentation directory (docs/). Use after editing a plugin's local documentation to keep the MkDocs site up to date.
|
||||
---
|
||||
|
||||
# Doc Mirror Sync
|
||||
|
||||
## Overview
|
||||
Automates the mirroring of `plugins/{type}/{name}/README.md` to `docs/plugins/{type}/{name}.md`.
|
||||
|
||||
## Workflow
|
||||
1. Identify changed READMEs.
|
||||
2. Copy content to corresponding mirror paths.
|
||||
3. Update version badges in `docs/plugins/{type}/index.md`.
|
||||
38
.gemini/skills/doc-mirror-sync/scripts/sync.py
Normal file
38
.gemini/skills/doc-mirror-sync/scripts/sync.py
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import shutil
|
||||
import re
|
||||
|
||||
def sync_mirrors():
|
||||
plugins_root = "plugins"
|
||||
docs_root = "docs/plugins"
|
||||
|
||||
types = ["actions", "filters", "pipes", "pipelines", "tools"]
|
||||
|
||||
for t in types:
|
||||
src_type_dir = os.path.join(plugins_root, t)
|
||||
dest_type_dir = os.path.join(docs_root, t)
|
||||
|
||||
if not os.path.exists(src_type_dir): continue
|
||||
os.makedirs(dest_type_dir, exist_ok=True)
|
||||
|
||||
for name in os.listdir(src_type_dir):
|
||||
plugin_dir = os.path.join(src_type_dir, name)
|
||||
if not os.path.isdir(plugin_dir): continue
|
||||
|
||||
# Sync README.md -> docs/plugins/{type}/{name}.md
|
||||
src_readme = os.path.join(plugin_dir, "README.md")
|
||||
if os.path.exists(src_readme):
|
||||
dest_readme = os.path.join(dest_type_dir, f"{name}.md")
|
||||
shutil.copy(src_readme, dest_readme)
|
||||
print(f"✅ Mirrored: {t}/{name} (EN)")
|
||||
|
||||
# Sync README_CN.md -> docs/plugins/{type}/{name}.zh.md
|
||||
src_readme_cn = os.path.join(plugin_dir, "README_CN.md")
|
||||
if os.path.exists(src_readme_cn):
|
||||
dest_readme_zh = os.path.join(dest_type_dir, f"{name}.zh.md")
|
||||
shutil.copy(src_readme_cn, dest_readme_zh)
|
||||
print(f"✅ Mirrored: {t}/{name} (ZH)")
|
||||
|
||||
if __name__ == "__main__":
|
||||
sync_mirrors()
|
||||
51
.gemini/skills/gh-issue-replier/SKILL.md
Normal file
51
.gemini/skills/gh-issue-replier/SKILL.md
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: gh-issue-replier
|
||||
description: Professional English replier for GitHub issues. Use when a task is completed, a bug is fixed, or more info is needed from the user. Automates replying using the 'gh' CLI tool.
|
||||
---
|
||||
|
||||
# Gh Issue Replier
|
||||
|
||||
## Overview
|
||||
|
||||
The `gh-issue-replier` skill enables Gemini CLI to interact with GitHub issues professionally. It enforces English for all communications and leverages the `gh` CLI to post comments.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Identify the Issue**: Find the issue number (e.g., #49).
|
||||
2. **Check Star Status**: Run the bundled script to check if the author has starred the repo.
|
||||
* Command: `bash scripts/check_star.sh <issue-number>`
|
||||
* Interpretation:
|
||||
* Exit code **0**: User has starred. Use "Already Starred" templates.
|
||||
* Exit code **1**: User has NOT starred. Include "Star Request" in the reply.
|
||||
3. **Select a Template**: Load [templates.md](references/templates.md) to choose a suitable English response pattern.
|
||||
4. **Draft the Reply**: Compose a concise message based on the star status.
|
||||
5. **Post the Comment**: Use the `gh` tool to submit the reply.
|
||||
|
||||
## Tool Integration
|
||||
|
||||
### Check Star Status
|
||||
```bash
|
||||
bash scripts/check_star.sh <issue-number>
|
||||
```
|
||||
|
||||
### Post Comment
|
||||
```bash
|
||||
gh issue comment <issue-number> --body "<message-body>"
|
||||
```
|
||||
|
||||
Example (if user has NOT starred):
|
||||
```bash
|
||||
gh issue comment 49 --body "This has been fixed in v1.2.7. If you find this helpful, a star on the repo would be much appreciated! ⭐"
|
||||
```
|
||||
|
||||
Example (if user HAS starred):
|
||||
```bash
|
||||
gh issue comment 49 --body "This has been fixed in v1.2.7. Thanks for your support!"
|
||||
```
|
||||
|
||||
## Guidelines
|
||||
|
||||
- **Language**: ALWAYS use English for the comment body, even if the system prompt or user conversation is in another language.
|
||||
- **Tone**: Professional, helpful, and appreciative.
|
||||
- **Precision**: When announcing a fix, mention the specific version or the logic change (e.g., "Updated regex pattern").
|
||||
- **Closing**: If the issue is resolved and you have permission, you can also use `gh issue close <number>`.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Reference Documentation for Gh Issue Replier
|
||||
|
||||
This is a placeholder for detailed reference documentation.
|
||||
Replace with actual reference content or delete if not needed.
|
||||
|
||||
## Structure Suggestions
|
||||
|
||||
### API Reference Example
|
||||
- Overview
|
||||
- Authentication
|
||||
- Endpoints with examples
|
||||
- Error codes
|
||||
|
||||
### Workflow Guide Example
|
||||
- Prerequisites
|
||||
- Step-by-step instructions
|
||||
- Best practices
|
||||
45
.gemini/skills/gh-issue-replier/references/templates.md
Normal file
45
.gemini/skills/gh-issue-replier/references/templates.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Issue Reply Templates
|
||||
|
||||
Use these templates to craft professional English replies. Adjust placeholders like `@username`, `v1.2.x`, and `[commit hash]` as needed.
|
||||
|
||||
## 1. Acknowledging a New Issue
|
||||
Use when you first see an issue and want to let the user know you are working on it.
|
||||
|
||||
- "Thank you for reporting this! I'm looking into it right now."
|
||||
- "Thanks for bringing this to my attention. I'll try to reproduce this behavior and get back to you shortly."
|
||||
|
||||
## 2. Requesting More Information
|
||||
Use when you need logs or specific details to fix the bug.
|
||||
|
||||
- "Could you please provide the **'Original'** vs **'Normalized'** content from your browser console logs (F12)? It would help a lot in debugging."
|
||||
- "It would be very helpful if you could share the specific Markdown text that triggered this issue."
|
||||
|
||||
## 3. Announcing a Fix
|
||||
Use when you have pushed the fix to the repository.
|
||||
|
||||
- "This has been fixed in version **v1.2.x**. You can update the plugin to resolve it."
|
||||
- "I've just pushed a fix for this in [commit hash]. Please let me know if it works for you after updating."
|
||||
- "The issue was caused by a greedy regex pattern. I've updated it to use a tempered greedy token to prevent incorrect merging."
|
||||
|
||||
## 4. Guiding to Official Market
|
||||
Always provide the official market link to ensure the user gets the latest verified version.
|
||||
|
||||
- "The fix is now live! You can download the latest version from the official OpenWebUI Community page here: [Plugin Market Link]. Simply update the function in your OpenWebUI instance to apply the changes."
|
||||
- "I recommend getting the updated version from the official store: [Link]. It includes the fix for the spacing issue we discussed."
|
||||
|
||||
## 5. Closing the Issue
|
||||
Use when the issue is confirmed resolved.
|
||||
|
||||
- "Glad to hear it's working now! Closing this for now. Feel free to reopen it if the problem persists."
|
||||
- "Since this is resolved, I'm closing this issue. Thanks again for your feedback!"
|
||||
|
||||
## 5. Pro-tip: Star Request
|
||||
Gently handle star requests based on the user's current status.
|
||||
|
||||
### If User has NOT starred:
|
||||
- "If you find this plugin helpful, a star on the repo would be much appreciated! ⭐"
|
||||
- "We'd love your support! If this fixed your issue, please consider starring the repository. ⭐"
|
||||
|
||||
### If User HAS already starred:
|
||||
- "Thanks again for starring the project and for your continuous support!"
|
||||
- "I appreciate your support and for being a stargazer of this project!"
|
||||
31
.gemini/skills/gh-issue-replier/scripts/check_star.sh
Executable file
31
.gemini/skills/gh-issue-replier/scripts/check_star.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Robust Star Checker v2
|
||||
# Usage: ./check_star.sh <issue_number>
|
||||
|
||||
ISSUE_NUM=$1
|
||||
if [ -z "$ISSUE_NUM" ]; then exit 2; fi
|
||||
|
||||
# 1. Get Repo and Author info
|
||||
REPO_FULL=$(gh repo view --json owner,name -q ".owner.login + \"/\" + .name")
|
||||
USER_LOGIN=$(gh issue view "$ISSUE_NUM" --json author -q ".author.login")
|
||||
|
||||
# 2. Use GraphQL for high precision (Detects stars even when REST 404s)
|
||||
IS_STARRED=$(gh api graphql -f query='
|
||||
query($owner:String!, $repo:String!, $user:String!) {
|
||||
repository(owner:$owner, name:$repo) {
|
||||
stargazers(query:$user, first:1) {
|
||||
nodes {
|
||||
login
|
||||
}
|
||||
}
|
||||
}
|
||||
}' -f owner="${REPO_FULL%/*}" -f repo="${REPO_FULL#*/}" -f user="$USER_LOGIN" -q ".data.repository.stargazers.nodes[0].login")
|
||||
|
||||
if [ "$IS_STARRED" == "$USER_LOGIN" ]; then
|
||||
echo "Confirmed: @$USER_LOGIN HAS starred $REPO_FULL. ⭐"
|
||||
exit 0
|
||||
else
|
||||
echo "Confirmed: @$USER_LOGIN has NOT starred $REPO_FULL."
|
||||
exit 1
|
||||
fi
|
||||
42
.gemini/skills/gh-issue-scheduler/SKILL.md
Normal file
42
.gemini/skills/gh-issue-scheduler/SKILL.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: gh-issue-scheduler
|
||||
description: Finds all open GitHub issues that haven't been replied to by the owner, summarizes them, and generates a solution plan. Use when the user wants to audit pending tasks or plan maintenance work.
|
||||
---
|
||||
|
||||
# Gh Issue Scheduler
|
||||
|
||||
## Overview
|
||||
|
||||
The `gh-issue-scheduler` skill helps maintainers track community feedback by identifying unaddressed issues and drafting actionable technical plans to resolve them.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Identify Unanswered Issues**: Run the bundled script to fetch issues without owner replies.
|
||||
* Command: `bash scripts/find_unanswered.sh`
|
||||
2. **Analyze and Summarize**: For each identified issue, summarize the core problem and the user's intent.
|
||||
3. **Generate Solution Plans**: Draft a technical "Action Plan" for each issue, including:
|
||||
* **Root Cause Analysis** (if possible)
|
||||
* **Proposed Fix/Implementation**
|
||||
* **Verification Strategy**
|
||||
4. **Present to User**: Display a structured report of all pending issues and their respective plans.
|
||||
|
||||
## Tool Integration
|
||||
|
||||
### Find Unanswered Issues
|
||||
```bash
|
||||
bash scripts/find_unanswered.sh
|
||||
```
|
||||
|
||||
## Report Format
|
||||
|
||||
When presenting the summary, use the following Markdown structure:
|
||||
|
||||
### 📋 Unanswered Issues Audit
|
||||
|
||||
#### Issue #[Number]: [Title]
|
||||
- **Author**: @username
|
||||
- **Summary**: Concise description of the problem.
|
||||
- **Action Plan**:
|
||||
1. Step 1 (e.g., Investigate file X)
|
||||
2. Step 2 (e.g., Apply fix Y)
|
||||
3. Verification (e.g., Run test Z)
|
||||
42
.gemini/skills/gh-issue-scheduler/scripts/find_unanswered.sh
Executable file
42
.gemini/skills/gh-issue-scheduler/scripts/find_unanswered.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Fetch all open issues and filter for those without responses from the owner/collaborators.
|
||||
# Uses 'gh' CLI.
|
||||
|
||||
REPO_FULL=$(gh repo view --json owner,name -q ".owner.login + "/" + .name")
|
||||
OWNER=${REPO_FULL%/*}
|
||||
|
||||
# 1. Get all open issues
|
||||
OPEN_ISSUES=$(gh issue list --state open --json number,title,author,createdAt --limit 100)
|
||||
|
||||
echo "Analysis for repository: $REPO_FULL"
|
||||
echo "------------------------------------"
|
||||
|
||||
# Process each issue
|
||||
echo "$OPEN_ISSUES" | jq -c '.[]' | while read -r issue; do
|
||||
NUMBER=$(echo "$issue" | jq -r '.number')
|
||||
TITLE=$(echo "$issue" | jq -r '.title')
|
||||
AUTHOR=$(echo "$issue" | jq -r '.author.login')
|
||||
|
||||
# Check comments for owner responses
|
||||
# We look for comments where the author is the repo owner
|
||||
COMMENTS=$(gh issue view "$NUMBER" --json comments -q ".comments[].author.login" 2>/dev/null)
|
||||
|
||||
HAS_OWNER_REPLY=false
|
||||
for COMMENT_AUTHOR in $COMMENTS; do
|
||||
if [ "$COMMENT_AUTHOR" == "$OWNER" ]; then
|
||||
HAS_OWNER_REPLY=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$HAS_OWNER_REPLY" == "false" ]; then
|
||||
echo "ISSUE_START"
|
||||
echo "ID: $NUMBER"
|
||||
echo "Title: $TITLE"
|
||||
echo "Author: $AUTHOR"
|
||||
echo "Description:"
|
||||
gh issue view "$NUMBER" --json body -q ".body"
|
||||
echo "ISSUE_END"
|
||||
fi
|
||||
done
|
||||
14
.gemini/skills/i18n-validator/SKILL.md
Normal file
14
.gemini/skills/i18n-validator/SKILL.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: i18n-validator
|
||||
description: Validates multi-language consistency in the TRANSLATIONS dictionary of a plugin. Use to check if any language keys are missing or if translations need updating.
|
||||
---
|
||||
|
||||
# I18n Validator
|
||||
|
||||
## Overview
|
||||
Ensures all 12 supported languages (en-US, zh-CN, etc.) have aligned translation keys.
|
||||
|
||||
## Features
|
||||
- Detects missing keys in non-English dictionaries.
|
||||
- Suggests translations using the core AI engine.
|
||||
- Validates the `fallback_map` for variant redirects.
|
||||
54
.gemini/skills/i18n-validator/scripts/validate_i18n.py
Normal file
54
.gemini/skills/i18n-validator/scripts/validate_i18n.py
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import ast
|
||||
import os
|
||||
|
||||
def check_i18n(file_path):
|
||||
if not os.path.exists(file_path):
|
||||
print(f"Error: File not found {file_path}")
|
||||
return
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
tree = ast.parse(f.read())
|
||||
|
||||
translations = {}
|
||||
for node in tree.body:
|
||||
if isinstance(node, ast.Assign):
|
||||
for target in node.targets:
|
||||
if isinstance(target, ast.Name) and target.id == "TRANSLATIONS":
|
||||
translations = ast.literal_eval(node.value)
|
||||
break
|
||||
|
||||
if not translations:
|
||||
print("⚠️ No TRANSLATIONS dictionary found.")
|
||||
return
|
||||
|
||||
# Base keys from English
|
||||
base_lang = "en-US"
|
||||
if base_lang not in translations:
|
||||
print(f"❌ Error: {base_lang} missing in TRANSLATIONS.")
|
||||
return
|
||||
|
||||
base_keys = set(translations[base_lang].keys())
|
||||
print(f"🔍 Analyzing {file_path}...")
|
||||
print(f"Standard keys ({len(base_keys)}): {', '.join(sorted(base_keys))}
|
||||
")
|
||||
|
||||
for lang, keys in translations.items():
|
||||
if lang == base_lang: continue
|
||||
lang_keys = set(keys.keys())
|
||||
missing = base_keys - lang_keys
|
||||
extra = lang_keys - base_keys
|
||||
|
||||
if missing:
|
||||
print(f"❌ {lang}: Missing {len(missing)} keys: {', '.join(missing)}")
|
||||
if extra:
|
||||
print(f"⚠️ {lang}: Has {len(extra)} extra keys: {', '.join(extra)}")
|
||||
if not missing and not extra:
|
||||
print(f"✅ {lang}: Aligned.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: validate_i18n.py <path_to_plugin.py>")
|
||||
sys.exit(1)
|
||||
check_i18n(sys.argv[1])
|
||||
19
.gemini/skills/plugin-scaffolder/SKILL.md
Normal file
19
.gemini/skills/plugin-scaffolder/SKILL.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: plugin-scaffolder
|
||||
description: Generates a standardized single-file i18n Python plugin template based on project standards. Use when starting a new plugin development to skip boilerplate writing.
|
||||
---
|
||||
|
||||
# Plugin Scaffolder
|
||||
|
||||
## Overview
|
||||
Generates compliant OpenWebUI plugin templates with built-in i18n, common utility methods, and required docstring fields.
|
||||
|
||||
## Usage
|
||||
1. Provide the **Plugin Name** and **Type** (action/filter/pipe).
|
||||
2. The skill will generate the `.py` file and the bilingual `README` files.
|
||||
|
||||
## Template Standard
|
||||
- `Valves(BaseModel)` with `UPPER_SNAKE_CASE`
|
||||
- `_get_user_context` with JS fallback and timeout
|
||||
- `_emit_status` and `_emit_debug_log` methods
|
||||
- Standardized docstring metadata
|
||||
34
.gemini/skills/plugin-scaffolder/assets/README_template.md
Normal file
34
.gemini/skills/plugin-scaffolder/assets/README_template.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# {{TITLE}}
|
||||
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.1.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
|
||||
|
||||
{{DESCRIPTION}}
|
||||
|
||||
## 🔥 What's New in v0.1.0
|
||||
|
||||
* Initial release of {{TITLE}}.
|
||||
|
||||
## 🌐 Multilingual Support
|
||||
|
||||
Supports automatic interface and status switching for the following languages:
|
||||
`English`, `简体中文`, `繁體中文 (香港)`, `繁體中文 (台灣)`, `한국어`, `日本語`, `Français`, `Deutsch`, `Español`, `Italiano`, `Tiếng Việt`, `Bahasa Indonesia`.
|
||||
|
||||
## ✨ Core Features
|
||||
|
||||
* Feature 1
|
||||
* Feature 2
|
||||
|
||||
## How to Use 🛠️
|
||||
|
||||
1. Install the plugin in Open WebUI.
|
||||
2. Configure settings in Valves.
|
||||
|
||||
## Configuration (Valves) ⚙️
|
||||
|
||||
| Parameter | Default | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `priority` | `50` | Execution priority. |
|
||||
|
||||
## ⭐ Support
|
||||
|
||||
If this plugin has been useful, a star on [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) is a big motivation for me. Thank you for the support.
|
||||
80
.gemini/skills/plugin-scaffolder/assets/template.py
Normal file
80
.gemini/skills/plugin-scaffolder/assets/template.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
title: {{TITLE}}
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie/openwebui-extensions
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 0.1.0
|
||||
description: {{DESCRIPTION}}
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import json
|
||||
from typing import Optional, Dict, Any, List, Callable, Awaitable
|
||||
from pydantic import BaseModel, Field
|
||||
from fastapi import Request
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TRANSLATIONS = {
|
||||
"en-US": {"status_starting": "Starting {{TITLE}}..."},
|
||||
"zh-CN": {"status_starting": "正在启动 {{TITLE}}..."},
|
||||
"zh-HK": {"status_starting": "正在啟動 {{TITLE}}..."},
|
||||
"zh-TW": {"status_starting": "正在啟動 {{TITLE}}..."},
|
||||
"ko-KR": {"status_starting": "{{TITLE}} 시작 중..."},
|
||||
"ja-JP": {"status_starting": "{{TITLE}} を起動中..."},
|
||||
"fr-FR": {"status_starting": "Démarrage de {{TITLE}}..."},
|
||||
"de-DE": {"status_starting": "{{TITLE}} wird gestartet..."},
|
||||
"es-ES": {"status_starting": "Iniciando {{TITLE}}..."},
|
||||
"it-IT": {"status_starting": "Avvio di {{TITLE}}..."},
|
||||
"vi-VN": {"status_starting": "Đang khởi động {{TITLE}}..."},
|
||||
"id-ID": {"status_starting": "Memulai {{TITLE}}..."},
|
||||
}
|
||||
|
||||
class {{CLASS_NAME}}:
|
||||
class Valves(BaseModel):
|
||||
priority: int = Field(default=50, description="Priority level (lower = earlier).")
|
||||
show_status: bool = Field(default=True, description="Show status updates in UI.")
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
self.fallback_map = {
|
||||
"zh": "zh-CN", "en": "en-US", "ko": "ko-KR", "ja": "ja-JP",
|
||||
"fr": "fr-FR", "de": "de-DE", "es": "es-ES", "it": "it-IT",
|
||||
"vi": "vi-VN", "id": "id-ID"
|
||||
}
|
||||
|
||||
def _get_translation(self, lang: str, key: str, **kwargs) -> str:
|
||||
target_lang = lang
|
||||
if target_lang not in TRANSLATIONS:
|
||||
base = target_lang.split("-")[0]
|
||||
target_lang = self.fallback_map.get(base, "en-US")
|
||||
|
||||
lang_dict = TRANSLATIONS.get(target_lang, TRANSLATIONS["en-US"])
|
||||
text = lang_dict.get(key, TRANSLATIONS["en-US"].get(key, key))
|
||||
return text.format(**kwargs) if kwargs else text
|
||||
|
||||
async def _get_user_context(self, __user__: Optional[dict], __event_call__: Optional[Callable] = None, __request__: Optional[Request] = None) -> dict:
|
||||
user_data = __user__ if isinstance(__user__, dict) else {}
|
||||
user_language = user_data.get("language", "en-US")
|
||||
if __event_call__:
|
||||
try:
|
||||
js = "try { return (document.documentElement.lang || localStorage.getItem('locale') || navigator.language || 'en-US'); } catch (e) { return 'en-US'; }"
|
||||
frontend_lang = await asyncio.wait_for(__event_call__({"type": "execute", "data": {"code": js}}), timeout=2.0)
|
||||
if frontend_lang: user_language = frontend_lang
|
||||
except: pass
|
||||
return {"user_language": user_language}
|
||||
|
||||
async def {{METHOD_NAME}}(self, body: dict, __user__: Optional[dict] = None, __event_emitter__=None, __event_call__=None, __request__: Optional[Request] = None) -> dict:
|
||||
if self.valves.show_status and __event_emitter__:
|
||||
user_ctx = await self._get_user_context(__user__, __event_call__, __request__)
|
||||
msg = self._get_translation(user_ctx["user_language"], "status_starting")
|
||||
await __event_emitter__({"type": "status", "data": {"description": msg, "done": False}})
|
||||
|
||||
# Implement core logic here
|
||||
|
||||
if self.valves.show_status and __event_emitter__:
|
||||
await __event_emitter__({"type": "status", "data": {"description": "Done", "done": True}})
|
||||
return body
|
||||
80
.gemini/skills/plugin-scaffolder/assets/template.py.j2
Normal file
80
.gemini/skills/plugin-scaffolder/assets/template.py.j2
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
title: {{TITLE}}
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie/openwebui-extensions
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 0.1.0
|
||||
description: {{DESCRIPTION}}
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import json
|
||||
from typing import Optional, Dict, Any, List, Callable, Awaitable
|
||||
from pydantic import BaseModel, Field
|
||||
from fastapi import Request
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TRANSLATIONS = {
|
||||
"en-US": {"status_starting": "Starting {{TITLE}}..."},
|
||||
"zh-CN": {"status_starting": "正在启动 {{TITLE}}..."},
|
||||
"zh-HK": {"status_starting": "正在啟動 {{TITLE}}..."},
|
||||
"zh-TW": {"status_starting": "正在啟動 {{TITLE}}..."},
|
||||
"ko-KR": {"status_starting": "{{TITLE}} 시작 중..."},
|
||||
"ja-JP": {"status_starting": "{{TITLE}} を起動中..."},
|
||||
"fr-FR": {"status_starting": "Démarrage de {{TITLE}}..."},
|
||||
"de-DE": {"status_starting": "{{TITLE}} wird gestartet..."},
|
||||
"es-ES": {"status_starting": "Iniciando {{TITLE}}..."},
|
||||
"it-IT": {"status_starting": "Avvio di {{TITLE}}..."},
|
||||
"vi-VN": {"status_starting": "Đang khởi động {{TITLE}}..."},
|
||||
"id-ID": {"status_starting": "Memulai {{TITLE}}..."},
|
||||
}
|
||||
|
||||
class {{CLASS_NAME}}:
|
||||
class Valves(BaseModel):
|
||||
priority: int = Field(default=50, description="Priority level (lower = earlier).")
|
||||
show_status: bool = Field(default=True, description="Show status updates in UI.")
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
self.fallback_map = {
|
||||
"zh": "zh-CN", "en": "en-US", "ko": "ko-KR", "ja": "ja-JP",
|
||||
"fr": "fr-FR", "de": "de-DE", "es": "es-ES", "it": "it-IT",
|
||||
"vi": "vi-VN", "id": "id-ID"
|
||||
}
|
||||
|
||||
def _get_translation(self, lang: str, key: str, **kwargs) -> str:
|
||||
target_lang = lang
|
||||
if target_lang not in TRANSLATIONS:
|
||||
base = target_lang.split("-")[0]
|
||||
target_lang = self.fallback_map.get(base, "en-US")
|
||||
|
||||
lang_dict = TRANSLATIONS.get(target_lang, TRANSLATIONS["en-US"])
|
||||
text = lang_dict.get(key, TRANSLATIONS["en-US"].get(key, key))
|
||||
return text.format(**kwargs) if kwargs else text
|
||||
|
||||
async def _get_user_context(self, __user__: Optional[dict], __event_call__: Optional[Callable] = None, __request__: Optional[Request] = None) -> dict:
|
||||
user_data = __user__ if isinstance(__user__, dict) else {}
|
||||
user_language = user_data.get("language", "en-US")
|
||||
if __event_call__:
|
||||
try:
|
||||
js = "try { return (document.documentElement.lang || localStorage.getItem('locale') || navigator.language || 'en-US'); } catch (e) { return 'en-US'; }"
|
||||
frontend_lang = await asyncio.wait_for(__event_call__({"type": "execute", "data": {"code": js}}), timeout=2.0)
|
||||
if frontend_lang: user_language = frontend_lang
|
||||
except: pass
|
||||
return {"user_language": user_language}
|
||||
|
||||
async def {{METHOD_NAME}}(self, body: dict, __user__: Optional[dict] = None, __event_emitter__=None, __event_call__=None, __request__: Optional[Request] = None) -> dict:
|
||||
if self.valves.show_status and __event_emitter__:
|
||||
user_ctx = await self._get_user_context(__user__, __event_call__, __request__)
|
||||
msg = self._get_translation(user_ctx["user_language"], "status_starting")
|
||||
await __event_emitter__({"type": "status", "data": {"description": msg, "done": False}})
|
||||
|
||||
# Implement core logic here
|
||||
|
||||
if self.valves.show_status and __event_emitter__:
|
||||
await __event_emitter__({"type": "status", "data": {"description": "Done", "done": True}})
|
||||
return body
|
||||
66
.gemini/skills/plugin-scaffolder/scripts/scaffold.py
Normal file
66
.gemini/skills/plugin-scaffolder/scripts/scaffold.py
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
def scaffold(p_type, p_name, title, desc):
|
||||
target_dir = f"plugins/{p_type}/{p_name}"
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
|
||||
class_name = (
|
||||
"Action"
|
||||
if p_type == "actions"
|
||||
else (
|
||||
"Filter"
|
||||
if p_type == "filters"
|
||||
else "Tools" if p_type == "tools" else "Pipe"
|
||||
)
|
||||
)
|
||||
method_name = (
|
||||
"action"
|
||||
if p_type == "actions"
|
||||
else (
|
||||
"outlet"
|
||||
if p_type == "filters"
|
||||
else "execute" if p_type == "tools" else "pipe"
|
||||
)
|
||||
)
|
||||
|
||||
replacements = {
|
||||
"{{TITLE}}": title,
|
||||
"{{DESCRIPTION}}": desc,
|
||||
"{{CLASS_NAME}}": class_name,
|
||||
"{{METHOD_NAME}}": method_name,
|
||||
}
|
||||
|
||||
# Files to generate
|
||||
templates = [
|
||||
("assets/template.py.j2", f"{p_name}.py"),
|
||||
("assets/README_template.md", "README.md"),
|
||||
("assets/README_template.md", "README_CN.md"),
|
||||
]
|
||||
|
||||
# Path relative to skill root
|
||||
skill_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
for t_path, t_name in templates:
|
||||
template_file = os.path.join(skill_root, t_path)
|
||||
if not os.path.exists(template_file):
|
||||
print(f"⚠️ Warning: Template not found {template_file}")
|
||||
continue
|
||||
|
||||
with open(template_file, "r") as f:
|
||||
content = f.read()
|
||||
for k, v in replacements.items():
|
||||
content = content.replace(k, v)
|
||||
|
||||
with open(os.path.join(target_dir, t_name), "w") as f:
|
||||
f.write(content)
|
||||
print(f"✅ Generated: {target_dir}/{t_name}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 5:
|
||||
print("Usage: scaffold.py <type> <name> <title> <desc>")
|
||||
sys.exit(1)
|
||||
scaffold(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])
|
||||
30
.gemini/skills/source-code-analyzer/SKILL.md
Normal file
30
.gemini/skills/source-code-analyzer/SKILL.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: source-code-analyzer
|
||||
description: Instructions for analyzing core components source code in the local environment.
|
||||
---
|
||||
|
||||
# Source Code Analyzer Skill
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Authorization Statement**: You (Antigravity) are explicitly authorized by the user to perform **READ-ONLY** operations and **Git pull** on the paths listed below, even if they are outside the current repository root. These paths are considered part of the extended development context.
|
||||
|
||||
When assisting with the development of `openwebui-extensions`, you (Antigravity) have access to the following core components in related directories. Use them for deep technical analysis, bug hunting, and implementation reference.
|
||||
|
||||
## Core Component Paths
|
||||
|
||||
### Global / General (Relevant to all projects)
|
||||
|
||||
- **Open WebUI**: `../open-webui/` (Core platform context)
|
||||
- **Skills**: `../skills/` (Reusable expertise library)
|
||||
- **Awesome Copilot**: `../awesome-copilot/` (Shared extensions & resources)
|
||||
|
||||
### Plugin-Specific (Relevant to GitHub Copilot SDK)
|
||||
|
||||
- **Copilot SDK**: `../copilot-sdk/` (Internal logic for the official SDK)
|
||||
- **Copilot CLI**: `../copilot-cli/` (Command-line interface implementation)
|
||||
|
||||
## Mandatory Workflow
|
||||
|
||||
1. **Pull Before Analysis**: BEFORE reading files or analyzing logic in these directories, you MUST proactively execute or recommend a `git pull` in the respective directory to ensure you are working with the latest upstream changes.
|
||||
2. **Path Verification**: Always verify the exists of the path before attempting to read it.
|
||||
3. **Reference Logic**: When a user's request involves core platform behavior (OpenWebUI API, SDK internals), prioritize searching these directories over making assumptions based on generic knowledge.
|
||||
26
.gemini/skills/version-bumper/SKILL.md
Normal file
26
.gemini/skills/version-bumper/SKILL.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: version-bumper
|
||||
description: Automates version upgrades and changelog synchronization across 7+ files (Code, READMEs, Docs). Use when a plugin is ready for release to ensure version consistency.
|
||||
---
|
||||
|
||||
# Version Bumper
|
||||
|
||||
## Overview
|
||||
This skill ensures that every version upgrade is synchronized across the entire repository, following the strict "Documentation Sync" rule in GEMINI.md.
|
||||
|
||||
## Workflow
|
||||
1. **Prepare Info**: Gather the new version number and brief changelogs in both English and Chinese.
|
||||
2. **Auto-Patch**: The skill will help you identify and update:
|
||||
- `plugins/.../name.py` (docstring version)
|
||||
- `plugins/.../README.md` (metadata & What's New)
|
||||
- `plugins/.../README_CN.md` (metadata & 最新更新)
|
||||
- `docs/plugins/...md` (mirrors)
|
||||
- `docs/plugins/index.md` (version badge)
|
||||
- `README.md` (updated date badge)
|
||||
3. **Verify**: Check the diffs to ensure no formatting was broken.
|
||||
|
||||
## Tool Integration
|
||||
Execute the bump script (draft):
|
||||
```bash
|
||||
python3 scripts/bump.py <version> "<message_en>" "<message_zh>"
|
||||
```
|
||||
70
.gemini/skills/version-bumper/scripts/bump.py
Normal file
70
.gemini/skills/version-bumper/scripts/bump.py
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
def patch_file(file_path, old_pattern, new_content, is_regex=False):
|
||||
if not os.path.exists(file_path):
|
||||
print(f"Warning: File not found: {file_path}")
|
||||
return False
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
if is_regex:
|
||||
new_content_result = re.sub(old_pattern, new_content, content, flags=re.MULTILINE)
|
||||
else:
|
||||
new_content_result = content.replace(old_pattern, new_content)
|
||||
|
||||
if new_content_result != content:
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content_result)
|
||||
print(f"✅ Patched: {file_path}")
|
||||
return True
|
||||
else:
|
||||
print(f"ℹ️ No change needed: {file_path}")
|
||||
return False
|
||||
|
||||
def bump_version(plugin_type, plugin_name, new_version, msg_en, msg_zh):
|
||||
print(f"🚀 Bumping {plugin_name} ({plugin_type}) to {new_version}...")
|
||||
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
today_badge = today.replace("-", "--")
|
||||
|
||||
# 1. Patch Plugin Python File
|
||||
py_file = f"plugins/{plugin_type}/{plugin_name}/{plugin_name}.py"
|
||||
patch_file(py_file, r"version: \d+\.\d+\.\d+", f"version: {new_version}", is_regex=True)
|
||||
|
||||
# 2. Patch Plugin READMEs
|
||||
readme_en = f"plugins/{plugin_type}/{plugin_name}/README.md"
|
||||
readme_zh = f"plugins/{plugin_type}/{plugin_name}/README_CN.md"
|
||||
|
||||
# Update version in metadata
|
||||
patch_file(readme_en, r"\*\*Version:\*\* \d+\.\d+\.\d+", f"**Version:** {new_version}", is_regex=True)
|
||||
patch_file(readme_zh, r"\*\*版本:\*\* \d+\.\d+\.\d+", f"**版本:** {new_version}", is_regex=True)
|
||||
|
||||
# Update What's New (Assuming standard headers)
|
||||
patch_file(readme_en, r"## 🔥 What's New in v.*?\n", f"## 🔥 What's New in v{new_version}\n\n* {msg_en}\n", is_regex=True)
|
||||
patch_file(readme_zh, r"## 🔥 最新更新 v.*?\n", f"## 🔥 最新更新 v{new_version}\n\n* {msg_zh}\n", is_regex=True)
|
||||
|
||||
# 3. Patch Docs Mirrors
|
||||
doc_en = f"docs/plugins/{plugin_type}/{plugin_name}.md"
|
||||
doc_zh = f"docs/plugins/{plugin_type}/{plugin_name}.zh.md"
|
||||
patch_file(doc_en, r"\*\*Version:\*\* \d+\.\d+\.\d+", f"**Version:** {new_version}", is_regex=True)
|
||||
patch_file(doc_zh, r"\*\*版本:\*\* \d+\.\d+\.\d+", f"**版本:** {new_version}", is_regex=True)
|
||||
|
||||
# 4. Patch Root READMEs (Updated Date Badge)
|
||||
patch_file("README.md", r"badge/202\d--\d\d--\d\d-gray", f"badge/{today_badge}-gray", is_regex=True)
|
||||
patch_file("README_CN.md", r"badge/202\d--\d\d--\d\d-gray", f"badge/{today_badge}-gray", is_regex=True)
|
||||
|
||||
print("\n✨ All synchronization tasks completed.")
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 6:
|
||||
print("Usage: bump.py <type> <name> <version> <msg_en> <msg_zh>")
|
||||
print("Example: bump.py filters markdown_normalizer 1.2.8 'Fix bug' '修复错误'")
|
||||
sys.exit(1)
|
||||
|
||||
bump_version(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5])
|
||||
62
.github/agents/plugin-implementer.agent.md
vendored
Normal file
62
.github/agents/plugin-implementer.agent.md
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
name: Plugin Implementer
|
||||
description: Implement OpenWebUI plugin and docs updates with strict project standards
|
||||
argument-hint: Provide approved plan or feature request to implement
|
||||
tools: ['execute/getTerminalOutput', 'execute/runInTerminal', 'read/readFile', 'read/terminalSelection', 'read/terminalLastCommand', 'edit/createFile', 'edit/editFiles', 'search', 'web', 'web/fetch', 'web/githubRepo', 'agent']
|
||||
infer: true
|
||||
handoffs:
|
||||
- label: Run Review
|
||||
agent: Plugin Reviewer
|
||||
prompt: Review the implementation for i18n, safety, and consistency issues.
|
||||
send: false
|
||||
---
|
||||
You are the **implementation specialist** for the `openwebui-extensions` repository.
|
||||
|
||||
## Execution Rules
|
||||
1. **Minimal diffs**: Change only what the approved plan specifies.
|
||||
2. **Single-file i18n**: Every plugin is one `.py` file with built-in `TRANSLATIONS` dict. Never create `_cn.py` split files.
|
||||
3. **Context helpers**: Always use `_get_user_context(__user__)` and `_get_chat_context(body, __metadata__)` — never access dict keys directly.
|
||||
4. **Emitter guards**: Every `await emitter(...)` must be guarded by `if emitter:`.
|
||||
5. **Logging**: Use `logging.getLogger(__name__)` — no bare `print()` in production code.
|
||||
6. **Async safety**: Wrap all `__event_call__` with `asyncio.wait_for(..., timeout=2.0)` + inner JS `try { ... } catch(e) { return fallback; }`.
|
||||
|
||||
## Required Plugin Pattern
|
||||
```python
|
||||
# Docstring: title, author, author_url, funding_url, version, description
|
||||
# icon_url is REQUIRED for Action plugins (Lucide SVG, base64)
|
||||
|
||||
class Action: # or Filter / Pipe
|
||||
class Valves(BaseModel):
|
||||
SHOW_STATUS: bool = Field(default=True, description="...")
|
||||
# All fields UPPER_SNAKE_CASE
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
|
||||
def _get_user_context(self, __user__): ... # always implement
|
||||
def _get_chat_context(self, body, __metadata__=None): ... # always implement
|
||||
async def _emit_status(self, emitter, description, done=False): ...
|
||||
async def _emit_notification(self, emitter, content, ntype="info"): ...
|
||||
```
|
||||
|
||||
## Known Split-File Plugins (Legacy — Do NOT Add More)
|
||||
These still have `_cn.py` files. When touching any of them, migrate CN content into `TRANSLATIONS` dict:
|
||||
- `plugins/actions/deep-dive/deep_dive_cn.py`
|
||||
- `plugins/actions/export_to_docx/export_to_word_cn.py`
|
||||
- `plugins/actions/export_to_excel/export_to_excel_cn.py`
|
||||
- `plugins/actions/flash-card/flash_card_cn.py`
|
||||
- `plugins/actions/infographic/infographic_cn.py`
|
||||
- `plugins/filters/folder-memory/folder_memory_cn.py`
|
||||
|
||||
## Version Bump Rule
|
||||
Only bump version when user explicitly says "发布" / "release" / "bump version".
|
||||
When bumping, update ALL 7+ files (code docstring + 2× README + 2× doc detail + 2× doc index + 2× root README date badge).
|
||||
|
||||
## Git Policy
|
||||
- Never run `git commit`, `git push`, or create PRs automatically.
|
||||
- After all edits, list what changed and why, then stop.
|
||||
|
||||
## Completion Output
|
||||
- Modified files (full relative paths, one-line descriptions)
|
||||
- Remaining manual checks
|
||||
- Suggested handoff to **Plugin Reviewer**
|
||||
78
.github/agents/plugin-planner.agent.md
vendored
Normal file
78
.github/agents/plugin-planner.agent.md
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
name: Plugin Planner
|
||||
description: Analyze requirements and produce a safe implementation plan for OpenWebUI plugins
|
||||
argument-hint: Describe the plugin goal, constraints, and target files
|
||||
tools: ['read/readFile', 'search', 'web', 'web/githubRepo', 'read/terminalLastCommand', 'read/terminalSelection', 'agent']
|
||||
infer: true
|
||||
handoffs:
|
||||
- label: Start Implementation
|
||||
agent: Plugin Implementer
|
||||
prompt: Implement the approved plan step by step with minimal diffs.
|
||||
send: false
|
||||
---
|
||||
You are the **planning specialist** for the `openwebui-extensions` repository.
|
||||
|
||||
## Your Responsibilities
|
||||
1. Read existing plugin code and docs **before** proposing any change.
|
||||
2. Produce a **small, reversible** plan (one logical change per file per step).
|
||||
3. Clearly list all impacted files including the docs sync chain.
|
||||
4. Flag risks: breaking changes, release implications, version bumps needed.
|
||||
|
||||
## Hard Rules
|
||||
- Never propose `git commit`, `git push`, or PR creation.
|
||||
- Every plan must end with an acceptance checklist for the user to approve before handing off.
|
||||
- Reference `.github/copilot-instructions.md` as the authoritative spec.
|
||||
|
||||
## Repository Plugin Inventory
|
||||
|
||||
### Actions (`plugins/actions/`)
|
||||
| Dir | Main file | Version | i18n status |
|
||||
|-----|-----------|---------|-------------|
|
||||
| `deep-dive` | `deep_dive.py` | 1.0.0 | ⚠️ has `deep_dive_cn.py` split |
|
||||
| `export_to_docx` | `export_to_word.py` | 0.4.4 | ⚠️ has `export_to_word_cn.py` split |
|
||||
| `export_to_excel` | `export_to_excel.py` | 0.3.7 | ⚠️ has `export_to_excel_cn.py` split |
|
||||
| `flash-card` | `flash_card.py` | 0.2.4 | ⚠️ has `flash_card_cn.py` split |
|
||||
| `infographic` | `infographic.py` | 1.5.0 | ⚠️ has `infographic_cn.py` split |
|
||||
| `smart-mind-map` | `smart_mind_map.py` | 1.0.0 | ✅ single file |
|
||||
| `smart-mermaid` | _(empty stub)_ | — | — |
|
||||
|
||||
### Filters (`plugins/filters/`)
|
||||
| Dir | Main file | Version | i18n status |
|
||||
|-----|-----------|---------|-------------|
|
||||
| `async-context-compression` | `async_context_compression.py` | 1.3.0 | ✅ |
|
||||
| `context_enhancement_filter` | `context_enhancement_filter.py` | 0.3 | ⚠️ non-SemVer version |
|
||||
| `copilot_files_preprocessor` | _(empty stub)_ | — | — |
|
||||
| `folder-memory` | `folder_memory.py` | 0.1.0 | ⚠️ has `folder_memory_cn.py` split |
|
||||
| `github_copilot_sdk_files_filter` | `github_copilot_sdk_files_filter.py` | 0.1.2 | ✅ |
|
||||
| `markdown_normalizer` | `markdown_normalizer.py` | 1.2.4 | ✅ |
|
||||
| `web_gemini_multimodel_filter` | `web_gemini_multimodel.py` | 0.3.2 | ✅ |
|
||||
|
||||
### Pipes / Pipelines / Tools
|
||||
| Path | Main file | Version |
|
||||
|------|-----------|---------|
|
||||
| `pipes/github-copilot-sdk` | `github_copilot_sdk.py` | 0.7.0 |
|
||||
| `pipelines/moe_prompt_refiner` | `moe_prompt_refiner.py` | — |
|
||||
| `tools/workspace-file-manager` | `workspace_file_manager.py` | 0.2.0 |
|
||||
|
||||
## Naming Conventions (Actual Mix)
|
||||
- Action dirs: some use **dashes** (`deep-dive`, `flash-card`, `smart-mind-map`), some **underscores** (`export_to_docx`, `export_to_excel`, `infographic`)
|
||||
- Filter dirs: similarly mixed — prefer underscores for new plugins
|
||||
- Main `.py` filenames always use **underscores**
|
||||
|
||||
## Docs Sync Chain (for every plugin change)
|
||||
For `plugins/{type}/{name}/`, these 7+ files must stay in sync:
|
||||
1. `plugins/{type}/{name}/{name}.py` — version in docstring
|
||||
2. `plugins/{type}/{name}/README.md` — version + What's New
|
||||
3. `plugins/{type}/{name}/README_CN.md` — version + 最新更新
|
||||
4. `docs/plugins/{type}/{name}.md`
|
||||
5. `docs/plugins/{type}/{name}.zh.md`
|
||||
6. `docs/plugins/{type}/index.md` — version badge
|
||||
7. `docs/plugins/{type}/index.zh.md` — version badge
|
||||
8. Root `README.md` / `README_CN.md` — date badge
|
||||
|
||||
## Output Format
|
||||
- **Scope summary**
|
||||
- **Affected files** (full relative paths)
|
||||
- **Step-by-step plan** (numbered, ≤10 steps)
|
||||
- **Risk flags** (version bump? breaking change? split-file migration needed?)
|
||||
- **Acceptance checklist** → user must approve before handoff to Implementer
|
||||
71
.github/agents/plugin-reviewer.agent.md
vendored
Normal file
71
.github/agents/plugin-reviewer.agent.md
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: Plugin Reviewer
|
||||
description: Perform strict repository-aligned code review for OpenWebUI plugin changes
|
||||
argument-hint: Share changed files or branch diff to review
|
||||
tools: ['search', 'read/readFile', 'web', 'web/fetch', 'web/githubRepo', 'execute/getTerminalOutput', 'read/terminalLastCommand', 'read/terminalSelection']
|
||||
infer: true
|
||||
handoffs:
|
||||
- label: Prepare Release Draft
|
||||
agent: Release Prep
|
||||
prompt: Create a bilingual release draft and commit message based on reviewed changes.
|
||||
send: false
|
||||
---
|
||||
You are the **review specialist** for the `openwebui-extensions` repository.
|
||||
|
||||
Full review rules are in .github/instructions/code-review.instructions.md.
|
||||
|
||||
## Review Checklist
|
||||
|
||||
### 🔴 Blocking (must fix before release)
|
||||
|
||||
**1. Single-file i18n Architecture**
|
||||
- [ ] No new `_cn.py` split files created.
|
||||
- [ ] All user-visible strings go through `TRANSLATIONS[lang].get(key, fallback)`.
|
||||
- [ ] `FALLBACK_MAP` covers at least `zh → zh-CN` and `en → en-US`.
|
||||
- [ ] `format(**kwargs)` on translations wrapped in `try/except KeyError`.
|
||||
|
||||
**2. Context Helpers**
|
||||
- [ ] Uses `_get_user_context(__user__)` (not `__user__["name"]` directly).
|
||||
- [ ] Uses `_get_chat_context(body, __metadata__)` (not ad-hoc `body.get("chat_id")`).
|
||||
|
||||
**3. Antigravity Safety**
|
||||
- [ ] Every `__event_call__` wrapped: `asyncio.wait_for(..., timeout=2.0)`.
|
||||
- [ ] JS code passed to `__event_call__` has `try { ... } catch(e) { return fallback; }`.
|
||||
- [ ] File path operations validated against workspace root (no traversal).
|
||||
- [ ] Upload paths have dual-channel fallback (API → DB/local).
|
||||
|
||||
**4. Emitter Guards**
|
||||
- [ ] Every `await emitter(...)` guarded by `if emitter:`.
|
||||
- [ ] `_emit_status(done=False)` on start, `done=True` on success, `_emit_notification("error")` on failure.
|
||||
- [ ] No bare `print()` — use `logging.getLogger(__name__)`.
|
||||
|
||||
**5. Filter Singleton Safety**
|
||||
- [ ] No mutable per-request state stored on `self` in Filter plugins.
|
||||
|
||||
**6. Streaming Compatibility (OpenWebUI 0.8.x)**
|
||||
- [ ] `</think>` tag closed before any normal text or tool cards.
|
||||
- [ ] `<details type="tool_calls" ...>` attributes escape `"` as `"`.
|
||||
- [ ] `<details ...>` block has newline immediately after `>`.
|
||||
|
||||
**7. Version & Docs Sync**
|
||||
- [ ] Version bumped in docstring (if release).
|
||||
- [ ] `README.md` + `README_CN.md` updated (What's New + version).
|
||||
- [ ] `docs/plugins/{type}/{name}.md` and `.zh.md` match README.
|
||||
- [ ] `docs/plugins/{type}/index.md` and `.zh.md` version badges updated.
|
||||
- [ ] Root `README.md` / `README_CN.md` date badge updated.
|
||||
|
||||
### 🟡 Non-blocking (suggestions)
|
||||
- Copilot SDK tools: `params_type=MyParams` in `define_tool()`.
|
||||
- Long tasks (>3s): periodic `_emit_notification("info")` every 5s.
|
||||
- `icon_url` present for Action plugins (Lucide SVG, base64).
|
||||
|
||||
## Known Pre-existing Issues (Don't block on unless the PR introduces new ones)
|
||||
- `_cn.py` splits in: `deep-dive`, `export_to_docx`, `export_to_excel`, `flash-card`, `infographic`, `folder-memory` — legacy, not new.
|
||||
- `context_enhancement_filter` version is `0.3` (non-SemVer) — pre-existing.
|
||||
- `copilot_files_preprocessor` and `smart-mermaid` are empty stubs — pre-existing.
|
||||
|
||||
## Review Output
|
||||
- **Blocking issues** (file:line references)
|
||||
- **Non-blocking suggestions**
|
||||
- **Pass / Fail verdict**
|
||||
- **Next step**: Pass → handoff to Release Prep; Fail → return to Implementer with fix list
|
||||
82
.github/agents/release-prep.agent.md
vendored
Normal file
82
.github/agents/release-prep.agent.md
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
name: Release Prep
|
||||
description: Prepare release-ready summaries and Conventional Commit drafts without pushing
|
||||
argument-hint: Provide final change list and target version (optional)
|
||||
tools: ['search', 'read/readFile', 'web', 'web/fetch', 'web/githubRepo', 'execute/getTerminalOutput', 'read/terminalLastCommand', 'read/terminalSelection']
|
||||
infer: true
|
||||
---
|
||||
You are the **release preparation specialist** for the `openwebui-extensions` repository.
|
||||
|
||||
Full commit message rules: .github/instructions/commit-message.instructions.md
|
||||
Full release workflow: .agent/workflows/plugin-development.md
|
||||
|
||||
## Responsibilities
|
||||
1. Generate a Conventional Commit message (English only).
|
||||
2. Draft bilingual release notes (EN + 中文).
|
||||
3. Verify ALL file sync locations are updated.
|
||||
4. **Stop before any commit or push** — wait for explicit user confirmation.
|
||||
|
||||
## Commit Message Format
|
||||
```text
|
||||
type(scope): brief imperative description
|
||||
|
||||
- Key change 1
|
||||
- Key change 2 (include migration note if needed)
|
||||
```
|
||||
- `type`: `feat` / `fix` / `docs` / `refactor` / `chore`
|
||||
- `scope`: plugin folder name (e.g., `smart-mind-map`, `github-copilot-sdk`, `folder-memory`)
|
||||
- Title ≤ 72 chars, imperative mood, no trailing period, no capital first letter
|
||||
|
||||
## 9-File Sync Checklist (fill in for each changed plugin)
|
||||
```text
|
||||
Plugin: {type}/{name} → v{new_version}
|
||||
[ ] 1. plugins/{type}/{name}/{name}.py → version in docstring
|
||||
[ ] 2. plugins/{type}/{name}/README.md → version + What's New
|
||||
[ ] 3. plugins/{type}/{name}/README_CN.md → version + 最新更新
|
||||
[ ] 4. docs/plugins/{type}/{name}.md → mirrors README
|
||||
[ ] 5. docs/plugins/{type}/{name}.zh.md → mirrors README_CN
|
||||
[ ] 6. docs/plugins/{type}/index.md → version badge updated
|
||||
[ ] 7. docs/plugins/{type}/index.zh.md → version badge updated
|
||||
[ ] 8. README.md (root) → date badge updated
|
||||
[ ] 9. README_CN.md (root) → date badge updated
|
||||
```
|
||||
|
||||
## Current Plugin Versions (as of last audit — 2026-02-23)
|
||||
| Plugin | Type | Version | Note |
|
||||
|--------|------|---------|------|
|
||||
| deep-dive | action | 1.0.0 | has `_cn.py` split |
|
||||
| export_to_docx | action | 0.4.4 | has `_cn.py` split |
|
||||
| export_to_excel | action | 0.3.7 | has `_cn.py` split |
|
||||
| flash-card | action | 0.2.4 | has `_cn.py` split |
|
||||
| infographic | action | 1.5.0 | has `_cn.py` split |
|
||||
| smart-mind-map | action | 1.0.0 | ✅ |
|
||||
| async-context-compression | filter | 1.3.0 | ✅ |
|
||||
| context_enhancement_filter | filter | 0.3 | ⚠️ non-SemVer |
|
||||
| folder-memory | filter | 0.1.0 | has `_cn.py` split |
|
||||
| github_copilot_sdk_files_filter | filter | 0.1.2 | ✅ |
|
||||
| markdown_normalizer | filter | 1.2.4 | ✅ |
|
||||
| web_gemini_multimodel_filter | filter | 0.3.2 | ✅ |
|
||||
| github-copilot-sdk | pipe | 0.7.0 | ✅ |
|
||||
| workspace-file-manager | tool | 0.2.0 | ✅ |
|
||||
|
||||
## Output Template
|
||||
|
||||
### Commit Message
|
||||
```text
|
||||
{type}({scope}): {description}
|
||||
|
||||
- {change 1}
|
||||
- {change 2}
|
||||
```
|
||||
|
||||
### Change Summary (EN)
|
||||
- {bullet list of user-visible changes}
|
||||
|
||||
### 变更摘要(中文)
|
||||
- {中文要点列表}
|
||||
|
||||
### Verification Status
|
||||
{filled-in 9-file checklist for each changed plugin}
|
||||
|
||||
---
|
||||
⚠️ **Waiting for user confirmation — no git operations will run until explicitly approved.**
|
||||
96
.github/copilot-instructions.md
vendored
96
.github/copilot-instructions.md
vendored
@@ -205,19 +205,56 @@ class Action:
|
||||
#### 用户上下文 (User Context)
|
||||
|
||||
```python
|
||||
def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
|
||||
"""安全提取用户上下文信息。"""
|
||||
if isinstance(__user__, (list, tuple)):
|
||||
user_data = __user__[0] if __user__ else {}
|
||||
elif isinstance(__user__, dict):
|
||||
user_data = __user__
|
||||
else:
|
||||
user_data = {}
|
||||
async def _get_user_context(
|
||||
self,
|
||||
__user__: Optional[dict],
|
||||
__event_call__: Optional[callable] = None,
|
||||
__request__: Optional[Request] = None,
|
||||
) -> dict:
|
||||
"""
|
||||
Robust extraction of user context with multi-level fallback for language detection.
|
||||
Priority: localStorage (via JS) > HTTP headers > User profile > en-US
|
||||
"""
|
||||
user_data = __user__ if isinstance(__user__, dict) else {}
|
||||
user_id = user_data.get("id", "unknown_user")
|
||||
user_name = user_data.get("name", "User")
|
||||
user_language = user_data.get("language", "en-US")
|
||||
|
||||
# 1. Fallback: HTTP Accept-Language header
|
||||
if __request__ and hasattr(__request__, "headers"):
|
||||
accept_lang = __request__.headers.get("accept-language", "")
|
||||
if accept_lang:
|
||||
user_language = accept_lang.split(",")[0].split(";")[0]
|
||||
|
||||
# 2. Priority: Frontend localStorage via JS (requires timeout protection)
|
||||
if __event_call__:
|
||||
try:
|
||||
js_code = """
|
||||
try {
|
||||
return (
|
||||
document.documentElement.lang ||
|
||||
localStorage.getItem('locale') ||
|
||||
navigator.language ||
|
||||
'en-US'
|
||||
);
|
||||
} catch (e) {
|
||||
return 'en-US';
|
||||
}
|
||||
"""
|
||||
# MUST use wait_for with timeout (e.g., 2.0s) to prevent backend deadlock
|
||||
frontend_lang = await asyncio.wait_for(
|
||||
__event_call__({"type": "execute", "data": {"code": js_code}}),
|
||||
timeout=2.0
|
||||
)
|
||||
if frontend_lang and isinstance(frontend_lang, str):
|
||||
user_language = frontend_lang
|
||||
except Exception:
|
||||
pass # Fallback to existing language
|
||||
|
||||
return {
|
||||
"user_id": user_data.get("id", "unknown_user"),
|
||||
"user_name": user_data.get("name", "User"),
|
||||
"user_language": user_data.get("language", "en-US"),
|
||||
"user_id": user_id,
|
||||
"user_name": user_name,
|
||||
"user_language": user_language,
|
||||
}
|
||||
```
|
||||
|
||||
@@ -568,26 +605,31 @@ async def _get_user_context(
|
||||
```
|
||||
|
||||
#### 实际使用 (Usage in Action/Filter)
|
||||
|
||||
在 Action 或者 Filter 执行时引用这套上下文获取机制,然后传入映射器获取最终翻译:
|
||||
|
||||
|
||||
```python
|
||||
async def action(
|
||||
self,
|
||||
body: dict,
|
||||
__user__: Optional[dict] = None,
|
||||
__event_call__: Optional[callable] = None,
|
||||
__request__: Optional[Request] = None,
|
||||
**kwargs
|
||||
) -> Optional[dict]:
|
||||
async def action(self, body: dict, __user__: Optional[dict] = None, __event_call__: Optional[callable] = None, ...):
|
||||
user_ctx = await self._get_user_context(__user__, __event_call__)
|
||||
lang = user_ctx["user_language"]
|
||||
|
||||
user_ctx = await self._get_user_context(__user__, __event_call__, __request__)
|
||||
user_lang = user_ctx["user_language"]
|
||||
|
||||
# 获取多语言文本 (通过你的 translation.get() 扩展)
|
||||
# start_msg = self._get_translation(user_lang, "status_starting")
|
||||
# Use helper to get localized string with optional formatting
|
||||
msg = self._get_translation(lang, "status_starting", name=user_ctx["user_name"])
|
||||
await self._emit_status(emitter, msg)
|
||||
```
|
||||
|
||||
#### 提示词中的语言一致性 (Language Consistency in Prompts)
|
||||
|
||||
在为 LLM 生成系统提示词时,必须包含“输出语言与输入保持一致”的指令,以确保 i18n 逻辑在 AI 生成环节也不断裂。
|
||||
|
||||
**标准指令示例**:
|
||||
- `Language`: All output must be in the exact same language as the input text provided by the user.
|
||||
- `Format Consistency`: Even if this system prompt is in English, if the user input is in Chinese, your output must be in Chinese.
|
||||
|
||||
#### CJK 脚本的特殊考量 (CJK Script Considerations)
|
||||
|
||||
当涉及字符长度限制(如思维导图标题、卡片摘要)时,应区分 CJK(中日韩)和拉丁脚本。
|
||||
- **CJK (zh, ja, ko)**: 通常应设置更小的字符数限制(如 10 字以内)。
|
||||
- **Latin (en, es, fr)**: 可设置较长的字符限制(如 5-8 个词或 35 字符)。
|
||||
|
||||
### 9. 智能代理文件交付规范 (Agent File Delivery Standards)
|
||||
|
||||
在开发具备文件生成能力的智能代理插件(如 GitHub Copilot SDK 集成)时,必须遵循以下标准流程,以确保文件在不同存储后端(本地/S3)下的可用性并绕过不必要的 RAG 处理。
|
||||
|
||||
54
.github/instructions/code-review.instructions.md
vendored
Normal file
54
.github/instructions/code-review.instructions.md
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
name: Plugin Code Review
|
||||
description: Comprehensive OpenWebUI plugin review checklist covering i18n, context helpers, antigravity safety, and streaming compatibility
|
||||
applyTo: "plugins/**/*.py"
|
||||
---
|
||||
# Code Review Instructions — OpenWebUI Plugins
|
||||
|
||||
You are an expert Senior Software Engineer reviewing OpenWebUI plugins for the `openwebui-extensions` repository.
|
||||
When reviewing plugin code, you MUST verify each point below to ensure the code meets the strict repository standards.
|
||||
|
||||
## 1. Single-file i18n Pattern (CRITICAL)
|
||||
- **One File Rule**: One `.py` file per plugin. No `_cn.py` or language-split files.
|
||||
- **Translations**: All user-visible strings (status, notification, UI text) MUST go through a `TRANSLATIONS` dictionary and a `FALLBACK_MAP`.
|
||||
- **Safety**: `format(**kwargs)` calls on translated strings MUST be wrapped in `try/except KeyError` to prevent crashes if a translation is missing a placeholder.
|
||||
|
||||
## 2. Context Helpers (CRITICAL)
|
||||
- **User Context**: MUST use `_get_user_context(__user__)` instead of direct `__user__["name"]` access. `__user__` can be a list, dict, or None.
|
||||
- **Chat Context**: MUST use `_get_chat_context(body, __metadata__)` instead of ad-hoc `body.get("chat_id")` calls.
|
||||
|
||||
## 3. Event & Logging
|
||||
- **No Print**: No bare `print()` in production code. Use `logging.getLogger(__name__)`.
|
||||
- **Emitter Safety**: Every `await emitter(...)` call MUST be guarded by `if emitter:` (or equivalent).
|
||||
- **Status Lifecycle**:
|
||||
- `_emit_status(done=False)` at task start.
|
||||
- `_emit_status(done=True)` on completion.
|
||||
- `_emit_notification("error")` on failure.
|
||||
|
||||
## 4. Antigravity Safety (CRITICAL)
|
||||
- **Timeout Guards**: All `__event_call__` JS executions MUST be wrapped with `asyncio.wait_for(..., timeout=2.0)`. Failure to do this can hang the entire backend.
|
||||
- **JS Fallbacks**: JS code executed via `__event_call__` MUST have an internal `try { ... } catch (e) { return fallback; }` block.
|
||||
- **Path Sandboxing**: File path operations MUST be validated against the workspace root (no directory traversal vulnerabilities).
|
||||
- **Upload Fallbacks**: Upload paths MUST have a dual-channel fallback (API → local/DB).
|
||||
|
||||
## 5. Filter Singleton Safety
|
||||
- **No Mutable State**: Filter plugins are singletons. There MUST be NO request-scoped mutable state stored on `self` (e.g., `self.current_user = ...`).
|
||||
- **Statelessness**: Per-request values MUST be computed from `body` and context helpers on each call.
|
||||
|
||||
## 6. Copilot SDK Tools
|
||||
- **Pydantic Models**: Tool parameters MUST be defined as a `pydantic.BaseModel` subclass.
|
||||
- **Explicit Params**: `define_tool(...)` MUST include `params_type=MyParams`.
|
||||
- **Naming**: Tool names MUST match `^[a-zA-Z0-9_-]+$` (ASCII only).
|
||||
|
||||
## 7. Streaming Compatibility (OpenWebUI 0.8.x)
|
||||
- **Thinking Tags**: The `</think>` tag MUST be closed before any normal content or tool cards are emitted.
|
||||
- **Attribute Escaping**: Tool card `arguments` and `result` attributes MUST escape `"` as `"`.
|
||||
- **Newline Requirement**: The `<details type="tool_calls" ...>` block MUST be followed by a newline immediately after `>`.
|
||||
|
||||
## 8. Performance & Memory
|
||||
- **Streaming**: Large files or responses MUST be streamed, not loaded entirely into memory.
|
||||
- **Database Connections**: Plugins MUST reuse the OpenWebUI internal database connection (`owui_engine`, `owui_Session`) rather than creating new ones.
|
||||
|
||||
## 9. HTML Injection (Action Plugins)
|
||||
- **Wrapper Template**: HTML output MUST use the standard `<!-- OPENWEBUI_PLUGIN_OUTPUT -->` wrapper.
|
||||
- **Idempotency**: The plugin MUST implement `_remove_existing_html` to ensure multiple runs do not duplicate the HTML output.
|
||||
90
.github/instructions/commit-message.instructions.md
vendored
Normal file
90
.github/instructions/commit-message.instructions.md
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
name: Commit Message
|
||||
description: Comprehensive Conventional Commits guidelines for openwebui-extensions
|
||||
---
|
||||
# Commit Message Instructions
|
||||
|
||||
You are an expert developer generating commit messages for the `openwebui-extensions` repository.
|
||||
Always adhere to the following strict guidelines based on the Conventional Commits specification.
|
||||
|
||||
## 1. General Rules
|
||||
- **Language**: **English ONLY**. Never use Chinese or other languages in the commit title or body.
|
||||
- **Structure**:
|
||||
```text
|
||||
<type>(<scope>): <subject>
|
||||
<BLANK LINE>
|
||||
<body>
|
||||
<BLANK LINE>
|
||||
<footer>
|
||||
```
|
||||
|
||||
## 2. Type (`<type>`)
|
||||
Must be one of the following:
|
||||
- `feat`: A new feature or a new plugin.
|
||||
- `fix`: A bug fix.
|
||||
- `docs`: Documentation only changes (e.g., README, MkDocs).
|
||||
- `refactor`: A code change that neither fixes a bug nor adds a feature.
|
||||
- `perf`: A code change that improves performance.
|
||||
- `test`: Adding missing tests or correcting existing tests.
|
||||
- `chore`: Changes to the build process, CI/CD, or auxiliary tools/scripts.
|
||||
- `style`: Changes that do not affect the meaning of the code (white-space, formatting, etc.).
|
||||
|
||||
## 3. Scope (`<scope>`)
|
||||
The scope should indicate the affected component or plugin.
|
||||
- **For Plugins**: Use the plugin's folder name or category (e.g., `actions`, `filters`, `pipes`, `export-to-docx`, `github-copilot-sdk`).
|
||||
- **For Docs**: Use `docs` or the specific doc section (e.g., `guide`, `readme`).
|
||||
- **For Infra**: Use `ci`, `scripts`, `deps`.
|
||||
- *Note*: Omit the scope if the change is global or spans too many components.
|
||||
|
||||
## 4. Subject Line (`<subject>`)
|
||||
- **Length**: Keep it under 50-72 characters.
|
||||
- **Mood**: Use the imperative, present tense: "change" not "changed" nor "changes" (e.g., "add feature" instead of "added feature").
|
||||
- **Formatting**: Do not capitalize the first letter. Do not place a period `.` at the end.
|
||||
- **Clarity**: State exactly *what* changed. Avoid vague terms like "update code" or "fix bug".
|
||||
|
||||
## 5. Body (`<body>`)
|
||||
- **When to use**: Highly recommended for `feat`, `fix`, and `refactor`.
|
||||
- **Content**: Explain *what* and *why* (motivation), not just *how* (the code diff shows the how).
|
||||
- **Format**: Use a bulleted list (1-3 points) for readability.
|
||||
- **Repo Specifics**: If the commit involves a plugin version bump, explicitly mention that the READMEs and docs were synced.
|
||||
|
||||
## 6. Footer / Breaking Changes (`<footer>`)
|
||||
- If the commit introduces a breaking change (e.g., changing a Valve configuration key, altering a plugin's output format), append `!` after the scope/type: `feat(pipes)!: ...`
|
||||
- Add a `BREAKING CHANGE:` block in the footer explaining the migration path.
|
||||
|
||||
## 7. Examples
|
||||
|
||||
**Example 1: Feature with Body**
|
||||
```text
|
||||
feat(github-copilot-sdk): add antigravity timeout guards
|
||||
|
||||
- Wrap all __event_call__ JS executions with asyncio.wait_for(timeout=2.0)
|
||||
- Add dual-channel upload fallback (API → local/DB)
|
||||
- Emit staged status events for tasks > 3 seconds
|
||||
```
|
||||
|
||||
**Example 2: Bug Fix**
|
||||
```text
|
||||
fix(export-to-docx): resolve missing title fallback
|
||||
|
||||
- Fallback to user_name + date when chat_title is empty
|
||||
- Prevent crash when metadata variables are missing
|
||||
```
|
||||
|
||||
**Example 3: Documentation Sync**
|
||||
```text
|
||||
docs(plugins): sync bilingual READMEs for v1.2.0 release
|
||||
|
||||
- Update What's New section for folder-memory plugin
|
||||
- Sync docs/plugins/filters/folder-memory.md
|
||||
```
|
||||
|
||||
**Example 4: Breaking Change**
|
||||
```text
|
||||
refactor(core)!: rename global configuration valves
|
||||
|
||||
- Standardize all valve keys to UPPER_SNAKE_CASE
|
||||
- Remove deprecated legacy_api_key field
|
||||
|
||||
BREAKING CHANGE: Users must reconfigure their API keys in the UI as the old `api_key` field is no longer read.
|
||||
```
|
||||
54
.github/instructions/test-generation.instructions.md
vendored
Normal file
54
.github/instructions/test-generation.instructions.md
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
name: Test Generation
|
||||
description: Comprehensive Pytest conventions, mocking strategies, and model usage rules for OpenWebUI plugins
|
||||
applyTo: "plugins/debug/**,tests/**"
|
||||
---
|
||||
# Test Generation Instructions
|
||||
|
||||
You are an expert SDET (Software Development Engineer in Test) writing tests for the `openwebui-extensions` repository.
|
||||
Follow these comprehensive guidelines to ensure robust, reliable, and cost-effective testing.
|
||||
|
||||
## 1. Test Structure & Placement
|
||||
- **Unit Tests**: Place in the `tests/` directory at the root of the repository. Name files `test_<module>.py`.
|
||||
- **Integration/Debug Scripts**: Place in `plugins/debug/<plugin_name>/`. Name files `debug_<feature>.py` or `inspect_<feature>.py`.
|
||||
- **Fixtures**: Use `conftest.py` for shared fixtures (e.g., mock users, mock emitters, mock database sessions).
|
||||
|
||||
## 2. Model Usage & Cost Control (CRITICAL)
|
||||
When writing tests or debug scripts that actually invoke an LLM:
|
||||
- **Allowed Models**: You MUST use low-cost models: `gpt-5-mini` (Preferred) or `gpt-4.1`.
|
||||
- **Forbidden**: NEVER use expensive models (like `gpt-4-turbo`, `claude-3-opus`) in automated tests unless explicitly requested by the user.
|
||||
- **API Keys**: Never hardcode API keys. Always read from environment variables (`os.getenv("OPENAI_API_KEY")`) or a `.env` file.
|
||||
|
||||
## 3. Mocking OpenWebUI Internals
|
||||
OpenWebUI plugins rely heavily on injected runtime variables. Your tests must mock these accurately:
|
||||
- **`__user__`**: Mock as a dictionary. Always test both with a full user object and an empty/None object to ensure fallback logic works.
|
||||
```python
|
||||
mock_user_en = {"id": "u1", "name": "Alice", "language": "en-US"}
|
||||
mock_user_zh = {"id": "u2", "name": "Bob", "language": "zh-CN"}
|
||||
mock_user_none = None
|
||||
```
|
||||
- **`__event_emitter__`**: Mock as an async callable that records emitted events for assertion.
|
||||
```python
|
||||
async def mock_emitter(event: dict):
|
||||
emitted_events.append(event)
|
||||
```
|
||||
- **`__event_call__`**: Mock as an async callable. To test Antigravity timeout guards, you must occasionally mock this to sleep longer than the timeout (e.g., `await asyncio.sleep(3.0)`) to ensure `asyncio.wait_for` catches it.
|
||||
|
||||
## 4. Testing Async Code
|
||||
- All OpenWebUI plugin entry points (`action`, `inlet`, `outlet`) are asynchronous.
|
||||
- Use `@pytest.mark.asyncio` for all test functions.
|
||||
- Ensure you `await` all plugin method calls.
|
||||
|
||||
## 5. i18n (Internationalization) Testing
|
||||
Since all plugins must be single-file i18n:
|
||||
- **Mandatory**: Write parameterized tests to verify output in both English (`en-US`) and Chinese (`zh-CN`).
|
||||
- Verify that if an unsupported language is passed (e.g., `fr-FR`), the system correctly falls back to the default (usually `en-US`).
|
||||
|
||||
## 6. Antigravity & Safety Testing
|
||||
- **Timeout Guards**: Explicitly test that frontend JS executions (`__event_call__`) do not hang the backend. Assert that a `TimeoutError` is caught and handled gracefully.
|
||||
- **State Leakage**: For `Filter` plugins (which are singletons), write tests that simulate concurrent requests from different users to ensure no state leaks across `self`.
|
||||
|
||||
## 7. Assertions & Best Practices
|
||||
- Assert on **behavior and output**, not internal implementation details.
|
||||
- For HTML/Markdown generation plugins, use Regex or BeautifulSoup to assert the presence of required tags (e.g., `<!-- OPENWEBUI_PLUGIN_OUTPUT -->`) rather than exact string matching.
|
||||
- Keep tests isolated. Do not rely on the execution order of tests.
|
||||
44
.github/skills/README.md
vendored
Normal file
44
.github/skills/README.md
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
# Agent Skills Index
|
||||
|
||||
This folder contains reusable Agent Skills for GitHub Copilot / VS Code custom agent workflows.
|
||||
|
||||
## Available Skills
|
||||
|
||||
- **community-announcer**
|
||||
- Purpose: Generate community announcement content and related assets.
|
||||
- Entry: `community-announcer/SKILL.md`
|
||||
|
||||
- **doc-mirror-sync**
|
||||
- Purpose: Sync mirrored documentation content and helper scripts.
|
||||
- Entry: `doc-mirror-sync/SKILL.md`
|
||||
|
||||
- **gh-issue-replier**
|
||||
- Purpose: Draft standardized issue replies with templates.
|
||||
- Entry: `gh-issue-replier/SKILL.md`
|
||||
|
||||
- **gh-issue-scheduler**
|
||||
- Purpose: Schedule and discover unanswered issues for follow-up.
|
||||
- Entry: `gh-issue-scheduler/SKILL.md`
|
||||
|
||||
- **i18n-validator**
|
||||
- Purpose: Validate translation key consistency across i18n dictionaries.
|
||||
- Entry: `i18n-validator/SKILL.md`
|
||||
|
||||
- **plugin-scaffolder**
|
||||
- Purpose: Scaffold OpenWebUI plugin boilerplate with repository standards.
|
||||
- Entry: `plugin-scaffolder/SKILL.md`
|
||||
|
||||
- **version-bumper**
|
||||
- Purpose: Assist with semantic version bumping workflows.
|
||||
- Entry: `version-bumper/SKILL.md`
|
||||
|
||||
- **xlsx-single-file**
|
||||
- Purpose: Single-file spreadsheet operations workflow without LibreOffice.
|
||||
- Entry: `xlsx-single-file/SKILL.md`
|
||||
|
||||
## Notes
|
||||
|
||||
- Skill definitions follow the expected location pattern:
|
||||
- `.github/skills/<skill-name>/SKILL.md`
|
||||
- Each skill may include optional `assets/`, `references/`, and `scripts/` folders.
|
||||
- This directory mirrors `.gemini/skills` for compatibility.
|
||||
23
.github/skills/community-announcer/SKILL.md
vendored
Normal file
23
.github/skills/community-announcer/SKILL.md
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: community-announcer
|
||||
description: Drafts engaging English and Chinese update announcements for the OpenWebUI Community and other social platforms. Use when a new version is released.
|
||||
---
|
||||
|
||||
# Community Announcer
|
||||
|
||||
## Overview
|
||||
Automates the drafting of high-impact update announcements.
|
||||
|
||||
## Workflow
|
||||
1. **Source Intel**: Read the latest version's `What's New` section from `README.md`.
|
||||
2. **Drafting**: Create two versions:
|
||||
- **Community Post**: Professional, structured, technical.
|
||||
- **Catchy Short**: For Discord/Twitter, use emojis and bullet points.
|
||||
3. **Multi-language**: Generate BOTH English and Chinese versions automatically.
|
||||
|
||||
## Announcement Structure (Recommended)
|
||||
- **Headline**: "Update vX.X.X - [Main Feature]"
|
||||
- **Introduction**: Brief context.
|
||||
- **Key Highlights**: Bulleted list of fixes/features.
|
||||
- **Action**: "Download from [Market Link]"
|
||||
- **Closing**: Thanks and Star request.
|
||||
14
.github/skills/doc-mirror-sync/SKILL.md
vendored
Normal file
14
.github/skills/doc-mirror-sync/SKILL.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: doc-mirror-sync
|
||||
description: Automatically synchronizes plugin READMEs to the official documentation directory (docs/). Use after editing a plugin's local documentation to keep the MkDocs site up to date.
|
||||
---
|
||||
|
||||
# Doc Mirror Sync
|
||||
|
||||
## Overview
|
||||
Automates the mirroring of `plugins/{type}/{name}/README.md` to `docs/plugins/{type}/{name}.md`.
|
||||
|
||||
## Workflow
|
||||
1. Identify changed READMEs.
|
||||
2. Copy content to corresponding mirror paths.
|
||||
3. Update version badges in `docs/plugins/{type}/index.md`.
|
||||
38
.github/skills/doc-mirror-sync/scripts/sync.py
vendored
Normal file
38
.github/skills/doc-mirror-sync/scripts/sync.py
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import shutil
|
||||
import re
|
||||
|
||||
def sync_mirrors():
|
||||
plugins_root = "plugins"
|
||||
docs_root = "docs/plugins"
|
||||
|
||||
types = ["actions", "filters", "pipes", "pipelines", "tools"]
|
||||
|
||||
for t in types:
|
||||
src_type_dir = os.path.join(plugins_root, t)
|
||||
dest_type_dir = os.path.join(docs_root, t)
|
||||
|
||||
if not os.path.exists(src_type_dir): continue
|
||||
os.makedirs(dest_type_dir, exist_ok=True)
|
||||
|
||||
for name in os.listdir(src_type_dir):
|
||||
plugin_dir = os.path.join(src_type_dir, name)
|
||||
if not os.path.isdir(plugin_dir): continue
|
||||
|
||||
# Sync README.md -> docs/plugins/{type}/{name}.md
|
||||
src_readme = os.path.join(plugin_dir, "README.md")
|
||||
if os.path.exists(src_readme):
|
||||
dest_readme = os.path.join(dest_type_dir, f"{name}.md")
|
||||
shutil.copy(src_readme, dest_readme)
|
||||
print(f"✅ Mirrored: {t}/{name} (EN)")
|
||||
|
||||
# Sync README_CN.md -> docs/plugins/{type}/{name}.zh.md
|
||||
src_readme_cn = os.path.join(plugin_dir, "README_CN.md")
|
||||
if os.path.exists(src_readme_cn):
|
||||
dest_readme_zh = os.path.join(dest_type_dir, f"{name}.zh.md")
|
||||
shutil.copy(src_readme_cn, dest_readme_zh)
|
||||
print(f"✅ Mirrored: {t}/{name} (ZH)")
|
||||
|
||||
if __name__ == "__main__":
|
||||
sync_mirrors()
|
||||
51
.github/skills/gh-issue-replier/SKILL.md
vendored
Normal file
51
.github/skills/gh-issue-replier/SKILL.md
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: gh-issue-replier
|
||||
description: Professional English replier for GitHub issues. Use when a task is completed, a bug is fixed, or more info is needed from the user. Automates replying using the 'gh' CLI tool.
|
||||
---
|
||||
|
||||
# Gh Issue Replier
|
||||
|
||||
## Overview
|
||||
|
||||
The `gh-issue-replier` skill enables Gemini CLI to interact with GitHub issues professionally. It enforces English for all communications and leverages the `gh` CLI to post comments.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Identify the Issue**: Find the issue number (e.g., #49).
|
||||
2. **Check Star Status**: Run the bundled script to check if the author has starred the repo.
|
||||
* Command: `bash scripts/check_star.sh <issue-number>`
|
||||
* Interpretation:
|
||||
* Exit code **0**: User has starred. Use "Already Starred" templates.
|
||||
* Exit code **1**: User has NOT starred. Include "Star Request" in the reply.
|
||||
3. **Select a Template**: Load [templates.md](references/templates.md) to choose a suitable English response pattern.
|
||||
4. **Draft the Reply**: Compose a concise message based on the star status.
|
||||
5. **Post the Comment**: Use the `gh` tool to submit the reply.
|
||||
|
||||
## Tool Integration
|
||||
|
||||
### Check Star Status
|
||||
```bash
|
||||
bash scripts/check_star.sh <issue-number>
|
||||
```
|
||||
|
||||
### Post Comment
|
||||
```bash
|
||||
gh issue comment <issue-number> --body "<message-body>"
|
||||
```
|
||||
|
||||
Example (if user has NOT starred):
|
||||
```bash
|
||||
gh issue comment 49 --body "This has been fixed in v1.2.7. If you find this helpful, a star on the repo would be much appreciated! ⭐"
|
||||
```
|
||||
|
||||
Example (if user HAS starred):
|
||||
```bash
|
||||
gh issue comment 49 --body "This has been fixed in v1.2.7. Thanks for your support!"
|
||||
```
|
||||
|
||||
## Guidelines
|
||||
|
||||
- **Language**: ALWAYS use English for the comment body, even if the system prompt or user conversation is in another language.
|
||||
- **Tone**: Professional, helpful, and appreciative.
|
||||
- **Precision**: When announcing a fix, mention the specific version or the logic change (e.g., "Updated regex pattern").
|
||||
- **Closing**: If the issue is resolved and you have permission, you can also use `gh issue close <number>`.
|
||||
17
.github/skills/gh-issue-replier/references/example_reference.md
vendored
Normal file
17
.github/skills/gh-issue-replier/references/example_reference.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Reference Documentation for Gh Issue Replier
|
||||
|
||||
This is a placeholder for detailed reference documentation.
|
||||
Replace with actual reference content or delete if not needed.
|
||||
|
||||
## Structure Suggestions
|
||||
|
||||
### API Reference Example
|
||||
- Overview
|
||||
- Authentication
|
||||
- Endpoints with examples
|
||||
- Error codes
|
||||
|
||||
### Workflow Guide Example
|
||||
- Prerequisites
|
||||
- Step-by-step instructions
|
||||
- Best practices
|
||||
45
.github/skills/gh-issue-replier/references/templates.md
vendored
Normal file
45
.github/skills/gh-issue-replier/references/templates.md
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
# Issue Reply Templates
|
||||
|
||||
Use these templates to craft professional English replies. Adjust placeholders like `@username`, `v1.2.x`, and `[commit hash]` as needed.
|
||||
|
||||
## 1. Acknowledging a New Issue
|
||||
Use when you first see an issue and want to let the user know you are working on it.
|
||||
|
||||
- "Thank you for reporting this! I'm looking into it right now."
|
||||
- "Thanks for bringing this to my attention. I'll try to reproduce this behavior and get back to you shortly."
|
||||
|
||||
## 2. Requesting More Information
|
||||
Use when you need logs or specific details to fix the bug.
|
||||
|
||||
- "Could you please provide the **'Original'** vs **'Normalized'** content from your browser console logs (F12)? It would help a lot in debugging."
|
||||
- "It would be very helpful if you could share the specific Markdown text that triggered this issue."
|
||||
|
||||
## 3. Announcing a Fix
|
||||
Use when you have pushed the fix to the repository.
|
||||
|
||||
- "This has been fixed in version **v1.2.x**. You can update the plugin to resolve it."
|
||||
- "I've just pushed a fix for this in [commit hash]. Please let me know if it works for you after updating."
|
||||
- "The issue was caused by a greedy regex pattern. I've updated it to use a tempered greedy token to prevent incorrect merging."
|
||||
|
||||
## 4. Guiding to Official Market
|
||||
Always provide the official market link to ensure the user gets the latest verified version.
|
||||
|
||||
- "The fix is now live! You can download the latest version from the official OpenWebUI Community page here: [Plugin Market Link]. Simply update the function in your OpenWebUI instance to apply the changes."
|
||||
- "I recommend getting the updated version from the official store: [Link]. It includes the fix for the spacing issue we discussed."
|
||||
|
||||
## 5. Closing the Issue
|
||||
Use when the issue is confirmed resolved.
|
||||
|
||||
- "Glad to hear it's working now! Closing this for now. Feel free to reopen it if the problem persists."
|
||||
- "Since this is resolved, I'm closing this issue. Thanks again for your feedback!"
|
||||
|
||||
## 5. Pro-tip: Star Request
|
||||
Gently handle star requests based on the user's current status.
|
||||
|
||||
### If User has NOT starred:
|
||||
- "If you find this plugin helpful, a star on the repo would be much appreciated! ⭐"
|
||||
- "We'd love your support! If this fixed your issue, please consider starring the repository. ⭐"
|
||||
|
||||
### If User HAS already starred:
|
||||
- "Thanks again for starring the project and for your continuous support!"
|
||||
- "I appreciate your support and for being a stargazer of this project!"
|
||||
31
.github/skills/gh-issue-replier/scripts/check_star.sh
vendored
Executable file
31
.github/skills/gh-issue-replier/scripts/check_star.sh
vendored
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Robust Star Checker v2
|
||||
# Usage: ./check_star.sh <issue_number>
|
||||
|
||||
ISSUE_NUM=$1
|
||||
if [ -z "$ISSUE_NUM" ]; then exit 2; fi
|
||||
|
||||
# 1. Get Repo and Author info
|
||||
REPO_FULL=$(gh repo view --json owner,name -q ".owner.login + \"/\" + .name")
|
||||
USER_LOGIN=$(gh issue view "$ISSUE_NUM" --json author -q ".author.login")
|
||||
|
||||
# 2. Use GraphQL for high precision (Detects stars even when REST 404s)
|
||||
IS_STARRED=$(gh api graphql -f query='
|
||||
query($owner:String!, $repo:String!, $user:String!) {
|
||||
repository(owner:$owner, name:$repo) {
|
||||
stargazers(query:$user, first:1) {
|
||||
nodes {
|
||||
login
|
||||
}
|
||||
}
|
||||
}
|
||||
}' -f owner="${REPO_FULL%/*}" -f repo="${REPO_FULL#*/}" -f user="$USER_LOGIN" -q ".data.repository.stargazers.nodes[0].login")
|
||||
|
||||
if [ "$IS_STARRED" == "$USER_LOGIN" ]; then
|
||||
echo "Confirmed: @$USER_LOGIN HAS starred $REPO_FULL. ⭐"
|
||||
exit 0
|
||||
else
|
||||
echo "Confirmed: @$USER_LOGIN has NOT starred $REPO_FULL."
|
||||
exit 1
|
||||
fi
|
||||
42
.github/skills/gh-issue-scheduler/SKILL.md
vendored
Normal file
42
.github/skills/gh-issue-scheduler/SKILL.md
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: gh-issue-scheduler
|
||||
description: Finds all open GitHub issues that haven't been replied to by the owner, summarizes them, and generates a solution plan. Use when the user wants to audit pending tasks or plan maintenance work.
|
||||
---
|
||||
|
||||
# Gh Issue Scheduler
|
||||
|
||||
## Overview
|
||||
|
||||
The `gh-issue-scheduler` skill helps maintainers track community feedback by identifying unaddressed issues and drafting actionable technical plans to resolve them.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Identify Unanswered Issues**: Run the bundled script to fetch issues without owner replies.
|
||||
* Command: `bash scripts/find_unanswered.sh`
|
||||
2. **Analyze and Summarize**: For each identified issue, summarize the core problem and the user's intent.
|
||||
3. **Generate Solution Plans**: Draft a technical "Action Plan" for each issue, including:
|
||||
* **Root Cause Analysis** (if possible)
|
||||
* **Proposed Fix/Implementation**
|
||||
* **Verification Strategy**
|
||||
4. **Present to User**: Display a structured report of all pending issues and their respective plans.
|
||||
|
||||
## Tool Integration
|
||||
|
||||
### Find Unanswered Issues
|
||||
```bash
|
||||
bash scripts/find_unanswered.sh
|
||||
```
|
||||
|
||||
## Report Format
|
||||
|
||||
When presenting the summary, use the following Markdown structure:
|
||||
|
||||
### 📋 Unanswered Issues Audit
|
||||
|
||||
#### Issue #[Number]: [Title]
|
||||
- **Author**: @username
|
||||
- **Summary**: Concise description of the problem.
|
||||
- **Action Plan**:
|
||||
1. Step 1 (e.g., Investigate file X)
|
||||
2. Step 2 (e.g., Apply fix Y)
|
||||
3. Verification (e.g., Run test Z)
|
||||
42
.github/skills/gh-issue-scheduler/scripts/find_unanswered.sh
vendored
Executable file
42
.github/skills/gh-issue-scheduler/scripts/find_unanswered.sh
vendored
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Fetch all open issues and filter for those without responses from the owner/collaborators.
|
||||
# Uses 'gh' CLI.
|
||||
|
||||
REPO_FULL=$(gh repo view --json owner,name -q ".owner.login + "/" + .name")
|
||||
OWNER=${REPO_FULL%/*}
|
||||
|
||||
# 1. Get all open issues
|
||||
OPEN_ISSUES=$(gh issue list --state open --json number,title,author,createdAt --limit 100)
|
||||
|
||||
echo "Analysis for repository: $REPO_FULL"
|
||||
echo "------------------------------------"
|
||||
|
||||
# Process each issue
|
||||
echo "$OPEN_ISSUES" | jq -c '.[]' | while read -r issue; do
|
||||
NUMBER=$(echo "$issue" | jq -r '.number')
|
||||
TITLE=$(echo "$issue" | jq -r '.title')
|
||||
AUTHOR=$(echo "$issue" | jq -r '.author.login')
|
||||
|
||||
# Check comments for owner responses
|
||||
# We look for comments where the author is the repo owner
|
||||
COMMENTS=$(gh issue view "$NUMBER" --json comments -q ".comments[].author.login" 2>/dev/null)
|
||||
|
||||
HAS_OWNER_REPLY=false
|
||||
for COMMENT_AUTHOR in $COMMENTS; do
|
||||
if [ "$COMMENT_AUTHOR" == "$OWNER" ]; then
|
||||
HAS_OWNER_REPLY=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$HAS_OWNER_REPLY" == "false" ]; then
|
||||
echo "ISSUE_START"
|
||||
echo "ID: $NUMBER"
|
||||
echo "Title: $TITLE"
|
||||
echo "Author: $AUTHOR"
|
||||
echo "Description:"
|
||||
gh issue view "$NUMBER" --json body -q ".body"
|
||||
echo "ISSUE_END"
|
||||
fi
|
||||
done
|
||||
14
.github/skills/i18n-validator/SKILL.md
vendored
Normal file
14
.github/skills/i18n-validator/SKILL.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: i18n-validator
|
||||
description: Validates multi-language consistency in the TRANSLATIONS dictionary of a plugin. Use to check if any language keys are missing or if translations need updating.
|
||||
---
|
||||
|
||||
# I18n Validator
|
||||
|
||||
## Overview
|
||||
Ensures all 12 supported languages (en-US, zh-CN, etc.) have aligned translation keys.
|
||||
|
||||
## Features
|
||||
- Detects missing keys in non-English dictionaries.
|
||||
- Suggests translations using the core AI engine.
|
||||
- Validates the `fallback_map` for variant redirects.
|
||||
54
.github/skills/i18n-validator/scripts/validate_i18n.py
vendored
Normal file
54
.github/skills/i18n-validator/scripts/validate_i18n.py
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import ast
|
||||
import os
|
||||
|
||||
def check_i18n(file_path):
|
||||
if not os.path.exists(file_path):
|
||||
print(f"Error: File not found {file_path}")
|
||||
return
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
tree = ast.parse(f.read())
|
||||
|
||||
translations = {}
|
||||
for node in tree.body:
|
||||
if isinstance(node, ast.Assign):
|
||||
for target in node.targets:
|
||||
if isinstance(target, ast.Name) and target.id == "TRANSLATIONS":
|
||||
translations = ast.literal_eval(node.value)
|
||||
break
|
||||
|
||||
if not translations:
|
||||
print("⚠️ No TRANSLATIONS dictionary found.")
|
||||
return
|
||||
|
||||
# Base keys from English
|
||||
base_lang = "en-US"
|
||||
if base_lang not in translations:
|
||||
print(f"❌ Error: {base_lang} missing in TRANSLATIONS.")
|
||||
return
|
||||
|
||||
base_keys = set(translations[base_lang].keys())
|
||||
print(f"🔍 Analyzing {file_path}...")
|
||||
print(f"Standard keys ({len(base_keys)}): {', '.join(sorted(base_keys))}
|
||||
")
|
||||
|
||||
for lang, keys in translations.items():
|
||||
if lang == base_lang: continue
|
||||
lang_keys = set(keys.keys())
|
||||
missing = base_keys - lang_keys
|
||||
extra = lang_keys - base_keys
|
||||
|
||||
if missing:
|
||||
print(f"❌ {lang}: Missing {len(missing)} keys: {', '.join(missing)}")
|
||||
if extra:
|
||||
print(f"⚠️ {lang}: Has {len(extra)} extra keys: {', '.join(extra)}")
|
||||
if not missing and not extra:
|
||||
print(f"✅ {lang}: Aligned.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: validate_i18n.py <path_to_plugin.py>")
|
||||
sys.exit(1)
|
||||
check_i18n(sys.argv[1])
|
||||
19
.github/skills/plugin-scaffolder/SKILL.md
vendored
Normal file
19
.github/skills/plugin-scaffolder/SKILL.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: plugin-scaffolder
|
||||
description: Generates a standardized single-file i18n Python plugin template based on project standards. Use when starting a new plugin development to skip boilerplate writing.
|
||||
---
|
||||
|
||||
# Plugin Scaffolder
|
||||
|
||||
## Overview
|
||||
Generates compliant OpenWebUI plugin templates with built-in i18n, common utility methods, and required docstring fields.
|
||||
|
||||
## Usage
|
||||
1. Provide the **Plugin Name** and **Type** (action/filter/pipe).
|
||||
2. The skill will generate the `.py` file and the bilingual `README` files.
|
||||
|
||||
## Template Standard
|
||||
- `Valves(BaseModel)` with `UPPER_SNAKE_CASE`
|
||||
- `_get_user_context` with JS fallback and timeout
|
||||
- `_emit_status` and `_emit_debug_log` methods
|
||||
- Standardized docstring metadata
|
||||
34
.github/skills/plugin-scaffolder/assets/README_template.md
vendored
Normal file
34
.github/skills/plugin-scaffolder/assets/README_template.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# {{TITLE}}
|
||||
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.1.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
|
||||
|
||||
{{DESCRIPTION}}
|
||||
|
||||
## 🔥 What's New in v0.1.0
|
||||
|
||||
* Initial release of {{TITLE}}.
|
||||
|
||||
## 🌐 Multilingual Support
|
||||
|
||||
Supports automatic interface and status switching for the following languages:
|
||||
`English`, `简体中文`, `繁體中文 (香港)`, `繁體中文 (台灣)`, `한국어`, `日本語`, `Français`, `Deutsch`, `Español`, `Italiano`, `Tiếng Việt`, `Bahasa Indonesia`.
|
||||
|
||||
## ✨ Core Features
|
||||
|
||||
* Feature 1
|
||||
* Feature 2
|
||||
|
||||
## How to Use 🛠️
|
||||
|
||||
1. Install the plugin in Open WebUI.
|
||||
2. Configure settings in Valves.
|
||||
|
||||
## Configuration (Valves) ⚙️
|
||||
|
||||
| Parameter | Default | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `priority` | `50` | Execution priority. |
|
||||
|
||||
## ⭐ Support
|
||||
|
||||
If this plugin has been useful, a star on [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) is a big motivation for me. Thank you for the support.
|
||||
80
.github/skills/plugin-scaffolder/assets/template.py
vendored
Normal file
80
.github/skills/plugin-scaffolder/assets/template.py
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
title: {{TITLE}}
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie/openwebui-extensions
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 0.1.0
|
||||
description: {{DESCRIPTION}}
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import json
|
||||
from typing import Optional, Dict, Any, List, Callable, Awaitable
|
||||
from pydantic import BaseModel, Field
|
||||
from fastapi import Request
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TRANSLATIONS = {
|
||||
"en-US": {"status_starting": "Starting {{TITLE}}..."},
|
||||
"zh-CN": {"status_starting": "正在启动 {{TITLE}}..."},
|
||||
"zh-HK": {"status_starting": "正在啟動 {{TITLE}}..."},
|
||||
"zh-TW": {"status_starting": "正在啟動 {{TITLE}}..."},
|
||||
"ko-KR": {"status_starting": "{{TITLE}} 시작 중..."},
|
||||
"ja-JP": {"status_starting": "{{TITLE}} を起動中..."},
|
||||
"fr-FR": {"status_starting": "Démarrage de {{TITLE}}..."},
|
||||
"de-DE": {"status_starting": "{{TITLE}} wird gestartet..."},
|
||||
"es-ES": {"status_starting": "Iniciando {{TITLE}}..."},
|
||||
"it-IT": {"status_starting": "Avvio di {{TITLE}}..."},
|
||||
"vi-VN": {"status_starting": "Đang khởi động {{TITLE}}..."},
|
||||
"id-ID": {"status_starting": "Memulai {{TITLE}}..."},
|
||||
}
|
||||
|
||||
class {{CLASS_NAME}}:
|
||||
class Valves(BaseModel):
|
||||
priority: int = Field(default=50, description="Priority level (lower = earlier).")
|
||||
show_status: bool = Field(default=True, description="Show status updates in UI.")
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
self.fallback_map = {
|
||||
"zh": "zh-CN", "en": "en-US", "ko": "ko-KR", "ja": "ja-JP",
|
||||
"fr": "fr-FR", "de": "de-DE", "es": "es-ES", "it": "it-IT",
|
||||
"vi": "vi-VN", "id": "id-ID"
|
||||
}
|
||||
|
||||
def _get_translation(self, lang: str, key: str, **kwargs) -> str:
|
||||
target_lang = lang
|
||||
if target_lang not in TRANSLATIONS:
|
||||
base = target_lang.split("-")[0]
|
||||
target_lang = self.fallback_map.get(base, "en-US")
|
||||
|
||||
lang_dict = TRANSLATIONS.get(target_lang, TRANSLATIONS["en-US"])
|
||||
text = lang_dict.get(key, TRANSLATIONS["en-US"].get(key, key))
|
||||
return text.format(**kwargs) if kwargs else text
|
||||
|
||||
async def _get_user_context(self, __user__: Optional[dict], __event_call__: Optional[Callable] = None, __request__: Optional[Request] = None) -> dict:
|
||||
user_data = __user__ if isinstance(__user__, dict) else {}
|
||||
user_language = user_data.get("language", "en-US")
|
||||
if __event_call__:
|
||||
try:
|
||||
js = "try { return (document.documentElement.lang || localStorage.getItem('locale') || navigator.language || 'en-US'); } catch (e) { return 'en-US'; }"
|
||||
frontend_lang = await asyncio.wait_for(__event_call__({"type": "execute", "data": {"code": js}}), timeout=2.0)
|
||||
if frontend_lang: user_language = frontend_lang
|
||||
except: pass
|
||||
return {"user_language": user_language}
|
||||
|
||||
async def {{METHOD_NAME}}(self, body: dict, __user__: Optional[dict] = None, __event_emitter__=None, __event_call__=None, __request__: Optional[Request] = None) -> dict:
|
||||
if self.valves.show_status and __event_emitter__:
|
||||
user_ctx = await self._get_user_context(__user__, __event_call__, __request__)
|
||||
msg = self._get_translation(user_ctx["user_language"], "status_starting")
|
||||
await __event_emitter__({"type": "status", "data": {"description": msg, "done": False}})
|
||||
|
||||
# Implement core logic here
|
||||
|
||||
if self.valves.show_status and __event_emitter__:
|
||||
await __event_emitter__({"type": "status", "data": {"description": "Done", "done": True}})
|
||||
return body
|
||||
80
.github/skills/plugin-scaffolder/assets/template.py.j2
vendored
Normal file
80
.github/skills/plugin-scaffolder/assets/template.py.j2
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
title: {{TITLE}}
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie/openwebui-extensions
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 0.1.0
|
||||
description: {{DESCRIPTION}}
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import json
|
||||
from typing import Optional, Dict, Any, List, Callable, Awaitable
|
||||
from pydantic import BaseModel, Field
|
||||
from fastapi import Request
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TRANSLATIONS = {
|
||||
"en-US": {"status_starting": "Starting {{TITLE}}..."},
|
||||
"zh-CN": {"status_starting": "正在启动 {{TITLE}}..."},
|
||||
"zh-HK": {"status_starting": "正在啟動 {{TITLE}}..."},
|
||||
"zh-TW": {"status_starting": "正在啟動 {{TITLE}}..."},
|
||||
"ko-KR": {"status_starting": "{{TITLE}} 시작 중..."},
|
||||
"ja-JP": {"status_starting": "{{TITLE}} を起動中..."},
|
||||
"fr-FR": {"status_starting": "Démarrage de {{TITLE}}..."},
|
||||
"de-DE": {"status_starting": "{{TITLE}} wird gestartet..."},
|
||||
"es-ES": {"status_starting": "Iniciando {{TITLE}}..."},
|
||||
"it-IT": {"status_starting": "Avvio di {{TITLE}}..."},
|
||||
"vi-VN": {"status_starting": "Đang khởi động {{TITLE}}..."},
|
||||
"id-ID": {"status_starting": "Memulai {{TITLE}}..."},
|
||||
}
|
||||
|
||||
class {{CLASS_NAME}}:
|
||||
class Valves(BaseModel):
|
||||
priority: int = Field(default=50, description="Priority level (lower = earlier).")
|
||||
show_status: bool = Field(default=True, description="Show status updates in UI.")
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
self.fallback_map = {
|
||||
"zh": "zh-CN", "en": "en-US", "ko": "ko-KR", "ja": "ja-JP",
|
||||
"fr": "fr-FR", "de": "de-DE", "es": "es-ES", "it": "it-IT",
|
||||
"vi": "vi-VN", "id": "id-ID"
|
||||
}
|
||||
|
||||
def _get_translation(self, lang: str, key: str, **kwargs) -> str:
|
||||
target_lang = lang
|
||||
if target_lang not in TRANSLATIONS:
|
||||
base = target_lang.split("-")[0]
|
||||
target_lang = self.fallback_map.get(base, "en-US")
|
||||
|
||||
lang_dict = TRANSLATIONS.get(target_lang, TRANSLATIONS["en-US"])
|
||||
text = lang_dict.get(key, TRANSLATIONS["en-US"].get(key, key))
|
||||
return text.format(**kwargs) if kwargs else text
|
||||
|
||||
async def _get_user_context(self, __user__: Optional[dict], __event_call__: Optional[Callable] = None, __request__: Optional[Request] = None) -> dict:
|
||||
user_data = __user__ if isinstance(__user__, dict) else {}
|
||||
user_language = user_data.get("language", "en-US")
|
||||
if __event_call__:
|
||||
try:
|
||||
js = "try { return (document.documentElement.lang || localStorage.getItem('locale') || navigator.language || 'en-US'); } catch (e) { return 'en-US'; }"
|
||||
frontend_lang = await asyncio.wait_for(__event_call__({"type": "execute", "data": {"code": js}}), timeout=2.0)
|
||||
if frontend_lang: user_language = frontend_lang
|
||||
except: pass
|
||||
return {"user_language": user_language}
|
||||
|
||||
async def {{METHOD_NAME}}(self, body: dict, __user__: Optional[dict] = None, __event_emitter__=None, __event_call__=None, __request__: Optional[Request] = None) -> dict:
|
||||
if self.valves.show_status and __event_emitter__:
|
||||
user_ctx = await self._get_user_context(__user__, __event_call__, __request__)
|
||||
msg = self._get_translation(user_ctx["user_language"], "status_starting")
|
||||
await __event_emitter__({"type": "status", "data": {"description": msg, "done": False}})
|
||||
|
||||
# Implement core logic here
|
||||
|
||||
if self.valves.show_status and __event_emitter__:
|
||||
await __event_emitter__({"type": "status", "data": {"description": "Done", "done": True}})
|
||||
return body
|
||||
66
.github/skills/plugin-scaffolder/scripts/scaffold.py
vendored
Normal file
66
.github/skills/plugin-scaffolder/scripts/scaffold.py
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
def scaffold(p_type, p_name, title, desc):
|
||||
target_dir = f"plugins/{p_type}/{p_name}"
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
|
||||
class_name = (
|
||||
"Action"
|
||||
if p_type == "actions"
|
||||
else (
|
||||
"Filter"
|
||||
if p_type == "filters"
|
||||
else "Tools" if p_type == "tools" else "Pipe"
|
||||
)
|
||||
)
|
||||
method_name = (
|
||||
"action"
|
||||
if p_type == "actions"
|
||||
else (
|
||||
"outlet"
|
||||
if p_type == "filters"
|
||||
else "execute" if p_type == "tools" else "pipe"
|
||||
)
|
||||
)
|
||||
|
||||
replacements = {
|
||||
"{{TITLE}}": title,
|
||||
"{{DESCRIPTION}}": desc,
|
||||
"{{CLASS_NAME}}": class_name,
|
||||
"{{METHOD_NAME}}": method_name,
|
||||
}
|
||||
|
||||
# Files to generate
|
||||
templates = [
|
||||
("assets/template.py.j2", f"{p_name}.py"),
|
||||
("assets/README_template.md", "README.md"),
|
||||
("assets/README_template.md", "README_CN.md"),
|
||||
]
|
||||
|
||||
# Path relative to skill root
|
||||
skill_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
for t_path, t_name in templates:
|
||||
template_file = os.path.join(skill_root, t_path)
|
||||
if not os.path.exists(template_file):
|
||||
print(f"⚠️ Warning: Template not found {template_file}")
|
||||
continue
|
||||
|
||||
with open(template_file, "r") as f:
|
||||
content = f.read()
|
||||
for k, v in replacements.items():
|
||||
content = content.replace(k, v)
|
||||
|
||||
with open(os.path.join(target_dir, t_name), "w") as f:
|
||||
f.write(content)
|
||||
print(f"✅ Generated: {target_dir}/{t_name}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 5:
|
||||
print("Usage: scaffold.py <type> <name> <title> <desc>")
|
||||
sys.exit(1)
|
||||
scaffold(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])
|
||||
30
.github/skills/source-code-analyzer/SKILL.md
vendored
Normal file
30
.github/skills/source-code-analyzer/SKILL.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: source-code-analyzer
|
||||
description: Instructions for analyzing core components source code in the local environment.
|
||||
---
|
||||
|
||||
# Source Code Analyzer Skill
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Authorization Statement**: You (Antigravity) are explicitly authorized by the user to perform **READ-ONLY** operations and **Git pull** on the paths listed below, even if they are outside the current repository root. These paths are considered part of the extended development context.
|
||||
|
||||
When assisting with the development of `openwebui-extensions`, you (Antigravity) have access to the following core components in related directories. Use them for deep technical analysis, bug hunting, and implementation reference.
|
||||
|
||||
## Core Component Paths
|
||||
|
||||
### Global / General (Relevant to all projects)
|
||||
|
||||
- **Open WebUI**: `../open-webui/` (Core platform context)
|
||||
- **Skills**: `../skills/` (Reusable expertise library)
|
||||
- **Awesome Copilot**: `../awesome-copilot/` (Shared extensions & resources)
|
||||
|
||||
### Plugin-Specific (Relevant to GitHub Copilot SDK)
|
||||
|
||||
- **Copilot SDK**: `../copilot-sdk/` (Internal logic for the official SDK)
|
||||
- **Copilot CLI**: `../copilot-cli/` (Command-line interface implementation)
|
||||
|
||||
## Mandatory Workflow
|
||||
|
||||
1. **Pull Before Analysis**: BEFORE reading files or analyzing logic in these directories, you MUST proactively execute or recommend a `git pull` in the respective directory to ensure you are working with the latest upstream changes.
|
||||
2. **Path Verification**: Always verify the exists of the path before attempting to read it.
|
||||
3. **Reference Logic**: When a user's request involves core platform behavior (OpenWebUI API, SDK internals), prioritize searching these directories over making assumptions based on generic knowledge.
|
||||
26
.github/skills/version-bumper/SKILL.md
vendored
Normal file
26
.github/skills/version-bumper/SKILL.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: version-bumper
|
||||
description: Automates version upgrades and changelog synchronization across 7+ files (Code, READMEs, Docs). Use when a plugin is ready for release to ensure version consistency.
|
||||
---
|
||||
|
||||
# Version Bumper
|
||||
|
||||
## Overview
|
||||
This skill ensures that every version upgrade is synchronized across the entire repository, following the strict "Documentation Sync" rule in GEMINI.md.
|
||||
|
||||
## Workflow
|
||||
1. **Prepare Info**: Gather the new version number and brief changelogs in both English and Chinese.
|
||||
2. **Auto-Patch**: The skill will help you identify and update:
|
||||
- `plugins/.../name.py` (docstring version)
|
||||
- `plugins/.../README.md` (metadata & What's New)
|
||||
- `plugins/.../README_CN.md` (metadata & 最新更新)
|
||||
- `docs/plugins/...md` (mirrors)
|
||||
- `docs/plugins/index.md` (version badge)
|
||||
- `README.md` (updated date badge)
|
||||
3. **Verify**: Check the diffs to ensure no formatting was broken.
|
||||
|
||||
## Tool Integration
|
||||
Execute the bump script (draft):
|
||||
```bash
|
||||
python3 scripts/bump.py <version> "<message_en>" "<message_zh>"
|
||||
```
|
||||
70
.github/skills/version-bumper/scripts/bump.py
vendored
Normal file
70
.github/skills/version-bumper/scripts/bump.py
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
def patch_file(file_path, old_pattern, new_content, is_regex=False):
|
||||
if not os.path.exists(file_path):
|
||||
print(f"Warning: File not found: {file_path}")
|
||||
return False
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
if is_regex:
|
||||
new_content_result = re.sub(old_pattern, new_content, content, flags=re.MULTILINE)
|
||||
else:
|
||||
new_content_result = content.replace(old_pattern, new_content)
|
||||
|
||||
if new_content_result != content:
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content_result)
|
||||
print(f"✅ Patched: {file_path}")
|
||||
return True
|
||||
else:
|
||||
print(f"ℹ️ No change needed: {file_path}")
|
||||
return False
|
||||
|
||||
def bump_version(plugin_type, plugin_name, new_version, msg_en, msg_zh):
|
||||
print(f"🚀 Bumping {plugin_name} ({plugin_type}) to {new_version}...")
|
||||
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
today_badge = today.replace("-", "--")
|
||||
|
||||
# 1. Patch Plugin Python File
|
||||
py_file = f"plugins/{plugin_type}/{plugin_name}/{plugin_name}.py"
|
||||
patch_file(py_file, r"version: \d+\.\d+\.\d+", f"version: {new_version}", is_regex=True)
|
||||
|
||||
# 2. Patch Plugin READMEs
|
||||
readme_en = f"plugins/{plugin_type}/{plugin_name}/README.md"
|
||||
readme_zh = f"plugins/{plugin_type}/{plugin_name}/README_CN.md"
|
||||
|
||||
# Update version in metadata
|
||||
patch_file(readme_en, r"\*\*Version:\*\* \d+\.\d+\.\d+", f"**Version:** {new_version}", is_regex=True)
|
||||
patch_file(readme_zh, r"\*\*版本:\*\* \d+\.\d+\.\d+", f"**版本:** {new_version}", is_regex=True)
|
||||
|
||||
# Update What's New (Assuming standard headers)
|
||||
patch_file(readme_en, r"## 🔥 What's New in v.*?\n", f"## 🔥 What's New in v{new_version}\n\n* {msg_en}\n", is_regex=True)
|
||||
patch_file(readme_zh, r"## 🔥 最新更新 v.*?\n", f"## 🔥 最新更新 v{new_version}\n\n* {msg_zh}\n", is_regex=True)
|
||||
|
||||
# 3. Patch Docs Mirrors
|
||||
doc_en = f"docs/plugins/{plugin_type}/{plugin_name}.md"
|
||||
doc_zh = f"docs/plugins/{plugin_type}/{plugin_name}.zh.md"
|
||||
patch_file(doc_en, r"\*\*Version:\*\* \d+\.\d+\.\d+", f"**Version:** {new_version}", is_regex=True)
|
||||
patch_file(doc_zh, r"\*\*版本:\*\* \d+\.\d+\.\d+", f"**版本:** {new_version}", is_regex=True)
|
||||
|
||||
# 4. Patch Root READMEs (Updated Date Badge)
|
||||
patch_file("README.md", r"badge/202\d--\d\d--\d\d-gray", f"badge/{today_badge}-gray", is_regex=True)
|
||||
patch_file("README_CN.md", r"badge/202\d--\d\d--\d\d-gray", f"badge/{today_badge}-gray", is_regex=True)
|
||||
|
||||
print("\n✨ All synchronization tasks completed.")
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 6:
|
||||
print("Usage: bump.py <type> <name> <version> <msg_en> <msg_zh>")
|
||||
print("Example: bump.py filters markdown_normalizer 1.2.8 'Fix bug' '修复错误'")
|
||||
sys.exit(1)
|
||||
|
||||
bump_version(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5])
|
||||
7
.markdownlint.json
Normal file
7
.markdownlint.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"default": true,
|
||||
"MD013": false,
|
||||
"MD033": false,
|
||||
"MD030": false,
|
||||
"MD051": false
|
||||
}
|
||||
@@ -67,6 +67,8 @@ When the user invokes antigravity mode (high-speed iteration), enforce these saf
|
||||
| Small reversible edits | One logical change per file per operation |
|
||||
| Timeout guards | `asyncio.wait_for(..., timeout=2.0)` on all `__event_call__` JS executions |
|
||||
| Path sandbox | Verify every workspace path stays inside the repo root before read/write |
|
||||
| Source Sensing | Use `source-code-analyzer` skill for `../open-webui`, `../copilot-sdk` etc. `git pull` before analysis. |
|
||||
| External Auth | **AUTHORIZED** to read (RO) from specified external paths (open-webui, copilot-sdk, etc.) for analysis. |
|
||||
| Fallback chains | API upload → DB + local copy; never a single point of failure |
|
||||
| Progressive status | `status(done=False)` at start, `status(done=True)` on end, `notification(error)` on failure |
|
||||
| Validate before emit | Check `emitter is not None` before every `await emitter(...)` |
|
||||
|
||||
@@ -35,7 +35,7 @@ A collection of enhancements, plugins, and prompts for [OpenWebUI](https://githu
|
||||
|
||||
| Status | Plugin | Version | Downloads | Views | 📅 Updated |
|
||||
| :---: | :--- | :---: | :---: | :---: | :---: |
|
||||
| 🆕 | [GitHub Copilot SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) |  |  |  |  |
|
||||
| 🆕 | [GitHub Copilot SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) |  |  |  |  |
|
||||
|
||||
### 📈 Total Downloads Trend
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
|
||||
|
||||
| 状态 | 插件 | 版本 | 下载 | 浏览 | 📅 更新 |
|
||||
| :---: | :--- | :---: | :---: | :---: | :---: |
|
||||
| 🆕 | [GitHub Copilot SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) |  |  |  |  |
|
||||
| 🆕 | [GitHub Copilot SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) |  |  |  |  |
|
||||
|
||||
### 📈 总下载量累计趋势
|
||||
|
||||
|
||||
154
docs/development/copilot-engineering-plan.md
Normal file
154
docs/development/copilot-engineering-plan.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# Copilot Engineering Configuration Plan
|
||||
|
||||
> This document defines production-grade engineering configuration for plugin development with GitHub Copilot, Gemini CLI, and antigravity development mode.
|
||||
|
||||
---
|
||||
|
||||
## 1. Goals
|
||||
|
||||
- Standardize plugin engineering workflow for AI-assisted development.
|
||||
- Support dual assistant stack:
|
||||
- GitHub Copilot (primary in-editor agent)
|
||||
- Gemini CLI (secondary external execution/research lane)
|
||||
- Introduce antigravity development mode for safer, reversible, high-velocity iteration.
|
||||
|
||||
---
|
||||
|
||||
## 2. Scope
|
||||
|
||||
This plan applies to:
|
||||
|
||||
- Plugin development (`actions`, `filters`, `pipes`, `pipelines`, `tools`)
|
||||
- Documentation synchronization
|
||||
- File generation/delivery workflow in OpenWebUI
|
||||
- Streaming/tool-call rendering compatibility
|
||||
|
||||
---
|
||||
|
||||
## 3. Engineering Configuration (Copilot-centered)
|
||||
|
||||
### 3.1 Source of truth
|
||||
|
||||
- Primary standard file: `.github/copilot-instructions.md`
|
||||
- Agent workflow file: `.agent/workflows/plugin-development.md`
|
||||
- Runtime guidance docs:
|
||||
- `docs/development/plugin-guide.md`
|
||||
- `docs/development/plugin-guide.zh.md`
|
||||
|
||||
### 3.2 Required development contract
|
||||
|
||||
- Single-file i18n plugin source.
|
||||
- Bilingual README (`README.md` + `README_CN.md`).
|
||||
- Safe context extraction (`_get_user_context`, `_get_chat_context`).
|
||||
- Structured event handling (`status`, `notification`, `execute`).
|
||||
- No silent failures; logging + user-visible status.
|
||||
|
||||
### 3.3 Tool definition contract (Copilot SDK)
|
||||
|
||||
- Define tool params explicitly with `pydantic.BaseModel`.
|
||||
- Use `params_type` in tool registration.
|
||||
- Preserve defaults by avoiding forced null overrides.
|
||||
- Keep tool names normalized and deterministic.
|
||||
|
||||
---
|
||||
|
||||
## 4. Gemini CLI Compatibility Profile
|
||||
|
||||
Gemini CLI is treated as a parallel capability channel, not a replacement.
|
||||
|
||||
### 4.1 Usage boundary
|
||||
|
||||
- Use for:
|
||||
- rapid drafts
|
||||
- secondary reasoning
|
||||
- cross-checking migration plans
|
||||
- Do not bypass repository conventions or plugin contracts.
|
||||
|
||||
### 4.2 Output normalization
|
||||
|
||||
All Gemini CLI outputs must be normalized before merge:
|
||||
|
||||
- Match repository style and naming rules.
|
||||
- Preserve OpenWebUI plugin signatures and context methods.
|
||||
- Convert speculative outputs into explicit, testable implementation points.
|
||||
|
||||
### 4.3 Conflict policy
|
||||
|
||||
When Copilot and Gemini suggestions differ:
|
||||
|
||||
1. Prefer repository standard compliance.
|
||||
2. Prefer safer fallback behavior.
|
||||
3. Prefer lower integration risk.
|
||||
|
||||
---
|
||||
|
||||
## 5. Antigravity Development Mode
|
||||
|
||||
Antigravity mode means high-speed delivery with strict reversibility.
|
||||
|
||||
### 5.1 Core principles
|
||||
|
||||
- Small, isolated edits
|
||||
- Deterministic interfaces
|
||||
- Multi-level fallback paths
|
||||
- Roll-forward and rollback both feasible
|
||||
|
||||
### 5.2 Required patterns
|
||||
|
||||
- Timeout guards on frontend execution calls.
|
||||
- Path sandbox validation for all workspace file operations.
|
||||
- Dual-channel upload fallback (API first, local/DB fallback).
|
||||
- Progressive status reporting for long-running tasks.
|
||||
|
||||
---
|
||||
|
||||
## 6. File Creation & Delivery Standard
|
||||
|
||||
### 6.1 Create files in controlled workspace
|
||||
|
||||
- Write artifacts in current workspace scope.
|
||||
- Never use paths outside workspace boundary for deliverables.
|
||||
|
||||
### 6.2 Publish protocol
|
||||
|
||||
Use 3-step delivery:
|
||||
|
||||
1. local write
|
||||
2. publish from workspace
|
||||
3. present `/api/v1/files/{id}/content` link
|
||||
|
||||
### 6.3 Metadata policy
|
||||
|
||||
- Set `skip_rag=true` for generated downloadable artifacts where applicable.
|
||||
- Keep filename generation deterministic (`chat_title -> markdown_title -> user+date`).
|
||||
|
||||
---
|
||||
|
||||
## 7. Plugin Development Norms for Multi-Agent Stack
|
||||
|
||||
- Compatible with GitHub Copilot and Gemini CLI under same coding contract.
|
||||
- Keep streaming compatible with OpenWebUI native blocks (`<think>`, `<details type="tool_calls">`).
|
||||
- Escape tool card attributes safely (`"`) for parser stability.
|
||||
- Preserve async non-blocking behavior.
|
||||
|
||||
---
|
||||
|
||||
## 8. Documentation Sync Rule
|
||||
|
||||
Any meaningful plugin engineering change must sync:
|
||||
|
||||
1. plugin code
|
||||
2. plugin bilingual README
|
||||
3. docs plugin detail pages (EN/ZH)
|
||||
4. docs plugin indexes (EN/ZH)
|
||||
5. root README update badge/date when release is prepared
|
||||
|
||||
---
|
||||
|
||||
## 9. Acceptance Checklist
|
||||
|
||||
- Copilot config and workflow references are valid.
|
||||
- Gemini CLI outputs can be merged without violating conventions.
|
||||
- Antigravity safety mechanisms are present.
|
||||
- File creation and publication flow is reproducible.
|
||||
- Streaming/tool-card output format remains OpenWebUI-compatible.
|
||||
149
docs/development/copilot-engineering-plan.zh.md
Normal file
149
docs/development/copilot-engineering-plan.zh.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Copilot 工程化配置设计
|
||||
|
||||
> 本文档定义面向插件开发的工程化配置方案:支持 GitHub Copilot、兼容 Gemini CLI,并引入“反重力开发”模式。
|
||||
|
||||
---
|
||||
|
||||
## 1. 目标
|
||||
|
||||
- 建立统一、可落地的 AI 协同开发标准。
|
||||
- 支持双通道助手体系:
|
||||
- GitHub Copilot(编辑器内主通道)
|
||||
- Gemini CLI(外部补充通道)
|
||||
- 在高迭代速度下保障可回滚、可审计、可发布。
|
||||
|
||||
---
|
||||
|
||||
## 2. 适用范围
|
||||
|
||||
适用于以下类型开发:
|
||||
|
||||
- 插件代码(`actions` / `filters` / `pipes` / `pipelines` / `tools`)
|
||||
- 文档同步与发布准备
|
||||
- OpenWebUI 文件创建与交付流程
|
||||
- 流式输出与工具卡片兼容性
|
||||
|
||||
---
|
||||
|
||||
## 3. Copilot 工程化主配置
|
||||
|
||||
### 3.1 规范来源
|
||||
|
||||
- 主规范:`.github/copilot-instructions.md`
|
||||
- 工作流:`.agent/workflows/plugin-development.md`
|
||||
- 运行时开发指南:
|
||||
- `docs/development/plugin-guide.md`
|
||||
- `docs/development/plugin-guide.zh.md`
|
||||
|
||||
### 3.2 强制开发契约
|
||||
|
||||
- 单文件 i18n 插件源码。
|
||||
- README 双语(`README.md` + `README_CN.md`)。
|
||||
- 上下文统一入口(`_get_user_context`、`_get_chat_context`)。
|
||||
- 事件标准化(`status`、`notification`、`execute`)。
|
||||
- 禁止静默失败(用户可见状态 + 后端日志)。
|
||||
|
||||
### 3.3 Copilot SDK 工具契约
|
||||
|
||||
- 参数必须 `pydantic.BaseModel` 显式定义。
|
||||
- 工具注册必须声明 `params_type`。
|
||||
- 保留默认值语义,避免把未传参数强制覆盖为 `null`。
|
||||
- 工具名需可预测、可规范化。
|
||||
|
||||
---
|
||||
|
||||
## 4. Gemini CLI 兼容配置
|
||||
|
||||
Gemini CLI 作为补充通道,而不是替代主规范。
|
||||
|
||||
### 4.1 使用边界
|
||||
|
||||
- 适合:草案生成、方案对照、迁移校验。
|
||||
- 不得绕过仓库规范与插件契约。
|
||||
|
||||
### 4.2 输出归一化
|
||||
|
||||
Gemini CLI 输出合入前必须完成:
|
||||
|
||||
- 命名与结构对齐仓库约束。
|
||||
- 保留 OpenWebUI 插件标准签名与上下文方法。
|
||||
- 将“建议性文字”转换为可执行、可验证实现点。
|
||||
|
||||
### 4.3 冲突决策
|
||||
|
||||
Copilot 与 Gemini 建议冲突时按优先级处理:
|
||||
|
||||
1. 规范一致性优先
|
||||
2. 安全回退优先
|
||||
3. 低集成风险优先
|
||||
|
||||
---
|
||||
|
||||
## 5. 反重力开发(Antigravity)模式
|
||||
|
||||
反重力开发 = 高速迭代 + 强回退能力。
|
||||
|
||||
### 5.1 核心原则
|
||||
|
||||
- 小步、隔离、可逆变更
|
||||
- 接口稳定、行为可预测
|
||||
- 多级回退链路
|
||||
- 支持前滚与回滚
|
||||
|
||||
### 5.2 强制模式
|
||||
|
||||
- 前端执行调用必须设置超时保护。
|
||||
- 工作区文件操作必须做路径沙箱校验。
|
||||
- 上传链路采用 API 优先 + 本地/DB 回退。
|
||||
- 长任务必须分阶段状态反馈。
|
||||
|
||||
---
|
||||
|
||||
## 6. 文件创建与交付标准
|
||||
|
||||
### 6.1 创建范围
|
||||
|
||||
- 交付文件仅在受控 workspace 内创建。
|
||||
- 禁止将交付产物写到 workspace 边界之外。
|
||||
|
||||
### 6.2 三步交付协议
|
||||
|
||||
1. 本地写入
|
||||
2. 从 workspace 发布
|
||||
3. 返回并展示 `/api/v1/files/{id}/content`
|
||||
|
||||
### 6.3 元数据策略
|
||||
|
||||
- 需要绕过检索时为产物标记 `skip_rag=true`。
|
||||
- 文件名采用确定性策略:`chat_title -> markdown_title -> user+date`。
|
||||
|
||||
---
|
||||
|
||||
## 7. 多助手并行下的插件开发规范
|
||||
|
||||
- 在统一契约下同时兼容 GitHub Copilot 与 Gemini CLI。
|
||||
- 流式输出保持 OpenWebUI 原生兼容(`<think>`、`<details type="tool_calls">`)。
|
||||
- 工具卡片属性严格转义(`"`)保证解析稳定。
|
||||
- 全链路保持异步非阻塞。
|
||||
|
||||
---
|
||||
|
||||
## 8. 文档同步规则
|
||||
|
||||
插件工程化变更发生时,至少同步:
|
||||
|
||||
1. 插件代码
|
||||
2. 插件双语 README
|
||||
3. docs 详情页(EN/ZH)
|
||||
4. docs 索引页(EN/ZH)
|
||||
5. 发布准备阶段同步根 README 日期徽章
|
||||
|
||||
---
|
||||
|
||||
## 9. 验收清单
|
||||
|
||||
- Copilot 配置与工作流引用有效。
|
||||
- Gemini CLI 输出可无缝合入且不破坏规范。
|
||||
- 反重力安全机制完整。
|
||||
- 文件创建/发布流程可复现。
|
||||
- 流式与工具卡片格式保持 OpenWebUI 兼容。
|
||||
126
docs/development/copilot-sdk-tool-filtering.md
Normal file
126
docs/development/copilot-sdk-tool-filtering.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# GitHub Copilot SDK Tool Filtering Logic Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The tool filtering logic ensures that changes made in the **OpenWebUI admin panel take effect on the very next chat message** — no restart or cache flush required. The design balances three goals: administrator control, user autonomy, and built-in feature availability.
|
||||
|
||||
## Priority Hierarchy
|
||||
|
||||
Filtering is applied top-to-bottom. A higher layer can fully block a lower one:
|
||||
|
||||
| Priority | Layer | Controls |
|
||||
|---|---|---|
|
||||
| 1 (Highest) | **Plugin Valve toggles** | `ENABLE_OPENWEBUI_TOOLS`, `ENABLE_MCP_SERVER`, `ENABLE_OPENAPI_SERVER` — category master switches |
|
||||
| 2 | **Admin backend server toggle** | Per-server `config.enable` in OpenWebUI Connections panel — blocks specific servers |
|
||||
| 3 (Lowest) | **User Chat menu selection** | `tool_ids` from the chat UI — selects which enabled items to use |
|
||||
|
||||
---
|
||||
|
||||
## Core Decision Logic (Flowchart)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[New message arrives] --> V{Plugin Valve enabled\nfor this category?}
|
||||
V -- No --> VX[Drop all tools in this category]
|
||||
V -- Yes --> B{Admin backend:\nconfig.enable = True?}
|
||||
B -- No --> C[Skip this server]
|
||||
B -- Yes --> F{Built-in or Custom/Server tool?}
|
||||
|
||||
F -- Built-in --> G{Any builtin: IDs\nselected in Chat?}
|
||||
G -- Yes --> H[Enable ONLY the mapped categories\nunselected categories set to False]
|
||||
G -- No --> I[Enable default 4 categories:\nweb_search, image_generation,\ncode_interpreter, memory]
|
||||
|
||||
F -- Custom / Server --> J{Any custom IDs\nselected in Chat?}
|
||||
J -- Yes --> K[Load ONLY the selected IDs]
|
||||
J -- No --> L[Load ALL admin-enabled custom tools]
|
||||
|
||||
H & I & K & L --> M[Always inject: publish_file_from_workspace]
|
||||
M --> N[Start / Resume Copilot SDK Session]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scenario Reference Table
|
||||
|
||||
| User selects in Chat | Custom tools loaded | Built-in tools loaded |
|
||||
|---|---|---|
|
||||
| Nothing | All admin-enabled | Default 4 (search, image, code, memory) |
|
||||
| Only `builtin:xxx` | All admin-enabled (unaffected) | Only selected categories |
|
||||
| Only custom/server IDs | Only selected IDs | Default 4 |
|
||||
| Both builtin and custom | Only selected custom IDs | Only selected builtin categories |
|
||||
|
||||
---
|
||||
|
||||
## Technical Implementation Details
|
||||
|
||||
### 1. Real-time Admin Sync (No Caching)
|
||||
|
||||
Every request re-reads `TOOL_SERVER_CONNECTIONS.value` live. There is **no in-memory cache** for server state. As a result:
|
||||
|
||||
- Enable a server in the admin panel → it appears on the **next message**.
|
||||
- Disable a server → it is dropped on the **next message**.
|
||||
|
||||
```python
|
||||
# Read live on every request — no cache
|
||||
if hasattr(TOOL_SERVER_CONNECTIONS, "value"):
|
||||
raw_connections = TOOL_SERVER_CONNECTIONS.value
|
||||
|
||||
for server in connections:
|
||||
is_enabled = config.get("enable", False) # checked per-server, per-request
|
||||
if not is_enabled:
|
||||
continue # skipped immediately — hard block
|
||||
```
|
||||
|
||||
### 2. Built-in Tool Category Mapping
|
||||
|
||||
The plugin maps individual `builtin:func_name` IDs to one of 9 categories understood by `get_builtin_tools`. When the user selects specific builtins, **only those categories are enabled; unselected categories are explicitly set to `False`** (not omitted) to prevent OpenWebUI's default-`True` fallback:
|
||||
|
||||
```python
|
||||
if builtin_selected:
|
||||
# Strict mode: set every category explicitly
|
||||
for cat in all_builtin_categories: # all 9
|
||||
is_enabled = cat in enabled_categories # only selected ones are True
|
||||
builtin_tools_meta[cat] = is_enabled # unselected are explicitly False
|
||||
else:
|
||||
# Default mode: only the 4 core categories
|
||||
default_builtin_categories = [
|
||||
"web_search", "image_generation", "code_interpreter", "memory"
|
||||
]
|
||||
for cat in all_builtin_categories:
|
||||
builtin_tools_meta[cat] = cat in default_builtin_categories
|
||||
features.update(req_features) # merge backend feature flags
|
||||
```
|
||||
|
||||
### 3. Custom Tool "Select-All" Fallback
|
||||
|
||||
The whitelist is activated **only when the user explicitly selects custom/server IDs**. Selecting only `builtin:` IDs does not trigger the custom whitelist, so all admin-enabled servers remain accessible:
|
||||
|
||||
```python
|
||||
# custom_selected contains only non-builtin: IDs
|
||||
if custom_selected:
|
||||
# Whitelist active: keep only what the user picked
|
||||
tool_ids = [tid for tid in available_ids if tid in custom_selected]
|
||||
else:
|
||||
# No custom selection: load everything enabled in backend
|
||||
tool_ids = available_ids
|
||||
```
|
||||
|
||||
The same rule applies to MCP servers in `_parse_mcp_servers`.
|
||||
|
||||
### 4. Admin Backend Strict Validation
|
||||
|
||||
Applied uniformly to both OpenAPI and MCP servers, handling both dict and Pydantic object shapes:
|
||||
|
||||
```python
|
||||
is_enabled = False
|
||||
config = server.get("config", {}) if isinstance(server, dict) else getattr(server, "config", {})
|
||||
is_enabled = config.get("enable", False) if isinstance(config, dict) else getattr(config, "enable", False)
|
||||
|
||||
if not is_enabled:
|
||||
continue # hard skip — no user or valve setting can override this
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **SDK Internal Tools**: `available_tools = None` is passed to the session so SDK-native capabilities (`read_file`, `shell`, etc.) are never accidentally blocked by the custom tool list.
|
||||
- **Persistent Tool**: `publish_file_from_workspace` is always injected after all filtering — it is required for the file delivery workflow regardless of any toggle.
|
||||
206
docs/development/copilot-sdk-tool-filtering.zh.md
Normal file
206
docs/development/copilot-sdk-tool-filtering.zh.md
Normal file
@@ -0,0 +1,206 @@
|
||||
# GitHub Copilot SDK 工具过滤逻辑开发文档
|
||||
|
||||
## 核心需求
|
||||
|
||||
**管理员在后台修改工具服务的启用状态后,用户发送下一条消息时立即生效,无需重启服务或刷新缓存。**
|
||||
|
||||
过滤逻辑同时兼顾两个目标:管理员管控权、用户自主选择权。内置工具则完全独立,仅由模型配置决定。
|
||||
|
||||
---
|
||||
|
||||
## 工具分类说明
|
||||
|
||||
本文档涉及两类完全独立的工具,权限控制机制不同:
|
||||
|
||||
| 工具类型 | 说明 | 权限控制来源 |
|
||||
|---|---|---|
|
||||
| **内置工具(Builtin Tools)** | OpenWebUI 原生能力:时间、知识库、记忆、联网搜索、图像生成、代码解释器等 | 仅由模型配置 `meta.builtinTools` 决定,**与 Chat 前端选择无关** |
|
||||
| **OpenWebUI Tools** | 用户安装的 Python 工具插件 | 插件 Valve + Chat 工具选择(tool_ids) |
|
||||
| **工具服务器(OpenAPI / MCP)** | 外部 OpenAPI Server、MCP Server | 插件 Valve + 管理员 `config.enable` + `function_name_filter_list` + Chat 工具选择(tool_ids) |
|
||||
|
||||
---
|
||||
|
||||
## 内置工具权限控制(模型配置驱动,与前端无关)
|
||||
|
||||
内置工具**完全由模型配置决定**,Chat 界面的工具选择对其没有任何影响。
|
||||
|
||||
### 模型 `meta.builtinTools` 字段
|
||||
|
||||
在模型(自定义模型或基础模型)的 `meta` 字段中有一个可选的 `builtinTools` 对象:
|
||||
|
||||
```json
|
||||
{
|
||||
"meta": {
|
||||
"capabilities": { "builtin_tools": true },
|
||||
"builtinTools": {
|
||||
"time": false,
|
||||
"memory": true,
|
||||
"chats": true,
|
||||
"notes": true,
|
||||
"knowledge": true,
|
||||
"channels": true,
|
||||
"web_search": true,
|
||||
"image_generation": true,
|
||||
"code_interpreter": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**判定规则(源码 `utils/tools.py`):**
|
||||
|
||||
```python
|
||||
def is_builtin_tool_enabled(category: str) -> bool:
|
||||
builtin_tools = model.get("info", {}).get("meta", {}).get("builtinTools", {})
|
||||
return builtin_tools.get(category, True) # 缺省时默认 True
|
||||
```
|
||||
|
||||
- `builtinTools` 字段**不存在** → 所有内置工具类别默认全部开启
|
||||
- `builtinTools` 字段**存在** → 仅值为 `true` 的类别开启,其余关闭
|
||||
|
||||
---
|
||||
|
||||
## OpenWebUI Tools 和工具服务器的优先级层级
|
||||
|
||||
这两类工具的过滤从上到下依次执行,**受 Chat 前端选择影响**:
|
||||
|
||||
| 优先级 | 层级 | 控制范围 |
|
||||
|---|---|---|
|
||||
| 1(最高) | **插件 Valve 开关** | `ENABLE_OPENWEBUI_TOOLS` / `ENABLE_MCP_SERVER` / `ENABLE_OPENAPI_SERVER` — 类别总开关 |
|
||||
| 2 | **管理员后端服务器开关** | OpenWebUI 连接面板中每个服务器的 `config.enable` — 控制具体服务器是否启用 |
|
||||
| 3 | **管理员函数名过滤列表** | 工具服务器 `config.function_name_filter_list` — 限制该服务器对外暴露的函数列表(逗号分隔) |
|
||||
| 4(最低) | **用户 Chat 工具选择** | Chat 界面的 `tool_ids` — 在已启用范围内进一步筛选:未选则全选,有选则仅选中的 |
|
||||
|
||||
### 管理员函数名过滤列表说明
|
||||
|
||||
OpenWebUI 后台的工具服务器连接配置中支持设置 `function_name_filter_list` 字段(逗号分隔的函数名),用于限制该服务器对外暴露的函数。源码逻辑:
|
||||
|
||||
```python
|
||||
# utils/tools.py
|
||||
function_name_filter_list = tool_server_connection.get("config", {}).get("function_name_filter_list", "")
|
||||
if isinstance(function_name_filter_list, str):
|
||||
function_name_filter_list = function_name_filter_list.split(",")
|
||||
|
||||
for spec in specs:
|
||||
function_name = spec["name"]
|
||||
if function_name_filter_list:
|
||||
if not is_string_allowed(function_name, function_name_filter_list):
|
||||
continue # 不在列表中的函数被跳过
|
||||
```
|
||||
|
||||
- 列表**为空** → 该服务器所有函数均可用
|
||||
- 列表**有值** → 只有名称匹配的函数会被暴露给用户
|
||||
|
||||
---
|
||||
|
||||
## 核心判定流程
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[新消息到来] --> BT[内置工具:读模型 meta.builtinTools]
|
||||
BT --> BT2{builtinTools 字段存在?}
|
||||
BT2 -- 否 --> BT3[开启全部内置工具]
|
||||
BT2 -- 是 --> BT4[仅开启值为 true 的类别]
|
||||
|
||||
A --> CT[OpenWebUI Tools / 工具服务器]
|
||||
CT --> V{插件 Valve 开启了该类别?}
|
||||
V -- 否 --> VX[丢弃该类别]
|
||||
V -- 是 --> B{后端 config.enable = True?}
|
||||
B -- 否 --> C[跳过该服务器]
|
||||
B -- 是 --> FL{function_name_filter_list 有值?}
|
||||
FL -- 是 --> FL2[过滤掉不在列表中的函数]
|
||||
FL -- 否 --> J
|
||||
FL2 --> J{Chat tool_ids 有勾选?}
|
||||
J -- 有 --> K[仅加载勾选的 ID]
|
||||
J -- 无 --> L[加载所有后台已启用工具]
|
||||
|
||||
BT3 & BT4 & K & L --> M[始终注入: publish_file_from_workspace]
|
||||
M --> N[启动/恢复 Copilot SDK 会话]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 场景速查表
|
||||
|
||||
### 内置工具(与前端选择无关)
|
||||
|
||||
| 模型配置 | 结果 |
|
||||
|---|---|
|
||||
| `meta.builtinTools` 字段不存在 | 全部内置工具类别开启 |
|
||||
| `meta.builtinTools` 字段存在 | 仅 `true` 的类别开启 |
|
||||
|
||||
### OpenWebUI Tools / 工具服务器(受 Chat 前端选择影响)
|
||||
|
||||
| Chat 工具选择情况 | 加载逻辑 |
|
||||
|---|---|
|
||||
| 什么都没选 | 加载所有 Valve 开启且后台已启用的工具(Python Tools + OpenAPI + MCP) |
|
||||
| 选了部分 tool_ids | 仅加载勾选的 ID(必须同时通过 Valve 和 config.enable 校验) |
|
||||
|
||||
---
|
||||
|
||||
## 代码实现详述
|
||||
|
||||
### 1. 管理员后台变更即时同步
|
||||
|
||||
OpenWebUI 通过 `PersistentConfig` + Redis 保证多 worker 之间的配置同步,插件直接读取 `request.app.state.config.TOOL_SERVER_CONNECTIONS` 即可获取最新值:
|
||||
|
||||
- 后台**启用**一个服务器 → **下一条消息**就出现。
|
||||
- 后台**禁用**一个服务器 → **下一条消息**就消失。
|
||||
|
||||
```python
|
||||
# 直接读取 OpenWebUI 的配置对象,有 Redis 时每次读取都会同步最新值
|
||||
connections = request.app.state.config.TOOL_SERVER_CONNECTIONS # list
|
||||
|
||||
for server in connections:
|
||||
config = server.get("config", {})
|
||||
is_enabled = config.get("enable", False) # 每条服务器、每次请求都检查
|
||||
if not is_enabled:
|
||||
continue # 立即跳过,硬性拦截
|
||||
```
|
||||
|
||||
### 2. 内置工具直接透传给 OpenWebUI 处理
|
||||
|
||||
插件调用 OpenWebUI 的 `get_builtin_tools(request, extra_params, model)` 时,将 `model` 原样传入即可。OpenWebUI 内部会自动读取 `model.info.meta.builtinTools` 来决定哪些内置工具生效:
|
||||
|
||||
```python
|
||||
# OpenWebUI 源码 utils/tools.py 中的判定逻辑(开发参考,非插件代码)
|
||||
def is_builtin_tool_enabled(category: str) -> bool:
|
||||
builtin_tools = model.get("info", {}).get("meta", {}).get("builtinTools", {})
|
||||
return builtin_tools.get(category, True) # 缺省值 True:未配置时全部开启
|
||||
```
|
||||
|
||||
插件无需自行维护内置工具分类映射,也不需要向 `builtinTools` 注入任何值。
|
||||
|
||||
### 3. 自定义工具"默认全选"(白名单仅在显式勾选时激活)
|
||||
|
||||
白名单**只有在用户明确勾选了 tool_ids 时才启用**。未勾选任何工具时,加载所有后台已启用工具:
|
||||
|
||||
```python
|
||||
# tool_ids 来自 Chat 请求体
|
||||
if tool_ids:
|
||||
# 白名单模式:严格只保留勾选项
|
||||
available_ids = [tid for tid in available_ids if tid in tool_ids]
|
||||
else:
|
||||
# 无勾选:加载所有后台已启用工具(免配置可用)
|
||||
pass # available_ids 保持不变
|
||||
```
|
||||
|
||||
MCP 服务器的 `_parse_mcp_servers` 遵循同样规则。
|
||||
|
||||
### 4. 后端状态硬校验(OpenAPI 和 MCP 统一处理)
|
||||
|
||||
```python
|
||||
is_enabled = False
|
||||
config = server.get("config", {}) if isinstance(server, dict) else getattr(server, "config", {})
|
||||
is_enabled = config.get("enable", False) if isinstance(config, dict) else getattr(config, "enable", False)
|
||||
|
||||
if not is_enabled:
|
||||
continue # 硬性跳过,任何用户或 Valve 设置都无法绕过
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
- **SDK 内部工具**:向会话传 `available_tools = None`,保证 `read_file`、`shell` 等 SDK 原生能力不被自定义工具列表意外屏蔽。
|
||||
- **始终注入的工具**:`publish_file_from_workspace` 在所有过滤完成后硬性追加,是文件交付工作流的必要依赖,不受任何开关影响。
|
||||
@@ -24,6 +24,14 @@ Learn how to develop plugins and contribute to OpenWebUI Extensions.
|
||||
|
||||
[:octicons-arrow-right-24: Read the Guide](documentation-guide.md)
|
||||
|
||||
- :material-robot:{ .lg .middle } **Copilot Engineering Plan**
|
||||
|
||||
---
|
||||
|
||||
Engineering configuration for GitHub Copilot + Gemini CLI + antigravity development mode.
|
||||
|
||||
[:octicons-arrow-right-24: Read the Plan](copilot-engineering-plan.md)
|
||||
|
||||
- :material-github:{ .lg .middle } **Contributing**
|
||||
|
||||
---
|
||||
@@ -164,5 +172,6 @@ user_language = __user__.get("language", "en-US")
|
||||
## Resources
|
||||
|
||||
- [Full Development Guide](plugin-guide.md)
|
||||
- [Copilot Engineering Plan](copilot-engineering-plan.md)
|
||||
- [Plugin Examples](https://github.com/Fu-Jie/openwebui-extensions/tree/main/plugins)
|
||||
- [OpenWebUI Documentation](https://docs.openwebui.com/)
|
||||
|
||||
@@ -24,6 +24,14 @@
|
||||
|
||||
[:octicons-arrow-right-24: 阅读指南](documentation-guide.md)
|
||||
|
||||
- :material-robot:{ .lg .middle } **Copilot 工程化配置**
|
||||
|
||||
---
|
||||
|
||||
面向 GitHub Copilot + Gemini CLI + 反重力开发模式的工程化设计文档。
|
||||
|
||||
[:octicons-arrow-right-24: 阅读文档](copilot-engineering-plan.md)
|
||||
|
||||
- :material-github:{ .lg .middle } **贡献指南**
|
||||
|
||||
---
|
||||
@@ -164,5 +172,6 @@ user_language = __user__.get("language", "en-US")
|
||||
## 资源
|
||||
|
||||
- [完整开发指南](plugin-guide.md)
|
||||
- [Copilot 工程化配置](copilot-engineering-plan.md)
|
||||
- [插件示例](https://github.com/Fu-Jie/openwebui-extensions/tree/main/plugins)
|
||||
- [OpenWebUI 文档](https://docs.openwebui.com/)
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
## 📚 Table of Contents
|
||||
|
||||
1. [Quick Start](#1-quick-start)
|
||||
2. [Core Concepts & SDK Details](#2-core-concepts-sdk-details)
|
||||
3. [Deep Dive into Plugin Types](#3-deep-dive-into-plugin-types)
|
||||
4. [Advanced Development Patterns](#4-advanced-development-patterns)
|
||||
5. [Best Practices & Design Principles](#5-best-practices-design-principles)
|
||||
6. [Troubleshooting](#6-troubleshooting)
|
||||
2. [Project Structure & Naming](#2-project-structure--naming)
|
||||
3. [Core Concepts & SDK Details](#3-core-concepts--sdk-details)
|
||||
4. [Deep Dive into Plugin Types](#4-deep-dive-into-plugin-types)
|
||||
5. [Advanced Development Patterns](#5-advanced-development-patterns)
|
||||
6. [Best Practices & Design Principles](#6-best-practices--design-principles)
|
||||
7. [Workflow & Process](#7-workflow--process)
|
||||
8. [Troubleshooting](#8-troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
@@ -64,9 +66,39 @@ class Action:
|
||||
|
||||
---
|
||||
|
||||
## 2. Core Concepts & SDK Details
|
||||
## 2. Project Structure & Naming
|
||||
|
||||
### 2.1 ⚠️ Important: Sync vs Async
|
||||
### 2.1 Language & Code Requirements
|
||||
|
||||
- **Single Code File**: `plugins/{type}/{name}/{name}.py`. Never create separate source files for different languages.
|
||||
- **Built-in i18n**: Must dynamically switch UI, prompts, and logs based on user language.
|
||||
- **Documentation**: Must include both `README.md` (English) and `README_CN.md` (Chinese).
|
||||
|
||||
### 2.2 Docstring Standard
|
||||
|
||||
Each plugin file must start with a standardized docstring:
|
||||
|
||||
```python
|
||||
"""
|
||||
title: Plugin Name
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie/openwebui-extensions
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 0.1.0
|
||||
icon_url: data:image/svg+xml;base64,<base64-encoded-svg>
|
||||
requirements: dependency1==1.0.0, dependency2>=2.0.0
|
||||
description: Brief description of plugin functionality.
|
||||
"""
|
||||
```
|
||||
|
||||
- **icon_url**: Required for Action plugins. Must be Base64 encoded SVG from [Lucide Icons](https://lucide.dev/icons/).
|
||||
- **requirements**: Only list dependencies not installed in the OpenWebUI environment.
|
||||
|
||||
---
|
||||
|
||||
## 3. Core Concepts & SDK Details
|
||||
|
||||
### 3.1 ⚠️ Important: Sync vs Async
|
||||
|
||||
OpenWebUI plugins run within an `asyncio` event loop.
|
||||
|
||||
@@ -75,7 +107,7 @@ OpenWebUI plugins run within an `asyncio` event loop.
|
||||
- **Pitfall**: Calling synchronous methods directly (e.g., `time.sleep`, `requests.get`) will freeze the entire server
|
||||
- **Solution**: Wrap synchronous calls using `await asyncio.to_thread(sync_func, ...)`
|
||||
|
||||
### 2.2 Core Parameters
|
||||
### 3.2 Core Parameters
|
||||
|
||||
All plugin methods (`inlet`, `outlet`, `pipe`, `action`) support injecting the following special parameters:
|
||||
|
||||
@@ -88,30 +120,43 @@ All plugin methods (`inlet`, `outlet`, `pipe`, `action`) support injecting the f
|
||||
| `__event_emitter__` | `func` | **One-way Notification**. Used to send Toast notifications or status bar updates |
|
||||
| `__event_call__` | `func` | **Two-way Interaction**. Used to execute JS code, show confirmation dialogs, or input boxes |
|
||||
|
||||
### 2.3 Configuration System (Valves)
|
||||
### 3.3 Configuration System (Valves)
|
||||
|
||||
- **`Valves`**: Global admin configuration
|
||||
- **`UserValves`**: User-level configuration (higher priority, overrides global)
|
||||
Use Pydantic BaseModel to define configurable parameters. All Valves fields must use **UPPER_SNAKE_CASE**.
|
||||
|
||||
```python
|
||||
class Filter:
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class Action:
|
||||
class Valves(BaseModel):
|
||||
API_KEY: str = Field(default="", description="Global API Key")
|
||||
|
||||
class UserValves(BaseModel):
|
||||
API_KEY: str = Field(default="", description="User Private API Key")
|
||||
|
||||
def inlet(self, body, __user__):
|
||||
# Prioritize user's Key
|
||||
user_valves = __user__.get("valves", self.UserValves())
|
||||
api_key = user_valves.API_KEY or self.valves.API_KEY
|
||||
SHOW_STATUS: bool = Field(default=True, description="Whether to show operation status updates.")
|
||||
# ...
|
||||
```
|
||||
|
||||
### 3.4 Context Access
|
||||
|
||||
All plugins **must** use `_get_user_context` and `_get_chat_context` methods to safely extract information, rather than accessing `__user__` or `body` directly.
|
||||
|
||||
### 3.5 Event Emission & Logging
|
||||
|
||||
- **Event Emission**: Implement helper methods `_emit_status` and `_emit_notification`.
|
||||
- **Frontend Console Debugging**: Highly recommended for real-time data flow viewing. Use `_emit_debug_log` to print structured debug logs in the browser console.
|
||||
- **Server-side Logging**: Use Python's standard `logging` module. Do not use `print()`.
|
||||
|
||||
### 3.6 Database & File Storage
|
||||
|
||||
- **Database**: Re-use Open WebUI's internal database connection (`open_webui.internal.db`).
|
||||
- **File Storage**: Implement multi-level fallback mechanisms (DB -> S3 -> Local -> URL -> API) to ensure compatibility across all storage configurations.
|
||||
|
||||
### 3.7 Internationalization (i18n)
|
||||
|
||||
Define a `TRANSLATIONS` dictionary and use a robust language detection mechanism (Multi-level Fallback: JS localStorage -> HTTP Accept-Language -> User Profile -> en-US).
|
||||
|
||||
---
|
||||
|
||||
## 3. Deep Dive into Plugin Types
|
||||
## 4. Deep Dive into Plugin Types
|
||||
|
||||
### 3.1 Action
|
||||
### 4.1 Action
|
||||
|
||||
**Role**: Adds buttons below messages that trigger upon user click.
|
||||
|
||||
@@ -136,7 +181,7 @@ async def action(self, body, __event_call__):
|
||||
await __event_call__({"type": "execute", "data": {"code": js}})
|
||||
```
|
||||
|
||||
### 3.2 Filter
|
||||
### 4.2 Filter
|
||||
|
||||
**Role**: Middleware that intercepts and modifies requests/responses.
|
||||
|
||||
@@ -157,7 +202,7 @@ async def inlet(self, body, __metadata__):
|
||||
return body
|
||||
```
|
||||
|
||||
### 3.3 Pipe
|
||||
### 4.3 Pipe
|
||||
|
||||
**Role**: Custom Model/Agent.
|
||||
|
||||
@@ -182,18 +227,27 @@ class Pipe:
|
||||
return r.iter_lines()
|
||||
```
|
||||
|
||||
### 4.4 Copilot SDK Tool Definition Standards
|
||||
|
||||
When developing custom tools for GitHub Copilot SDK, you **must** define a Pydantic `BaseModel` for parameters and explicitly reference it using `params_type` in `define_tool`.
|
||||
|
||||
### 4.5 Copilot SDK Streaming & Tool Card Standards
|
||||
|
||||
- **Reasoning Streaming**: Must use native `<think>` tags and ensure proper closure (`\n</think>\n`) before outputting main content or tool calls.
|
||||
- **Native Tool Calls Block**: Output strictly formatted HTML `<details type="tool_calls"...>` blocks. Ensure all double quotes in attributes are escaped as `"`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Advanced Development Patterns
|
||||
## 5. Advanced Development Patterns
|
||||
|
||||
### 4.1 Pipe & Filter Collaboration
|
||||
### 5.1 Pipe & Filter Collaboration
|
||||
|
||||
Use `__request__.app.state` to share data between plugins:
|
||||
|
||||
- **Pipe**: `__request__.app.state.search_results = [...]`
|
||||
- **Filter (Outlet)**: Read `search_results` and format them as citation links
|
||||
|
||||
### 4.2 Async Background Tasks
|
||||
### 5.2 Async Background Tasks
|
||||
|
||||
Execute time-consuming operations without blocking the user response:
|
||||
|
||||
@@ -209,7 +263,7 @@ async def background_job(self, chat_id):
|
||||
pass
|
||||
```
|
||||
|
||||
### 4.3 Calling Built-in LLM
|
||||
### 5.3 Calling Built-in LLM
|
||||
|
||||
```python
|
||||
from open_webui.utils.chat import generate_chat_completion
|
||||
@@ -235,13 +289,13 @@ llm_response = await generate_chat_completion(
|
||||
)
|
||||
```
|
||||
|
||||
### 4.4 JS Render to Markdown (Data URL Embedding)
|
||||
### 5.4 JS Render to Markdown (Data URL Embedding)
|
||||
|
||||
For scenarios requiring complex frontend rendering (e.g., AntV charts, Mermaid diagrams) but wanting **persistent pure Markdown output**, use the Data URL embedding pattern:
|
||||
|
||||
#### Workflow
|
||||
|
||||
```
|
||||
```text
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 1. Python Action │
|
||||
│ ├── Analyze message content │
|
||||
@@ -259,116 +313,28 @@ For scenarios requiring complex frontend rendering (e.g., AntV charts, Mermaid d
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Python Side (Send JS for Execution)
|
||||
### 5.5 Agent File Delivery Standards (3-Step Delivery Protocol)
|
||||
|
||||
```python
|
||||
async def action(self, body, __event_call__, __metadata__, ...):
|
||||
chat_id = self._extract_chat_id(body, __metadata__)
|
||||
message_id = self._extract_message_id(body, __metadata__)
|
||||
|
||||
# Generate JS code
|
||||
js_code = self._generate_js_code(
|
||||
chat_id=chat_id,
|
||||
message_id=message_id,
|
||||
data=processed_data,
|
||||
)
|
||||
|
||||
# Execute JS
|
||||
if __event_call__:
|
||||
await __event_call__({
|
||||
"type": "execute",
|
||||
"data": {"code": js_code}
|
||||
})
|
||||
```
|
||||
|
||||
#### JavaScript Side (Render and Write-back)
|
||||
|
||||
```javascript
|
||||
(async function() {
|
||||
// 1. Load visualization library
|
||||
if (typeof VisualizationLib === 'undefined') {
|
||||
await new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdn.example.com/lib.min.js';
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Create offscreen container
|
||||
const container = document.createElement('div');
|
||||
container.style.cssText = 'position:absolute;left:-9999px;';
|
||||
document.body.appendChild(container);
|
||||
|
||||
// 3. Render visualization
|
||||
const instance = new VisualizationLib({ container });
|
||||
instance.render(data);
|
||||
|
||||
// 4. Export to Data URL
|
||||
const dataUrl = await instance.toDataURL({ type: 'svg', embedResources: true });
|
||||
|
||||
// 5. Cleanup
|
||||
instance.destroy();
|
||||
document.body.removeChild(container);
|
||||
|
||||
// 6. Generate Markdown image
|
||||
const markdownImage = ``;
|
||||
|
||||
// 7. Update message via API
|
||||
const token = localStorage.getItem("token");
|
||||
await fetch(`/api/v1/chats/${chatId}/messages/${messageId}/event`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: "chat:message",
|
||||
data: { content: originalContent + "\n\n" + markdownImage }
|
||||
})
|
||||
});
|
||||
})();
|
||||
```
|
||||
|
||||
#### Benefits
|
||||
|
||||
- **Pure Markdown Output**: Standard Markdown image syntax, no HTML code blocks
|
||||
- **Self-Contained**: Images embedded as Base64 Data URL, no external dependencies
|
||||
- **Persistent**: Via API write-back, images remain after page reload
|
||||
- **Cross-Platform**: Works on any client supporting Markdown images
|
||||
|
||||
#### HTML Injection vs JS Render to Markdown
|
||||
|
||||
| Feature | HTML Injection | JS Render + Markdown |
|
||||
|---------|----------------|----------------------|
|
||||
| Output Format | HTML code block | Markdown image |
|
||||
| Interactivity | ✅ Buttons, animations | ❌ Static image |
|
||||
| External Deps | Requires JS libraries | None (self-contained) |
|
||||
| Persistence | Depends on browser | ✅ Permanent |
|
||||
| File Export | Needs special handling | ✅ Direct export |
|
||||
| Use Case | Interactive content | Infographics, chart snapshots |
|
||||
|
||||
#### Reference Implementations
|
||||
|
||||
- `plugins/actions/infographic/infographic.py` - Production-ready implementation using AntV + Data URL
|
||||
1. **Write Local**: Create files in the current execution directory (`.`).
|
||||
2. **Publish**: Call `publish_file_from_workspace(filename='name.ext')`.
|
||||
3. **Display Link**: Present the returned `download_url` as a Markdown link.
|
||||
|
||||
---
|
||||
|
||||
## 5. Best Practices & Design Principles
|
||||
## 6. Best Practices & Design Principles
|
||||
|
||||
### 5.1 Naming & Positioning
|
||||
### 6.1 Naming & Positioning
|
||||
|
||||
- **Short & Punchy**: e.g., "FlashCard", "DeepRead". Avoid generic terms like "Text Analysis Assistant"
|
||||
- **Complementary**: Don't reinvent the wheel; clarify what specific problem your plugin solves
|
||||
|
||||
### 5.2 User Experience (UX)
|
||||
### 6.2 User Experience (UX)
|
||||
|
||||
- **Timely Feedback**: Send a `notification` ("Generating...") before time-consuming operations
|
||||
- **Visual Appeal**: When Action outputs HTML, use modern CSS (rounded corners, shadows, gradients)
|
||||
- **Smart Guidance**: If text is too short, prompt the user: "Suggest entering more content for better results"
|
||||
|
||||
### 5.3 Error Handling
|
||||
### 6.3 Error Handling
|
||||
|
||||
!!! danger "Never fail silently"
|
||||
Always catch exceptions and inform the user via `__event_emitter__`.
|
||||
@@ -384,9 +350,39 @@ except Exception as e:
|
||||
})
|
||||
```
|
||||
|
||||
### 6.4 Long-running Task Notifications
|
||||
|
||||
If a foreground task is expected to take more than 3 seconds, implement a user notification mechanism (e.g., sending a notification every 5 seconds).
|
||||
|
||||
---
|
||||
|
||||
## 6. Troubleshooting
|
||||
## 7. Workflow & Process
|
||||
|
||||
### 7.1 Source-derived Knowledge (from `plugins/`)
|
||||
|
||||
- **Input/context safety**: normalize multimodal text extraction, use `_get_user_context` / `_get_chat_context`, and protect frontend language detection with timeout guards.
|
||||
- **Long task UX**: emit immediate `status/notification`, then staged progress updates; keep full exception detail in backend logs.
|
||||
- **HTML merge strategy**: use stable wrapper markers (`OPENWEBUI_PLUGIN_OUTPUT`) and support both overwrite and merge modes.
|
||||
- **Theme consistency**: detect parent/system theme and apply theme-aware rendering/export styles for iframe-based outputs.
|
||||
- **Render-export-persist loop**: offscreen render (SVG/PNG) -> upload `/api/v1/files/` -> event update + persistence update to avoid refresh loss.
|
||||
- **DOCX production path**: `TITLE_SOURCE` fallback naming, reasoning-block stripping, native Word math (`latex2mathml + mathml2omml`), and citation/reference anchoring.
|
||||
- **File retrieval fallback chain**: DB inline -> S3 direct -> local path variants -> public URL -> internal API -> raw fields, with max-byte guards on each stage.
|
||||
- **Filter singleton discipline**: do not store request-scoped mutable state on `self`; compute from request context each run.
|
||||
- **Async compression pattern**: `inlet` summary injection + `outlet` background summary generation, with model-threshold override and system-message protection.
|
||||
- **Workspace/tool hardening**: explicit `params_type` schemas, strict path-boundary validation, and publish flow returning `/api/v1/files/{id}/content` with `skip_rag=true` metadata.
|
||||
- **MoE refinement pipeline**: detect aggregation prompts, parse segmented responses, and rewrite to synthesis-oriented master prompt with optional reroute model.
|
||||
|
||||
### 7.2 Copilot Engineering Configuration
|
||||
|
||||
- For repository-wide AI-assisted engineering setup (GitHub Copilot + Gemini CLI + antigravity mode), follow `docs/development/copilot-engineering-plan.md`.
|
||||
- This plan defines the shared contract for tool parameter schema/routing, file creation/publish protocol, rollback-safe delivery patterns, and streaming/tool-card compatibility.
|
||||
|
||||
- **Consistency Maintenance**: Any addition, modification, or removal of a plugin must simultaneously update the plugin code, READMEs, project docs, doc indexes, and the root README.
|
||||
- **Release Workflow**: Pushing to `main` triggers automatic release. Ensure version numbers are updated and follow SemVer. Use Conventional Commits.
|
||||
|
||||
---
|
||||
|
||||
## 8. Troubleshooting
|
||||
|
||||
??? question "HTML not showing?"
|
||||
Ensure it's wrapped in a ` ```html ... ``` ` code block.
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
|
||||
## 📚 目录
|
||||
|
||||
1. [插件开发快速入门](#1-quick-start)
|
||||
2. [核心概念与 SDK 详解](#2-core-concepts-sdk-details)
|
||||
3. [插件类型深度解析](#3-plugin-types)
|
||||
* [Action (动作)](#31-action)
|
||||
* [Filter (过滤器)](#32-filter)
|
||||
* [Pipe (管道)](#33-pipe)
|
||||
4. [高级开发模式](#4-advanced-patterns)
|
||||
5. [最佳实践与设计原则](#5-best-practices)
|
||||
6. [故障排查](#6-troubleshooting)
|
||||
1. [插件开发快速入门](#1-quick-start)
|
||||
2. [核心概念与 SDK 详解](#2-core-concepts-sdk-details)
|
||||
3. [插件类型深度解析](#3-plugin-types)
|
||||
* [Action (动作)](#31-action)
|
||||
* [Filter (过滤器)](#32-filter)
|
||||
* [Pipe (管道)](#33-pipe)
|
||||
4. [高级开发模式](#4-advanced-patterns)
|
||||
5. [最佳实践与设计原则](#5-best-practices)
|
||||
6. [仓库规范(openwebui-extensions)](#6-repo-standards)
|
||||
7. [故障排查](#7-troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
@@ -21,9 +22,10 @@
|
||||
### 1.1 什么是 OpenWebUI 插件?
|
||||
|
||||
OpenWebUI 插件(官方称为 "Functions")是扩展平台功能的主要方式。它们运行在后端 Python 环境中,允许你:
|
||||
* 🔌 **集成新模型**:通过 Pipe 接入 Claude、Gemini 或自定义 RAG。
|
||||
* 🎨 **增强交互**:通过 Action 在消息旁添加按钮(如"导出"、"生成图表")。
|
||||
* 🔧 **干预流程**:通过 Filter 在请求前后修改数据(如注入上下文、敏感词过滤)。
|
||||
|
||||
* 🔌 **集成新模型**:通过 Pipe 接入 Claude、Gemini 或自定义 RAG。
|
||||
* 🎨 **增强交互**:通过 Action 在消息旁添加按钮(如"导出"、"生成图表")。
|
||||
* 🔧 **干预流程**:通过 Filter 在请求前后修改数据(如注入上下文、敏感词过滤)。
|
||||
|
||||
### 1.2 你的第一个插件 (Hello World)
|
||||
|
||||
@@ -69,9 +71,10 @@ class Action:
|
||||
### 2.1 ⚠️ 重要:同步与异步
|
||||
|
||||
OpenWebUI 插件运行在 `asyncio` 事件循环中。
|
||||
* **原则**:所有 I/O 操作(数据库、文件、网络)必须非阻塞。
|
||||
* **陷阱**:直接调用同步方法(如 `time.sleep`, `requests.get`)会卡死整个服务器。
|
||||
* **解决**:使用 `await asyncio.to_thread(sync_func, ...)` 包装同步调用。
|
||||
|
||||
* **原则**:所有 I/O 操作(数据库、文件、网络)必须非阻塞。
|
||||
* **陷阱**:直接调用同步方法(如 `time.sleep`, `requests.get`)会卡死整个服务器。
|
||||
* **解决**:使用 `await asyncio.to_thread(sync_func, ...)` 包装同步调用。
|
||||
|
||||
### 2.2 核心参数详解
|
||||
|
||||
@@ -88,8 +91,8 @@ OpenWebUI 插件运行在 `asyncio` 事件循环中。
|
||||
|
||||
### 2.3 配置系统 (Valves)
|
||||
|
||||
* **`Valves`**: 管理员全局配置。
|
||||
* **`UserValves`**: 用户级配置(优先级更高,可覆盖全局)。
|
||||
* **`Valves`**: 管理员全局配置。
|
||||
* **`UserValves`**: 用户级配置(优先级更高,可覆盖全局)。
|
||||
|
||||
```python
|
||||
class Filter:
|
||||
@@ -113,7 +116,7 @@ class Filter:
|
||||
|
||||
**定位**:在消息下方添加按钮,用户点击触发。
|
||||
|
||||
**高级用法:前端执行 JavaScript (文件下载示例)**
|
||||
#### 高级用法:前端执行 JavaScript (文件下载示例)
|
||||
|
||||
```python
|
||||
import base64
|
||||
@@ -138,11 +141,11 @@ async def action(self, body, __event_call__):
|
||||
|
||||
**定位**:中间件,拦截并修改请求/响应。
|
||||
|
||||
* **`inlet`**: 请求前。用于注入上下文、修改模型参数。
|
||||
* **`outlet`**: 响应后。用于格式化输出、保存日志。
|
||||
* **`stream`**: 流式处理中。用于实时敏感词过滤。
|
||||
* **`inlet`**: 请求前。用于注入上下文、修改模型参数。
|
||||
* **`outlet`**: 响应后。用于格式化输出、保存日志。
|
||||
* **`stream`**: 流式处理中。用于实时敏感词过滤。
|
||||
|
||||
**示例:注入环境变量**
|
||||
#### 示例:注入环境变量
|
||||
|
||||
```python
|
||||
async def inlet(self, body, __metadata__):
|
||||
@@ -159,7 +162,7 @@ async def inlet(self, body, __metadata__):
|
||||
|
||||
**定位**:自定义模型/代理。
|
||||
|
||||
**示例:简单的 OpenAI 代理**
|
||||
#### 示例:简单的 OpenAI 代理
|
||||
|
||||
```python
|
||||
import requests
|
||||
@@ -180,11 +183,14 @@ class Pipe:
|
||||
## 4. 高级开发模式 {: #4-advanced-patterns }
|
||||
|
||||
### 4.1 Pipe 与 Filter 协同
|
||||
|
||||
利用 `__request__.app.state` 在不同插件间共享数据。
|
||||
* **Pipe**: `__request__.app.state.search_results = [...]`
|
||||
* **Filter (Outlet)**: 读取 `search_results` 并将其格式化为引用链接附加到回复末尾。
|
||||
|
||||
* **Pipe**: `__request__.app.state.search_results = [...]`
|
||||
* **Filter (Outlet)**: 读取 `search_results` 并将其格式化为引用链接附加到回复末尾。
|
||||
|
||||
### 4.2 异步后台任务
|
||||
|
||||
不阻塞用户响应,在后台执行耗时操作(如生成总结、存库)。
|
||||
|
||||
```python
|
||||
@@ -205,7 +211,7 @@ async def background_job(self, chat_id):
|
||||
|
||||
#### 工作流程
|
||||
|
||||
```
|
||||
```text
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 1. Python Action │
|
||||
│ ├── 分析消息内容 │
|
||||
@@ -297,10 +303,10 @@ async def action(self, body, __event_call__, __metadata__, ...):
|
||||
|
||||
#### 优势
|
||||
|
||||
- **纯 Markdown 输出**:结果是标准的 Markdown 图片语法,无需 HTML 代码块
|
||||
- **自包含**:图片以 Base64 Data URL 嵌入,无外部依赖
|
||||
- **持久化**:通过 API 回写,消息重新加载后图片仍然存在
|
||||
- **跨平台**:任何支持 Markdown 图片的客户端都能显示
|
||||
* **纯 Markdown 输出**:结果是标准的 Markdown 图片语法,无需 HTML 代码块
|
||||
* **自包含**:图片以 Base64 Data URL 嵌入,无外部依赖
|
||||
* **持久化**:通过 API 回写,消息重新加载后图片仍然存在
|
||||
* **跨平台**:任何支持 Markdown 图片的客户端都能显示
|
||||
|
||||
#### HTML 注入 vs JS 渲染嵌入 Markdown
|
||||
|
||||
@@ -315,20 +321,23 @@ async def action(self, body, __event_call__, __metadata__, ...):
|
||||
|
||||
#### 参考实现
|
||||
|
||||
- `plugins/actions/infographic/infographic.py` - 基于 AntV + Data URL 的生产级实现
|
||||
* `plugins/actions/infographic/infographic.py` - 基于 AntV + Data URL 的生产级实现
|
||||
|
||||
## 5. 最佳实践与设计原则 {: #5-best-practices }
|
||||
|
||||
### 5.1 命名与定位
|
||||
* **简短有力**:如 "闪记卡", "精读"。避免 "文本分析助手" 这种泛词。
|
||||
* **功能互补**:不要重复造轮子,明确你的插件解决了什么特定问题。
|
||||
|
||||
* **简短有力**:如 "闪记卡", "精读"。避免 "文本分析助手" 这种泛词。
|
||||
* **功能互补**:不要重复造轮子,明确你的插件解决了什么特定问题。
|
||||
|
||||
### 5.2 用户体验 (UX)
|
||||
* **反馈及时**:耗时操作前先发送 `notification` ("正在生成...")。
|
||||
* **视觉美观**:Action 输出 HTML 时,使用现代化的 CSS(圆角、阴影、渐变)。
|
||||
* **智能引导**:检测到文本过短时,提示用户"建议输入更多内容以获得更好结果"。
|
||||
|
||||
* **反馈及时**:耗时操作前先发送 `notification` ("正在生成...")。
|
||||
* **视觉美观**:Action 输出 HTML 时,使用现代化的 CSS(圆角、阴影、渐变)。
|
||||
* **智能引导**:检测到文本过短时,提示用户"建议输入更多内容以获得更好结果"。
|
||||
|
||||
### 5.3 错误处理
|
||||
|
||||
永远不要让插件静默失败。捕获异常并通过 `__event_emitter__` 告知用户。
|
||||
|
||||
```python
|
||||
@@ -343,8 +352,76 @@ except Exception as e:
|
||||
|
||||
---
|
||||
|
||||
## 6. 故障排查 {: #6-troubleshooting }
|
||||
## 6. 仓库规范(openwebui-extensions) {: #6-repo-standards }
|
||||
|
||||
* **HTML 不显示?** 确保包裹在 ` ```html ... ``` ` 代码块中。
|
||||
* **数据库报错?** 检查是否在 `async` 函数中直接调用了同步的 DB 方法,请使用 `asyncio.to_thread`。
|
||||
* **参数未生效?** 检查 `Valves` 定义是否正确,以及是否被 `UserValves` 覆盖。
|
||||
### 6.1 单文件 i18n 规范
|
||||
|
||||
本仓库要求每个插件采用**单文件源码 + 内置多语言**方案,禁止按语言拆分多个 `.py` 文件。
|
||||
|
||||
* 代码路径规范:`plugins/{type}/{name}/{name}.py`
|
||||
* 文档规范:必须同时提供 `README.md` 与 `README_CN.md`
|
||||
|
||||
### 6.2 上下文访问规范(必选)
|
||||
|
||||
优先通过 `_get_user_context` 与 `_get_chat_context` 提取上下文,避免直接硬编码读取 `__user__` 或 `body` 字段。
|
||||
|
||||
### 6.3 事件与日志规范
|
||||
|
||||
* 用状态/通知事件给用户反馈进度。
|
||||
* 前端调试优先使用 `execute` 注入的控制台日志。
|
||||
* 后端统一使用 Python `logging`,生产代码避免 `print()`。
|
||||
|
||||
### 6.4 前端语言探测防卡死
|
||||
|
||||
通过 `__event_call__` 获取前端语言时,必须同时满足:
|
||||
|
||||
* JS 端 `try...catch` 并保证返回值
|
||||
* 后端 `asyncio.wait_for(..., timeout=2.0)`
|
||||
|
||||
这样可以避免前端异常导致后端永久等待。
|
||||
|
||||
### 6.5 Copilot SDK 工具参数定义
|
||||
|
||||
开发 Copilot SDK 工具时,应使用 `pydantic.BaseModel` 显式声明参数,并在 `define_tool(...)` 中通过 `params_type` 传入。
|
||||
|
||||
### 6.6 Copilot SDK 流式输出格式
|
||||
|
||||
* 思考过程使用原生 `<think>...</think>`。
|
||||
* 正文或工具卡片输出前,必须先闭合 `</think>`。
|
||||
* 工具卡片使用 `<details type="tool_calls" ...>` 原生结构。
|
||||
* `arguments` 与 `result` 属性中的双引号必须转义为 `"`。
|
||||
|
||||
### 6.7 从 `plugins/` 全量提炼的关键开发知识
|
||||
|
||||
* **输入与上下文**:统一做多模态文本抽取;优先 `_get_user_context` / `_get_chat_context`;前端语言探测必须 `wait_for` 超时保护。
|
||||
* **长任务反馈**:执行前立即发送 `status/notification`,过程分阶段汇报,失败时用户提示简洁、后台日志完整。
|
||||
* **HTML/渲染输出**:使用固定包装器与插入点,支持覆盖与合并两种模式;主题跟随父页面与系统主题。
|
||||
* **前端导出闭环**:离屏渲染 SVG/PNG -> 上传 `/api/v1/files/` -> 事件更新 + 持久化更新,避免刷新丢失。
|
||||
* **DOCX 生产模式**:`TITLE_SOURCE` 多级回退;导出前剔除 reasoning;LaTeX 转 OMML;支持引用锚点与参考文献。
|
||||
* **文件访问回退链**:DB 内联 -> S3 -> 本地路径变体 -> 公网 URL -> 内部 API -> 原始字段,并在每层限制字节上限。
|
||||
* **Filter 单例安全**:禁止在 `self` 保存请求态;上下文压缩采用 inlet/outlet 双阶段 + 异步后台任务。
|
||||
* **工具与工作区安全**:工具参数用 `params_type` 显式建模;路径解析后必须二次校验在 workspace 根目录内。
|
||||
* **文件交付标准**:本地生成 -> 发布工具 -> 返回 `/api/v1/files/{id}/content`,并带 `skip_rag=true` 元数据。
|
||||
* **MoE 聚合优化**:识别聚合提示词后重写为结构化综合分析任务,并可在聚合阶段切换模型。
|
||||
|
||||
### 6.8 Copilot 工程化配置(GitHub Copilot + Gemini CLI + 反重力开发)
|
||||
|
||||
统一工程化配置请参考:
|
||||
|
||||
* `docs/development/copilot-engineering-plan.md`
|
||||
* `docs/development/copilot-engineering-plan.zh.md`
|
||||
|
||||
该设计文档规定了:
|
||||
|
||||
* 工具参数建模与路由规则
|
||||
* 文件创建与发布协议
|
||||
* 可回滚的高迭代交付模式
|
||||
* 流式输出与工具卡片兼容要求
|
||||
|
||||
---
|
||||
|
||||
## 7. 故障排查 {: #7-troubleshooting }
|
||||
|
||||
* **HTML 不显示?** 确保包裹在 ` ```html ... ``` ` 代码块中。
|
||||
* **数据库报错?** 检查是否在 `async` 函数中直接调用了同步的 DB 方法,请使用 `asyncio.to_thread`。
|
||||
* **参数未生效?** 检查 `Valves` 定义是否正确,以及是否被 `UserValves` 覆盖。
|
||||
|
||||
@@ -1,50 +1,46 @@
|
||||
# 开源项目重组实施计划
|
||||
# Implementation Plan (Current)
|
||||
|
||||
## 1. 目标
|
||||
将 `openwebui-extras` 打造为一个 **OpenWebUI 增强功能集合库**,专注于分享个人开发和收集的优质插件、提示词,而非作为一个独立的 Python 应用程序发布。
|
||||
## 1. Objective
|
||||
|
||||
## 2. 当前状态分析
|
||||
- **定位明确**:项目核心价值在于内容(Plugins, Prompts, Docs),而非运行环境。
|
||||
- **结构已优化**:
|
||||
- `plugins/`:核心插件资源。
|
||||
- `prompts/`:提示词资源。
|
||||
- `docs/`:详细的使用和开发文档。
|
||||
- `scripts/`:辅助工具脚本(如本地测试用的 `run.py`)。
|
||||
- **已移除不必要文件**:移除了 `requirements.txt`,避免用户误以为需要配置 Python 环境。
|
||||
Define an engineering-ready design baseline for OpenWebUI plugin development that:
|
||||
|
||||
## 3. 重组方案
|
||||
- uses GitHub Copilot as the primary coding agent,
|
||||
- supports Gemini CLI as a secondary execution/research lane,
|
||||
- adopts antigravity development mode for reversible, low-risk iteration.
|
||||
|
||||
### 3.1 目录结构
|
||||
保持当前的清晰结构,强调“拿来即用”:
|
||||
## 2. Current Decision
|
||||
|
||||
```
|
||||
openwebui-extras/
|
||||
├── docs/ # 文档与教程
|
||||
├── plugins/ # 插件库 (核心资源)
|
||||
│ ├── actions/
|
||||
│ ├── filters/
|
||||
│ ├── pipelines/
|
||||
│ └── pipes/
|
||||
├── prompts/ # 提示词库 (核心资源)
|
||||
├── scripts/ # 维护者工具 (非用户必须)
|
||||
├── LICENSE # MIT 许可证
|
||||
├── README.md # 项目入口与资源索引
|
||||
└── index.html # 项目展示页
|
||||
```
|
||||
The active design document is:
|
||||
|
||||
### 3.2 核心调整
|
||||
1. **移除依赖管理**:删除了 `requirements.txt`。用户不需要 `pip install` 任何东西,只需下载对应的 `.py` 或 `.md` 文件导入 OpenWebUI 即可。
|
||||
2. **文档侧重**:README 和文档将侧重于“如何下载”和“如何导入”,而不是“如何安装项目”。
|
||||
- `docs/development/copilot-engineering-plan.md`
|
||||
- `docs/development/copilot-engineering-plan.zh.md`
|
||||
|
||||
### 3.3 后续建议
|
||||
1. **资源索引**:建议在 `README.md` 中维护一个高质量的插件/提示词索引表,方便用户快速查找。
|
||||
2. **贡献指南**:制定简单的 `CONTRIBUTING.md`,告诉其他人如何提交他们的插件或提示词(例如:只需提交文件到对应目录)。
|
||||
3. **版本控制**:虽然不需要 Python 环境,但建议在插件文件的头部注释中保留版本号和兼容性说明(如 `Compatible with OpenWebUI v0.3.x`)。
|
||||
This file is retained as a stable pointer to avoid design drift.
|
||||
|
||||
## 4. 发布流程
|
||||
1. **提交更改**:`git add . && git commit -m "Update project structure for resource sharing"`
|
||||
2. **推送到 GitHub**。
|
||||
3. **宣传**:在 OpenWebUI 社区分享此仓库链接。
|
||||
## 3. Engineering Baseline
|
||||
|
||||
- Single-file i18n plugin code architecture.
|
||||
- Bilingual documentation contract.
|
||||
- Copilot SDK tool schema discipline (`params_type`).
|
||||
- Gemini CLI output normalization before merge.
|
||||
- Workspace file sandbox + publish protocol.
|
||||
- Streaming compatibility with native `<think>` and `<details type="tool_calls">`.
|
||||
|
||||
## 4. File Creation & Delivery Baseline
|
||||
|
||||
- Create artifacts in workspace-scoped paths.
|
||||
- Publish artifacts via workspace publish flow.
|
||||
- Return and display `/api/v1/files/{id}/content` links for delivery.
|
||||
|
||||
## 5. Maintenance Rule
|
||||
|
||||
Any update to plugin engineering standards must be reflected in:
|
||||
|
||||
1. `docs/development/copilot-engineering-plan.md`
|
||||
2. `docs/development/copilot-engineering-plan.zh.md`
|
||||
3. `docs/development/plugin-guide.md`
|
||||
4. `docs/development/plugin-guide.zh.md`
|
||||
|
||||
---
|
||||
*生成时间:2025-12-19*
|
||||
|
||||
Updated: 2026-02-23
|
||||
|
||||
@@ -4,15 +4,17 @@
|
||||
|
||||
## 📚 Table of Contents
|
||||
|
||||
1. [Quick Start](#1-quick-start)
|
||||
2. [Core Concepts & SDK Details](#2-core-concepts--sdk-details)
|
||||
3. [Deep Dive into Plugin Types](#3-deep-dive-into-plugin-types)
|
||||
* [Action](#31-action)
|
||||
* [Filter](#32-filter)
|
||||
* [Pipe](#33-pipe)
|
||||
4. [Advanced Development Patterns](#4-advanced-development-patterns)
|
||||
5. [Best Practices & Design Principles](#5-best-practices--design-principles)
|
||||
6. [Troubleshooting](#6-troubleshooting)
|
||||
1. [Quick Start](#1-quick-start)
|
||||
2. [Core Concepts & SDK Details](#2-core-concepts--sdk-details)
|
||||
3. [Deep Dive into Plugin Types](#3-deep-dive-into-plugin-types)
|
||||
* [Action](#31-action)
|
||||
* [Filter](#32-filter)
|
||||
* [Pipe](#33-pipe)
|
||||
4. [Advanced Development Patterns](#4-advanced-development-patterns)
|
||||
5. [Best Practices & Design Principles](#5-best-practices--design-principles)
|
||||
6. [Repository Standards (openwebui-extensions)](#6-repository-standards-openwebui-extensions)
|
||||
7. [Custom Agent Design Recommendations](#7-custom-agent-design-recommendations)
|
||||
8. [Troubleshooting](#8-troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
@@ -21,9 +23,10 @@
|
||||
### 1.1 What are OpenWebUI Plugins?
|
||||
|
||||
OpenWebUI Plugins (officially called "Functions") are the primary way to extend the platform's capabilities. Running in a backend Python environment, they allow you to:
|
||||
* 🔌 **Integrate New Models**: Connect to Claude, Gemini, or custom RAGs via Pipes.
|
||||
* 🎨 **Enhance Interaction**: Add buttons (e.g., "Export", "Generate Chart") next to messages via Actions.
|
||||
* 🔧 **Intervene in Processes**: Modify data before requests or after responses (e.g., inject context, filter sensitive words) via Filters.
|
||||
|
||||
* 🔌 **Integrate New Models**: Connect to Claude, Gemini, or custom RAGs via Pipes.
|
||||
* 🎨 **Enhance Interaction**: Add buttons (e.g., "Export", "Generate Chart") next to messages via Actions.
|
||||
* 🔧 **Intervene in Processes**: modify data before requests or after responses (e.g., inject context, filter sensitive words) via Filters.
|
||||
|
||||
### 1.2 Your First Plugin (Hello World)
|
||||
|
||||
@@ -69,9 +72,10 @@ class Action:
|
||||
### 2.1 ⚠️ Important: Sync vs Async
|
||||
|
||||
OpenWebUI plugins run within an `asyncio` event loop.
|
||||
* **Principle**: All I/O operations (database, file, network) must be non-blocking.
|
||||
* **Pitfall**: Calling synchronous methods directly (e.g., `time.sleep`, `requests.get`) will freeze the entire server.
|
||||
* **Solution**: Wrap synchronous calls using `await asyncio.to_thread(sync_func, ...)`.
|
||||
|
||||
* **Principle**: All I/O operations (database, file, network) must be non-blocking.
|
||||
* **Pitfall**: Calling synchronous methods directly (e.g., `time.sleep`, `requests.get`) will freeze the entire server.
|
||||
* **Solution**: Wrap synchronous calls using `await asyncio.to_thread(sync_func, ...)`.
|
||||
|
||||
### 2.2 Core Parameters
|
||||
|
||||
@@ -88,8 +92,8 @@ All plugin methods (`inlet`, `outlet`, `pipe`, `action`) support injecting the f
|
||||
|
||||
### 2.3 Configuration System (Valves)
|
||||
|
||||
* **`Valves`**: Global admin configuration.
|
||||
* **`UserValves`**: User-level configuration (higher priority, overrides global).
|
||||
* **`Valves`**: Global admin configuration.
|
||||
* **`UserValves`**: User-level configuration (higher priority, overrides global).
|
||||
|
||||
```python
|
||||
class Filter:
|
||||
@@ -113,7 +117,7 @@ class Filter:
|
||||
|
||||
**Role**: Adds buttons below messages that trigger upon user click.
|
||||
|
||||
**Advanced Usage: Execute JavaScript on Frontend (File Download Example)**
|
||||
#### Advanced Usage: Execute JavaScript on Frontend (File Download Example)
|
||||
|
||||
```python
|
||||
import base64
|
||||
@@ -138,11 +142,11 @@ async def action(self, body, __event_call__):
|
||||
|
||||
**Role**: Middleware that intercepts and modifies requests/responses.
|
||||
|
||||
* **`inlet`**: Before request. Used for injecting context, modifying model parameters.
|
||||
* **`outlet`**: After response. Used for formatting output, logging.
|
||||
* **`stream`**: During streaming. Used for real-time sensitive word filtering.
|
||||
* **`inlet`**: Before request. Used for injecting context, modifying model parameters.
|
||||
* **`outlet`**: After response. Used for formatting output, logging.
|
||||
* **`stream`**: During streaming. Used for real-time sensitive word filtering.
|
||||
|
||||
**Example: Injecting Environment Variables**
|
||||
#### Example: Injecting Environment Variables
|
||||
|
||||
```python
|
||||
async def inlet(self, body, __metadata__):
|
||||
@@ -159,7 +163,7 @@ async def inlet(self, body, __metadata__):
|
||||
|
||||
**Role**: Custom Model/Agent.
|
||||
|
||||
**Example: Simple OpenAI Wrapper**
|
||||
#### Example: Simple OpenAI Wrapper
|
||||
|
||||
```python
|
||||
import requests
|
||||
@@ -180,11 +184,14 @@ class Pipe:
|
||||
## 4. Advanced Development Patterns
|
||||
|
||||
### 4.1 Pipe & Filter Collaboration
|
||||
|
||||
Use `__request__.app.state` to share data between plugins.
|
||||
* **Pipe**: `__request__.app.state.search_results = [...]`
|
||||
* **Filter (Outlet)**: Read `search_results` and format them as citation links appended to the response.
|
||||
|
||||
* **Pipe**: `__request__.app.state.search_results = [...]`
|
||||
* **Filter (Outlet)**: Read `search_results` and format them as citation links appended to the response.
|
||||
|
||||
### 4.2 Async Background Tasks
|
||||
|
||||
Execute time-consuming operations (e.g., summarization, database storage) in the background without blocking the user response.
|
||||
|
||||
```python
|
||||
@@ -204,15 +211,18 @@ async def background_job(self, chat_id):
|
||||
## 5. Best Practices & Design Principles
|
||||
|
||||
### 5.1 Naming & Positioning
|
||||
* **Short & Punchy**: e.g., "FlashCard", "DeepRead". Avoid generic terms like "Text Analysis Assistant".
|
||||
* **Complementary**: Don't reinvent the wheel; clarify what specific problem your plugin solves.
|
||||
|
||||
* **Short & Punchy**: e.g., "FlashCard", "DeepRead". Avoid generic terms like "Text Analysis Assistant".
|
||||
* **Complementary**: Don't reinvent the wheel; clarify what specific problem your plugin solves.
|
||||
|
||||
### 5.2 User Experience (UX)
|
||||
* **Timely Feedback**: Send a `notification` ("Generating...") before time-consuming operations.
|
||||
* **Visual Appeal**: When Action outputs HTML, use modern CSS (rounded corners, shadows, gradients).
|
||||
* **Smart Guidance**: If text is too short, prompt the user: "Suggest entering more content for better results".
|
||||
|
||||
* **Timely Feedback**: Send a `notification` ("Generating...") before time-consuming operations.
|
||||
* **Visual Appeal**: When Action outputs HTML, use modern CSS (rounded corners, shadows, gradients).
|
||||
* **Smart Guidance**: If text is too short, prompt the user: "Suggest entering more content for better results".
|
||||
|
||||
### 5.3 Error Handling
|
||||
|
||||
Never let a plugin fail silently. Catch exceptions and inform the user via `__event_emitter__`.
|
||||
|
||||
```python
|
||||
@@ -227,8 +237,127 @@ except Exception as e:
|
||||
|
||||
---
|
||||
|
||||
## 6. Troubleshooting
|
||||
## 6. Repository Standards (openwebui-extensions)
|
||||
|
||||
* **HTML not showing?** Ensure it's wrapped in a ` ```html ... ``` ` code block.
|
||||
* **Database error?** Check if you called synchronous DB methods directly in an `async` function; use `asyncio.to_thread`.
|
||||
* **Parameters not working?** Check if `Valves` are defined correctly and if they are being overridden by `UserValves`.
|
||||
### 6.1 Single-file i18n Requirement
|
||||
|
||||
In this repository, each plugin must use a **single source file** with built-in i18n logic. Do not split source code by language.
|
||||
|
||||
* Required pattern: `plugins/{type}/{name}/{name}.py`
|
||||
* Required docs: `README.md` + `README_CN.md`
|
||||
|
||||
### 6.2 Safe Context Access (Required)
|
||||
|
||||
Prefer helper methods like `_get_user_context` and `_get_chat_context` instead of direct, fragile field access from `__user__` / `body`.
|
||||
|
||||
### 6.3 Event and Logging Conventions
|
||||
|
||||
* Use status/notification events for user-visible progress.
|
||||
* Use frontend console debug logs (`execute`) for live debugging during development.
|
||||
* Use Python `logging` for backend logs; avoid `print()` in production plugin code.
|
||||
|
||||
### 6.4 Frontend Language Detection and Timeout Guard
|
||||
|
||||
When reading frontend language via `__event_call__`, always use:
|
||||
|
||||
* JS `try...catch` fallback return
|
||||
* backend `asyncio.wait_for(..., timeout=2.0)`
|
||||
|
||||
This prevents deadlocks when frontend execution fails.
|
||||
|
||||
### 6.5 Copilot SDK Tool Definition
|
||||
|
||||
For custom Copilot SDK tools, define explicit parameter schema using a `pydantic.BaseModel` and pass it with `params_type` in `define_tool(...)`.
|
||||
|
||||
### 6.6 Copilot SDK Streaming Output Format
|
||||
|
||||
* Use native `<think>...</think>` for reasoning output.
|
||||
* Ensure `</think>` is closed before normal content or tool cards.
|
||||
* For tool result cards, use native `<details type="tool_calls" ...>` format.
|
||||
* Escape attribute quotes in `arguments` and `result` as `"`.
|
||||
|
||||
### 6.7 Source-derived Production Patterns (Recommended)
|
||||
|
||||
The following patterns are extracted from `github_copilot_sdk.py` and `workspace_file_manager.py`:
|
||||
|
||||
* **Tool parameter anti-drift**: define tools with `params_type=BaseModel`, and execute with `model_dump(exclude_unset=True)` so missing params do not become explicit `None`.
|
||||
* **Tool name normalization**: enforce `^[a-zA-Z0-9_-]+$`; if non-ASCII names collapse, use an `md5` suffix fallback to keep registration stable.
|
||||
* **Workspace sandboxing**: resolve and verify every path stays inside the workspace root to prevent traversal.
|
||||
* **3-step file delivery**: local write -> `publish_file_from_workspace` -> return `/api/v1/files/{id}/content`, with `skip_rag=true` metadata.
|
||||
* **Dual upload channel**: prefer API upload (S3-compatible), fallback to DB + local copy.
|
||||
* **Streaming stability**: close `<think>` before emitting `assistant.message_delta` content.
|
||||
* **Native tool cards**: emit `<details type="tool_calls">` on `tool.execution_complete` with strict HTML escaping (`"`, newline escaping).
|
||||
* **TODO persistence linkage**: on successful `update_todo`, sync both `TODO.md` and database state.
|
||||
|
||||
### 6.8 Full Source-derived Knowledge Base (from `plugins/`)
|
||||
|
||||
The following is a broader extraction from `actions/`, `filters/`, `pipes/`, `pipelines/`, and `tools/`:
|
||||
|
||||
* **Action input hygiene**: normalize multimodal message content, strip old plugin HTML blocks (`OPENWEBUI_PLUGIN_OUTPUT`), and enforce minimum text length before expensive model calls.
|
||||
* **Action i18n hardening**: use `TRANSLATIONS + fallback_map + base-lang fallback` (`fr-CA -> fr-FR`, `en-GB -> en-US`), keep all status/UI/JS strings in i18n keys, and protect `format(**kwargs)` formatting.
|
||||
* **Frontend language detection (production-safe)**: use priority chain `document.lang -> localStorage(locale/language) -> navigator.language -> profile/request`, and always wrap `__event_call__(execute)` with timeout.
|
||||
* **Long-running UX pattern**: emit immediate `status + notification`, report staged progress (`analyzing/rendering/saving`), and keep detailed exception data in backend logs.
|
||||
* **HTML plugin composability**: use insertion markers for style/content/script, support both overwrite (`CLEAR_PREVIOUS_HTML`) and merge mode, and keep wrappers deterministic.
|
||||
* **Theme-aware iframe rendering**: detect theme from parent meta/class/data-theme with system fallback, and inject theme-aware colors for SVG/PNG export.
|
||||
* **Client-side render-and-export pipeline**: render offscreen chart/mindmap, export SVG/PNG, upload via `/api/v1/files/`, and persist updates through event API + chat persistence API.
|
||||
* **DOCX export production patterns**: apply `TITLE_SOURCE` fallback chain (`chat_title -> markdown_title -> user+date`), remove reasoning blocks, convert LaTeX via `latex2mathml + mathml2omml`, and emit citation-aware references/bookmarks.
|
||||
* **OpenWebUI file retrieval fallback ladder**: DB inline bytes/base64 -> S3 direct read -> local path variants -> public URL -> internal `/api/v1/files/{id}/content` -> raw object attrs, with max-byte guards at every stage.
|
||||
* **Filter singleton-safe design**: never store request-scoped mutable state on `self`; compute per-request values from `body` and context helpers.
|
||||
* **Async context compression patterns**: two-phase flow (`inlet` apply summary, `outlet` async generate summary), model-level threshold overrides, fast estimate + precise count near limit, and system-message protection (`effective_keep_first`).
|
||||
* **Model compatibility guardrails**: skip incompatible model families (e.g., `copilot_sdk` paths) and avoid hardcoded default model IDs.
|
||||
* **Folder memory pattern**: trigger periodic rule extraction (`every N messages`), replace rules idempotently using block markers (`RULES_BLOCK_START/END`), and optionally update root folder.
|
||||
* **Tool workspace hardening**: all file APIs (`list/read/write/delete/publish`) must re-check sandbox boundary, enforce size limits, and return user-ready download hints.
|
||||
* **MoE prompt refiner pattern (pipeline)**: detect aggregation prompts via trigger prefix, parse original query + segmented responses, then rewrite to synthesis-oriented master prompt with optional aggregation model reroute.
|
||||
|
||||
### 6.9 Copilot-related Engineering Configuration
|
||||
|
||||
To support plugin engineering with **GitHub Copilot + Gemini CLI + antigravity mode**, adopt these controls:
|
||||
|
||||
* **Primary/secondary assistant lanes**: Copilot is primary implementation lane; Gemini CLI is secondary draft/verification lane.
|
||||
* **Single merge contract**: both lanes must pass the same repository constraints (single-file i18n, context helpers, event conventions, release workflow rules).
|
||||
* **Tool schema discipline**: all Copilot SDK tools use explicit `params_type` with Pydantic models.
|
||||
* **Antigravity safety**: small reversible edits, timeout guards, fallback routing, and deterministic file/output paths.
|
||||
* **File creation protocol**: write in workspace scope, publish via workspace publish flow, return `/api/v1/files/{id}/content` for delivery.
|
||||
|
||||
Detailed design document:
|
||||
|
||||
* `docs/development/copilot-engineering-plan.md`
|
||||
|
||||
---
|
||||
|
||||
## 7. Custom Agent Design Recommendations
|
||||
|
||||
### 7.1 Suggested architecture (for this repo)
|
||||
|
||||
* **Orchestrator Pipe**: session lifecycle, model routing, streaming events.
|
||||
* **Tool Adapter Layer**: unify OpenWebUI Tools / OpenAPI / MCP with param validation and name normalization.
|
||||
* **Workspace I/O Layer**: sandboxed file operations + publish pipeline.
|
||||
* **Render Layer**: `<think>` lifecycle, tool cards, status/notification events.
|
||||
|
||||
### 7.2 MVP checklist
|
||||
|
||||
1. Dual config model: `Valves + UserValves` (user overrides first).
|
||||
2. Unified context helpers: `_get_user_context` / `_get_chat_context`.
|
||||
3. At least one artifact-delivery tool (e.g., `publish_file_from_workspace`).
|
||||
4. Minimal streaming loop: `reasoning_delta`, `message_delta`, `tool.execution_complete`.
|
||||
5. Unified error reporting via notification events.
|
||||
|
||||
### 7.3 Three high-impact agents you can build now
|
||||
|
||||
* **Repo Analyst Agent**: output architecture map, risk list, and refactor proposals.
|
||||
* **Release Draft Agent**: generate Conventional Commit title/body + bilingual release summary.
|
||||
* **Docs Sync Agent**: compare source/doc versions and output a concrete sync file list.
|
||||
|
||||
### 7.4 Implementation priority
|
||||
|
||||
* **P0**: Release Draft Agent (highest ROI, lowest risk).
|
||||
* **P1**: Docs Sync Agent (reduces doc drift).
|
||||
* **P2**: Repo Analyst Agent (medium/long-term evolution).
|
||||
|
||||
---
|
||||
|
||||
## 8. Troubleshooting
|
||||
|
||||
* **HTML not showing?** Ensure it's wrapped in a ` ```html ... ``` ` code block.
|
||||
* **Database error?** Check if you called synchronous DB methods directly in an `async` function; use `asyncio.to_thread`.
|
||||
* **Parameters not working?** Check if `Valves` are defined correctly and if they are being overridden by `UserValves`.
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
# GitHub Copilot SDK Files Filter (v0.1.2)
|
||||
# GitHub Copilot SDK Files Filter (v0.1.3)
|
||||
|
||||
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.**
|
||||
|
||||
## ✨ v0.1.3 Updates (What's New)
|
||||
|
||||
- **🔍 BYOK Model ID Matching Fixed**: Now correctly identifies models in `github_copilot_official_sdk_pipe.xxx` format via prefix matching, in addition to keyword fallback for backward compatibility. (v0.1.3)
|
||||
- **🐛 Dual-channel Debug Log**: Added `show_debug_log` valve. When enabled, logs are written to both server-side logger and browser console (`console.group`). (v0.1.3)
|
||||
|
||||
## 🎯 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.
|
||||
@@ -48,6 +53,6 @@ Default settings work for most users unless you have specific needs:
|
||||
|
||||
## ⚠️ 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.
|
||||
- **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.
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
# GitHub Copilot SDK 文件过滤器 (v0.1.2)
|
||||
# GitHub Copilot SDK 文件过滤器 (v0.1.3)
|
||||
|
||||
这是一个专门为 [GitHub Copilot SDK Pipe](../pipes/github-copilot-sdk.zh.md) 设计的**伴侣过滤器插件**。
|
||||
|
||||
它的核心使命是:**保护用户上传的文件不被 OpenWebUI 核心系统“抢先处理”,确保 Copilot Agent 能够接收到原始文件并进行自主分析。**
|
||||
|
||||
## ✨ 0.1.3 更新内容 (What's New)
|
||||
|
||||
- **🔍 BYOK 模型 ID 匹配修复**: 新增前缀匹配(`github_copilot_official_sdk_pipe.xxx` 格式),修复 BYOK 模型无法被正确识别的问题,关键词兜底保持向后兼容。(v0.1.3)
|
||||
- **🐛 双通道调试日志**: 新增 `show_debug_log` 配置项,启用后同时向后端日志和浏览器控制台(`console.group`)输出调试信息。(v0.1.3)
|
||||
|
||||
## 🎯 为什么需要它?
|
||||
|
||||
在 OpenWebUI 的默认流程中,当你上传一个文件(如 PDF、Excel、Python 脚本)时,OpenWebUI 会自动启动 **RAG(检索增强生成)** 流程:解析文件、向量化、提取文本并注入到提示词中。
|
||||
@@ -48,6 +53,6 @@
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
* **必须配合 Copilot SDK Pipe 使用**:如果你没有安装主 Pipe 插件,本插件将导致上传的文件“凭空消失”(因为没有后续插件去 `copilot_files` 里找它们)。
|
||||
* **Gemini Filter 兼容性**:本插件已完美兼容 Gemini 多模态过滤器。只要优先级设置正确(本插件 < Gemini 插件),它们可以共存互不干扰。
|
||||
* **物理文件路径**: 确保在 Pipe 插件的 Valves 中正确设置了 `OPENWEBUI_UPLOAD_PATH`,以便文件自动搬运功能正常工作。
|
||||
- **必须配合 Copilot SDK Pipe 使用**:如果你没有安装主 Pipe 插件,本插件将导致上传的文件“凭空消失”(因为没有后续插件去 `copilot_files` 里找它们)。
|
||||
- **Gemini Filter 兼容性**:本插件已完美兼容 Gemini 多模态过滤器。只要优先级设置正确(本插件 < Gemini 插件),它们可以共存互不干扰。
|
||||
- **物理文件路径**: 确保在 Pipe 插件的 Valves 中正确设置了 `OPENWEBUI_UPLOAD_PATH`,以便文件自动搬运功能正常工作。
|
||||
|
||||
@@ -82,7 +82,7 @@ Filters act as middleware in the message pipeline:
|
||||
|
||||
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
|
||||
**Version:** 0.1.3
|
||||
|
||||
[:octicons-arrow-right-24: Documentation](github-copilot-sdk-files-filter.md)
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ Filter 充当消息管线中的中间件:
|
||||
|
||||
专门用于绕过 OpenWebUI 默认 RAG 机制的过滤器,针对 GitHub Copilot SDK 模型。确保 Agent 能够接收到原始文件进行自主分析。
|
||||
|
||||
**版本:** 0.1.2
|
||||
**版本:** 0.1.3
|
||||
|
||||
[:octicons-arrow-right-24: 查看文档](github-copilot-sdk-files-filter.zh.md)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GitHub Copilot SDK Pipe for OpenWebUI
|
||||
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.7.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.8.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
|
||||
|
||||
This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/open-webui) that integrates the official [GitHub Copilot SDK](https://github.com/github/copilot-sdk). It enables you to use **GitHub Copilot models** (e.g., `gpt-5.2-codex`, `claude-sonnet-4.5`,`gemini-3-pro`, `gpt-5-mini`) **AND** your own models via **BYOK** (OpenAI, Anthropic) directly within OpenWebUI, providing a unified agentic experience with **strict User & Chat-level Workspace Isolation**.
|
||||
|
||||
@@ -14,13 +14,23 @@ This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/
|
||||
|
||||
---
|
||||
|
||||
## ✨ v0.7.0 Updates (What's New)
|
||||
## ✨ v0.8.0 Updates (What's New)
|
||||
|
||||
- **🚀 Integrated CLI Management**: The Copilot CLI is now automatically managed and bundled via the `github-copilot-sdk` pip package. (v0.7.0)
|
||||
- **🧠 Native Tool Call UI**: Full adaptation to **OpenWebUI's native tool call UI** and thinking process visualization. (v0.7.0)
|
||||
- **🏠 OpenWebUI v0.8.0+ Fix**: Resolved "Error getting file content" download failure by switching to absolute path registration for published files. (v0.7.0)
|
||||
- **🌐 Comprehensive Multi-language Support**: Native localization for status messages in 11 languages (EN, ZH, JA, KO, FR, DE, ES, IT, RU, VI, ID). (v0.7.0)
|
||||
- **🧹 Architecture Cleanup**: Refactored core setup and optimized reasoning status display for a leaner experience. (v0.7.0)
|
||||
- **🎛️ Conditional Tool Filtering (P1~P4)**: Four-priority tool permission system. **Default ON**: If no tools are selected in Chat UI (P4), all enabled tools are active. **Whitelist Mode**: Once specific tools are checked, the whitelist strictly filters both OpenWebUI tools and MCP servers. Admin-level `config.enable` (P2) allows global server disabling. (v0.8.0)
|
||||
- **🔧 File Publish Reliability**: Fixed `Error getting file content` across all storage backends (local/S3/GCS/Azure) by using `Storage.upload_file()` directly in the fallback path. HTML files are no longer blocked by `ALLOWED_FILE_EXTENSIONS` (`?process=false` always applied). (v0.8.0)
|
||||
- **🌐 HTML Direct Access Link**: When `publish_file_from_workspace` publishes an HTML file, the plugin also provides a directly accessible HTML link for instant in-chat preview/opening. (v0.8.0)
|
||||
- **🔒 Strict File URL Format**: Published file links must be relative paths starting with `/api/v1/files/` (e.g., `/api/v1/files/{id}/content/html`). Do not use `api/...` and do not prepend any domain. (v0.8.0)
|
||||
- **🛠️ CLI Built-in Tools Always Available**: `available_tools` is now always `None`, ensuring Copilot CLI built-ins (e.g. `bash`, `create_file`) are never silently blocked regardless of MCP configuration. (v0.8.0)
|
||||
- **📌 Publish Tool Always Injected**: `publish_file_from_workspace` is no longer lost when `ENABLE_OPENWEBUI_TOOLS` is disabled. (v0.8.0)
|
||||
- **⚠️ Code Interpreter Limitation**: The `code_interpreter` tool runs in a remote, ephemeral environment. A system prompt warning now clarifies that it cannot access local files or persist changes. (v0.8.0)
|
||||
|
||||
### 🐞 Bug Fixes in v0.8.0
|
||||
|
||||
- Fixed `{"detail":"[ERROR: Error getting file content]"}` when publishing files under object storage backends by replacing fallback manual copy/DB writes with `Storage.upload_file()`.
|
||||
- Fixed HTML artifact upload being rejected by `ALLOWED_FILE_EXTENSIONS` by always appending `?process=false` on file upload API calls.
|
||||
- Fixed invalid artifact links generated as `api/...` or domain-prefixed absolute URLs; links are now constrained to `/api/v1/files/...` relative paths.
|
||||
- Fixed Copilot CLI built-ins being silently unavailable when no server tools were configured/loaded (which resulted in `available_tools=[]`); now `available_tools` remains `None`.
|
||||
- Fixed `publish_file_from_workspace` disappearing when `ENABLE_OPENWEBUI_TOOLS` was disabled.
|
||||
|
||||
---
|
||||
|
||||
@@ -33,11 +43,23 @@ This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/
|
||||
- **🧠 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 (bypasses RAG for direct binary access).
|
||||
- **📤 Workspace Artifacts (`publish_file_from_workspace`)**: Agents can generate files (Excel, CSV, HTML reports, etc.) and provide **persistent download links** directly in the chat.
|
||||
- **📤 Workspace Artifacts (`publish_file_from_workspace`)**: Agents can generate files (Excel, CSV, HTML reports, etc.) and provide **persistent download links** directly in the chat. For HTML files, a direct-access HTML link is also provided.
|
||||
- **🖼️ Interactive Artifacts**: Automatically renders HTML/JS apps generated by the agent directly in the chat interface.
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Companion Files Filter (Required for raw files)
|
||||
|
||||
`GitHub Copilot SDK Files Filter` is the companion plugin that prevents OpenWebUI's default RAG pre-processing from consuming uploaded files before the Pipe receives them.
|
||||
|
||||
- **What it does**: Moves uploaded files to `copilot_files` so the Pipe can access raw binaries directly.
|
||||
- **Why it matters**: Without it, uploaded files may be parsed/vectorized early and the Agent may lose direct raw-file access.
|
||||
- **v0.1.3 highlights**:
|
||||
- BYOK model-id matching fix (supports `github_copilot_official_sdk_pipe.xxx` prefixes).
|
||||
- Optional dual-channel debug log (`show_debug_log`) to backend logger + browser console.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Core Configuration (Valves)
|
||||
|
||||
### 1. Administrator Settings (Base)
|
||||
@@ -105,6 +127,15 @@ If this plugin has been useful, a **Star** on [OpenWebUI Extensions](https://git
|
||||
2. Create **Fine-grained token**, granting **Account permissions** -> **Copilot Requests** access.
|
||||
3. Paste the generated Token into the `GH_TOKEN` field in Valves.
|
||||
|
||||
### 3) Authentication Requirement (Mandatory)
|
||||
|
||||
You MUST configure **at least one** credential source:
|
||||
|
||||
- `GH_TOKEN` (GitHub Copilot subscription route), or
|
||||
- `BYOK_API_KEY` (OpenAI/Anthropic route).
|
||||
|
||||
If neither is configured, the model list will not appear.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Troubleshooting & Dependencies
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GitHub Copilot SDK 官方管道
|
||||
|
||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 0.7.0 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT
|
||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 0.8.0 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT
|
||||
|
||||
这是一个用于 [OpenWebUI](https://github.com/open-webui/open-webui) 的高级 Pipe 函数,深度集成了 **GitHub Copilot SDK**。它不仅支持 **GitHub Copilot 官方模型**(如 `gpt-5.2-codex`, `claude-sonnet-4.5`, `gemini-3-pro`, `gpt-5-mini`),还支持 **BYOK (自带 Key)** 模式对接自定义服务商(OpenAI, Anthropic),并具备**严格的用户与会话级工作区隔离**能力,提供统一且安全的 Agent 交互体验。
|
||||
|
||||
@@ -14,13 +14,23 @@
|
||||
|
||||
---
|
||||
|
||||
## ✨ 0.7.0 更新内容 (What's New)
|
||||
## ✨ 0.8.0 更新内容 (What's New)
|
||||
|
||||
- **🚀 CLI 免维护集成**: Copilot CLI 现在通过 `github-copilot-sdk` pip 包自动同步管理,彻底告别手动 `curl | bash` 安装问题。(v0.7.0)
|
||||
- **🧠 原生工具调用 UI**: 全面适配 **OpenWebUI 原生工具调用 UI** 与模型思考过程(思维链)展示。(v0.7.0)
|
||||
- **🏠 OpenWebUI v0.8.0+ 兼容性修复**: 通过切换为绝对路径注册发布文件,彻底解决了“Error getting file content”无法下载到本地的问题。(v0.7.0)
|
||||
- **🌐 全面的多语言支持**: 针对状态消息进行了 11 国语言的原生本地化 (中/英/日/韩/法/德/西/意/俄/越/印尼)。(v0.7.0)
|
||||
- **🧹 架构精简**: 重构了初始化逻辑并优化了推理状态显示,提供更轻量稳健的体验。(v0.7.0)
|
||||
- **🎛️ 条件工具过滤 (P1~P4)**: 四优先级工具权限体系。**默认全开**: 若未在 Chat UI (P4) 勾选任何工具,则默认启用所有工具;**白名单模式**: 一旦勾选特定工具,即刻进入严格过滤模式,且 MCP server 同步受控;管理员亦可通过 `config.enable` (P2) 全局禁用工具服务器。(v0.8.0)
|
||||
- **🔧 文件发布全面修复**: 通过在回退路径直接调用 `Storage.upload_file()`,彻底修复了所有存储后端(local/S3/GCS/Azure)下的 `Error getting file content` 问题;同时上传时自动携带 `?process=false`,HTML 文件不再被 `ALLOWED_FILE_EXTENSIONS` 拦截。(v0.8.0)
|
||||
- **🌐 HTML 直达链接**: 当 `publish_file_from_workspace` 发布的是 HTML 文件时,插件会额外提供可直接访问的 HTML 链接,便于在聊天中即时预览/打开。(v0.8.0)
|
||||
- **🔒 文件链接格式严格约束**: 发布链接必须是以 `/api/v1/files/` 开头的相对路径(例如 `/api/v1/files/{id}/content/html`)。禁止使用 `api/...`,也禁止拼接任何域名。(v0.8.0)
|
||||
- **🛠️ CLI 内置工具始终可用**: `available_tools` 统一设为 `None`,Copilot CLI 内置工具(如 `bash`、`create_file`)无论 MCP 配置如何都不会被静默屏蔽。(v0.8.0)
|
||||
- **📌 发布工具始终注入**: 即使 `ENABLE_OPENWEBUI_TOOLS` 关闭,`publish_file_from_workspace` 工具也不再丢失。(v0.8.0)
|
||||
- **⚠️ 代码解释器限制**: `code_interpreter` 工具运行在远程临时环境中。系统提示词现已包含警告,明确指出该工具无法访问本地文件或持久化更改。(v0.8.0)
|
||||
|
||||
### 🐞 v0.8.0 Bug 修复说明
|
||||
|
||||
- 修复了对象存储后端发布文件时出现的 `{"detail":"[ERROR: Error getting file content]"}`,回退路径从手动复制/写库改为 `Storage.upload_file()`。
|
||||
- 修复了 HTML 产物被 `ALLOWED_FILE_EXTENSIONS` 拦截的问题,上传接口统一追加 `?process=false`。
|
||||
- 修复了产物链接偶发被生成成 `api/...` 或带域名绝对 URL 的问题,现统一限制为 `/api/v1/files/...` 相对路径。
|
||||
- 修复了在未配置/未加载任何 server 工具时(最终出现 `available_tools=[]`)Copilot CLI 内置工具被静默禁用的问题,现统一保持 `available_tools=None`。
|
||||
- 修复了 `ENABLE_OPENWEBUI_TOOLS` 关闭时 `publish_file_from_workspace` 工具丢失的问题。
|
||||
|
||||
---
|
||||
|
||||
@@ -38,6 +48,18 @@
|
||||
|
||||
---
|
||||
|
||||
## 🧩 配套 Files Filter(原始文件必备)
|
||||
|
||||
`GitHub Copilot SDK Files Filter` 是本 Pipe 的配套插件,用于阻止 OpenWebUI 默认 RAG 在 Pipe 接手前抢先处理上传文件。
|
||||
|
||||
- **作用**: 将上传文件移动到 `copilot_files`,让 Pipe 能直接读取原始二进制。
|
||||
- **必要性**: 若未安装,文件可能被提前解析/向量化,Agent 难以拿到原始文件。
|
||||
- **v0.1.3 重点**:
|
||||
- 修复 BYOK 模型 ID 识别(支持 `github_copilot_official_sdk_pipe.xxx` 前缀匹配)。
|
||||
- 新增双通道调试日志(`show_debug_log`):后端 logger + 浏览器控制台。
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ 核心配置参数 (Valves)
|
||||
|
||||
### 1. 管理员配置 (基础设置)
|
||||
@@ -105,6 +127,15 @@
|
||||
2. 创建 **Fine-grained token**,授予 **Account permissions** -> **Copilot Requests** 访问权限。
|
||||
3. 将生成的 Token 填入插件的 `GH_TOKEN` 配置项中。
|
||||
|
||||
### 3) 认证配置要求(必填)
|
||||
|
||||
你必须至少配置以下一种凭据:
|
||||
|
||||
- `GH_TOKEN`(GitHub Copilot 官方订阅路径),或
|
||||
- `BYOK_API_KEY`(OpenAI/Anthropic 自带 Key 路径)。
|
||||
|
||||
如果两者都未配置,模型列表将不会出现。
|
||||
|
||||
---
|
||||
|
||||
## 📋 常见问题与依赖 (Troubleshooting)
|
||||
|
||||
@@ -15,7 +15,7 @@ Pipes allow you to:
|
||||
|
||||
## Available Pipe Plugins
|
||||
|
||||
- [GitHub Copilot SDK](github-copilot-sdk.md) (v0.7.0) - Official GitHub Copilot SDK integration. Features **Workspace Isolation**, **Database Persistence**, **Zero-config OpenWebUI Tool Bridge**, **BYOK** support, and **dynamic MCP discovery**. Supports streaming, multimodal, and infinite sessions. [View Deep Dive](github-copilot-sdk-deep-dive.md) | [**View Advanced Tutorial**](github-copilot-sdk-tutorial.md).
|
||||
- [GitHub Copilot SDK](github-copilot-sdk.md) (v0.8.0) - Official GitHub Copilot SDK integration. Features **Workspace Isolation**, **Database Persistence**, **Zero-config OpenWebUI Tool Bridge**, **BYOK** support, and **dynamic MCP discovery**. Supports streaming, multimodal, and infinite sessions. [View Deep Dive](github-copilot-sdk-deep-dive.md) | [**View Advanced Tutorial**](github-copilot-sdk-tutorial.md).
|
||||
- **[Case Study: GitHub 100 Star Growth Analysis](star-prediction-example.md)** - Learn how to use the GitHub Copilot SDK Pipe with Minimax 2.1 to automatically analyze CSV data and generate project growth reports.
|
||||
- **[Case Study: High-Quality Video to GIF Conversion](video-processing-example.md)** - See how the model uses system-level FFmpeg to accelerate, scale, and optimize colors for screen recordings.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ Pipes 可以用于:
|
||||
|
||||
## 可用的 Pipe 插件
|
||||
|
||||
- [GitHub Copilot SDK](github-copilot-sdk.zh.md) (v0.7.0) - GitHub Copilot SDK 官方集成。具备**工作区安全隔离**、**数据库持久化**、**零配置工具桥接**与**BYOK (自带 Key) 支持**。支持流式输出、打字机思考过程及无限会话。[查看深度架构解析](github-copilot-sdk-deep-dive.zh.md) | [**查看进阶实战教程**](github-copilot-sdk-tutorial.zh.md)。
|
||||
- [GitHub Copilot SDK](github-copilot-sdk.zh.md) (v0.8.0) - GitHub Copilot SDK 官方集成。具备**工作区安全隔离**、**数据库持久化**、**零配置工具桥接**与**BYOK (自带 Key) 支持**。支持流式输出、打字机思考过程及无限会话。[查看深度架构解析](github-copilot-sdk-deep-dive.zh.md) | [**查看进阶实战教程**](github-copilot-sdk-tutorial.zh.md)。
|
||||
- **[实战案例:GitHub 100 Star 增长预测](star-prediction-example.zh.md)** - 展示如何使用 GitHub Copilot SDK Pipe 结合 Minimax 2.1 模型,自动编写脚本分析 CSV 数据并生成详细的项目增长报告。
|
||||
- **[实战案例:视频高质量 GIF 转换与加速](video-processing-example.zh.md)** - 演示模型如何通过底层 FFmpeg 工具对录屏进行加速、缩放及双阶段色彩优化处理。
|
||||
|
||||
|
||||
@@ -4,15 +4,17 @@
|
||||
|
||||
## 📚 目录
|
||||
|
||||
1. [插件开发快速入门](#1-插件开发快速入门)
|
||||
2. [核心概念与 SDK 详解](#2-核心概念与-sdk-详解)
|
||||
3. [插件类型深度解析](#3-插件类型深度解析)
|
||||
* [Action (动作)](#31-action-动作)
|
||||
* [Filter (过滤器)](#32-filter-过滤器)
|
||||
* [Pipe (管道)](#33-pipe-管道)
|
||||
4. [高级开发模式](#4-高级开发模式)
|
||||
5. [最佳实践与设计原则](#5-最佳实践与设计原则)
|
||||
6. [故障排查](#6-故障排查)
|
||||
1. [插件开发快速入门](#1-插件开发快速入门)
|
||||
2. [核心概念与 SDK 详解](#2-核心概念与-sdk-详解)
|
||||
3. [插件类型深度解析](#3-插件类型深度解析)
|
||||
* [Action (动作)](#31-action-动作)
|
||||
* [Filter (过滤器)](#32-filter-过滤器)
|
||||
* [Pipe (管道)](#33-pipe-管道)
|
||||
4. [高级开发模式](#4-高级开发模式)
|
||||
5. [最佳实践与设计原则](#5-最佳实践与设计原则)
|
||||
6. [仓库规范(openwebui-extensions)](#6-仓库规范openwebui-extensions)
|
||||
7. [自定义 Agents 设计建议](#7-自定义-agents-设计建议)
|
||||
8. [故障排查](#8-故障排查)
|
||||
|
||||
---
|
||||
|
||||
@@ -21,9 +23,10 @@
|
||||
### 1.1 什么是 OpenWebUI 插件?
|
||||
|
||||
OpenWebUI 插件(官方称为 "Functions")是扩展平台功能的主要方式。它们运行在后端 Python 环境中,允许你:
|
||||
* 🔌 **集成新模型**:通过 Pipe 接入 Claude、Gemini 或自定义 RAG。
|
||||
* 🎨 **增强交互**:通过 Action 在消息旁添加按钮(如"导出"、"生成图表")。
|
||||
* 🔧 **干预流程**:通过 Filter 在请求前后修改数据(如注入上下文、敏感词过滤)。
|
||||
|
||||
* 🔌 **集成新模型**:通过 Pipe 接入 Claude、Gemini 或自定义 RAG。
|
||||
* 🎨 **增强交互**:通过 Action 在消息旁添加按钮(如"导出"、"生成图表")。
|
||||
* 🔧 **干预流程**:通过 Filter 在请求前后修改数据(如注入上下文、敏感词过滤)。
|
||||
|
||||
### 1.2 你的第一个插件 (Hello World)
|
||||
|
||||
@@ -69,9 +72,10 @@ class Action:
|
||||
### 2.1 ⚠️ 重要:同步与异步
|
||||
|
||||
OpenWebUI 插件运行在 `asyncio` 事件循环中。
|
||||
* **原则**:所有 I/O 操作(数据库、文件、网络)必须非阻塞。
|
||||
* **陷阱**:直接调用同步方法(如 `time.sleep`, `requests.get`)会卡死整个服务器。
|
||||
* **解决**:使用 `await asyncio.to_thread(sync_func, ...)` 包装同步调用。
|
||||
|
||||
* **原则**:所有 I/O 操作(数据库、文件、网络)必须非阻塞。
|
||||
* **陷阱**:直接调用同步方法(如 `time.sleep`, `requests.get`)会卡死整个服务器。
|
||||
* **解决**:使用 `await asyncio.to_thread(sync_func, ...)` 包装同步调用。
|
||||
|
||||
### 2.2 核心参数详解
|
||||
|
||||
@@ -88,8 +92,8 @@ OpenWebUI 插件运行在 `asyncio` 事件循环中。
|
||||
|
||||
### 2.3 配置系统 (Valves)
|
||||
|
||||
* **`Valves`**: 管理员全局配置。
|
||||
* **`UserValves`**: 用户级配置(优先级更高,可覆盖全局)。
|
||||
* **`Valves`**: 管理员全局配置。
|
||||
* **`UserValves`**: 用户级配置(优先级更高,可覆盖全局)。
|
||||
|
||||
```python
|
||||
class Filter:
|
||||
@@ -113,7 +117,7 @@ class Filter:
|
||||
|
||||
**定位**:在消息下方添加按钮,用户点击触发。
|
||||
|
||||
**高级用法:前端执行 JavaScript (文件下载示例)**
|
||||
#### 高级用法:前端执行 JavaScript (文件下载示例)
|
||||
|
||||
```python
|
||||
import base64
|
||||
@@ -138,11 +142,11 @@ async def action(self, body, __event_call__):
|
||||
|
||||
**定位**:中间件,拦截并修改请求/响应。
|
||||
|
||||
* **`inlet`**: 请求前。用于注入上下文、修改模型参数。
|
||||
* **`outlet`**: 响应后。用于格式化输出、保存日志。
|
||||
* **`stream`**: 流式处理中。用于实时敏感词过滤。
|
||||
* **`inlet`**: 请求前。用于注入上下文、修改模型参数。
|
||||
* **`outlet`**: 响应后。用于格式化输出、保存日志。
|
||||
* **`stream`**: 流式处理中。用于实时敏感词过滤。
|
||||
|
||||
**示例:注入环境变量**
|
||||
#### 示例:注入环境变量
|
||||
|
||||
```python
|
||||
async def inlet(self, body, __metadata__):
|
||||
@@ -159,7 +163,7 @@ async def inlet(self, body, __metadata__):
|
||||
|
||||
**定位**:自定义模型/代理。
|
||||
|
||||
**示例:简单的 OpenAI 代理**
|
||||
#### 示例:简单的 OpenAI 代理
|
||||
|
||||
```python
|
||||
import requests
|
||||
@@ -180,11 +184,14 @@ class Pipe:
|
||||
## 4. 高级开发模式
|
||||
|
||||
### 4.1 Pipe 与 Filter 协同
|
||||
|
||||
利用 `__request__.app.state` 在不同插件间共享数据。
|
||||
* **Pipe**: `__request__.app.state.search_results = [...]`
|
||||
* **Filter (Outlet)**: 读取 `search_results` 并将其格式化为引用链接附加到回复末尾。
|
||||
|
||||
* **Pipe**: `__request__.app.state.search_results = [...]`
|
||||
* **Filter (Outlet)**: 读取 `search_results` 并将其格式化为引用链接附加到回复末尾。
|
||||
|
||||
### 4.2 异步后台任务
|
||||
|
||||
不阻塞用户响应,在后台执行耗时操作(如生成总结、存库)。
|
||||
|
||||
```python
|
||||
@@ -204,15 +211,18 @@ async def background_job(self, chat_id):
|
||||
## 5. 最佳实践与设计原则
|
||||
|
||||
### 5.1 命名与定位
|
||||
* **简短有力**:如 "闪记卡", "精读"。避免 "文本分析助手" 这种泛词。
|
||||
* **功能互补**:不要重复造轮子,明确你的插件解决了什么特定问题。
|
||||
|
||||
* **简短有力**:如 "闪记卡", "精读"。避免 "文本分析助手" 这种泛词。
|
||||
* **功能互补**:不要重复造轮子,明确你的插件解决了什么特定问题。
|
||||
|
||||
### 5.2 用户体验 (UX)
|
||||
* **反馈及时**:耗时操作前先发送 `notification` ("正在生成...")。
|
||||
* **视觉美观**:Action 输出 HTML 时,使用现代化的 CSS(圆角、阴影、渐变)。
|
||||
* **智能引导**:检测到文本过短时,提示用户"建议输入更多内容以获得更好结果"。
|
||||
|
||||
* **反馈及时**:耗时操作前先发送 `notification` ("正在生成...")。
|
||||
* **视觉美观**:Action 输出 HTML 时,使用现代化的 CSS(圆角、阴影、渐变)。
|
||||
* **智能引导**:检测到文本过短时,提示用户"建议输入更多内容以获得更好结果"。
|
||||
|
||||
### 5.3 错误处理
|
||||
|
||||
永远不要让插件静默失败。捕获异常并通过 `__event_emitter__` 告知用户。
|
||||
|
||||
```python
|
||||
@@ -227,8 +237,127 @@ except Exception as e:
|
||||
|
||||
---
|
||||
|
||||
## 6. 故障排查
|
||||
## 6. 仓库规范(openwebui-extensions)
|
||||
|
||||
* **HTML 不显示?** 确保包裹在 ` ```html ... ``` ` 代码块中。
|
||||
* **数据库报错?** 检查是否在 `async` 函数中直接调用了同步的 DB 方法,请使用 `asyncio.to_thread`。
|
||||
* **参数未生效?** 检查 `Valves` 定义是否正确,以及是否被 `UserValves` 覆盖。
|
||||
### 6.1 单文件 i18n 规范
|
||||
|
||||
本仓库要求每个插件采用**单文件源码 + 内置多语言**方案,禁止按语言拆分多个 `.py` 文件。
|
||||
|
||||
* 代码路径规范:`plugins/{type}/{name}/{name}.py`
|
||||
* 文档规范:必须同时提供 `README.md` 与 `README_CN.md`
|
||||
|
||||
### 6.2 上下文访问规范(必选)
|
||||
|
||||
优先通过 `_get_user_context` 与 `_get_chat_context` 提取上下文,避免直接硬编码读取 `__user__` 或 `body` 字段。
|
||||
|
||||
### 6.3 事件与日志规范
|
||||
|
||||
* 用状态/通知事件给用户反馈进度。
|
||||
* 前端调试优先使用 `execute` 注入的控制台日志。
|
||||
* 后端统一使用 Python `logging`,生产代码避免 `print()`。
|
||||
|
||||
### 6.4 前端语言探测防卡死
|
||||
|
||||
通过 `__event_call__` 获取前端语言时,必须同时满足:
|
||||
|
||||
* JS 端 `try...catch` 并保证返回值
|
||||
* 后端 `asyncio.wait_for(..., timeout=2.0)`
|
||||
|
||||
这样可以避免前端异常导致后端永久等待。
|
||||
|
||||
### 6.5 Copilot SDK 工具参数定义
|
||||
|
||||
开发 Copilot SDK 工具时,应使用 `pydantic.BaseModel` 显式声明参数,并在 `define_tool(...)` 中通过 `params_type` 传入。
|
||||
|
||||
### 6.6 Copilot SDK 流式输出格式
|
||||
|
||||
* 思考过程使用原生 `<think>...</think>`。
|
||||
* 正文或工具卡片输出前,必须先闭合 `</think>`。
|
||||
* 工具卡片使用 `<details type="tool_calls" ...>` 原生结构。
|
||||
* `arguments` 与 `result` 属性中的双引号必须转义为 `"`。
|
||||
|
||||
### 6.7 从源码提炼的实战模式(建议直接复用)
|
||||
|
||||
以下模式来自 `github_copilot_sdk.py` 与 `workspace_file_manager.py`:
|
||||
|
||||
* **工具参数防漂移**:工具定义使用 `params_type=BaseModel`,执行时用 `model_dump(exclude_unset=True)`,避免把未提供参数传成 `None` 覆盖函数默认值。
|
||||
* **工具名规范化**:将工具名限制为 `^[a-zA-Z0-9_-]+$`,若全中文等导致空名,使用 `md5` 后缀兜底,保证 SDK 可注册。
|
||||
* **工作区沙箱**:所有文件路径在解析后必须校验仍位于 workspace 根目录内,阻断目录穿越。
|
||||
* **文件发布三段式**:本地写入 -> `publish_file_from_workspace` -> 返回 `/api/v1/files/{id}/content`;并写入 `skip_rag=true` 元数据。
|
||||
* **S3/本地双通道上传**:优先走 API 上传(兼容对象存储),失败再回退 DB + 本地文件。
|
||||
* **流式渲染稳定性**:`assistant.message_delta` 前确保关闭 `<think>`,避免正文被吞进思考块。
|
||||
* **原生工具卡片输出**:`tool.execution_complete` 统一输出 `<details type="tool_calls">`,并对属性执行 HTML 转义(尤其 `"`、换行)。
|
||||
* **TODO 持久化联动**:`update_todo` 成功后同时回写 `TODO.md` 与数据库,保持会话可恢复。
|
||||
|
||||
### 6.8 从 `plugins/` 全量提炼的开发知识(Action / Filter / Pipe / Pipeline / Tool)
|
||||
|
||||
以下内容来自 `plugins/` 真实源码的横向归纳:
|
||||
|
||||
* **Action 输入治理**:统一抽取多模态文本内容,注入新 HTML 前清理旧插件块(`OPENWEBUI_PLUGIN_OUTPUT`),并在调用模型前做最小长度校验。
|
||||
* **Action i18n 工程化**:采用 `TRANSLATIONS + fallback_map + 基础语言回退`(如 `fr-CA -> fr-FR`、`en-GB -> en-US`),状态/UI/JS 文案统一走翻译键,并对 `format(**kwargs)` 做异常兜底。
|
||||
* **前端语言探测(生产可用)**:优先链路为 `document.lang -> localStorage(locale/language) -> navigator.language -> profile/request`,且 `__event_call__(execute)` 必须加超时保护。
|
||||
* **长任务体验模式**:开始即发送 `status + notification`,处理中分阶段更新,失败时对用户提示简洁并将详情写入后端日志。
|
||||
* **HTML 插件可组合模式**:通过样式/内容/脚本插入点支持多次累积,兼容覆盖模式(`CLEAR_PREVIOUS_HTML`)与合并模式。
|
||||
* **iframe 主题一致性**:从父页面 `meta[name=theme-color]`、父级 class/data-theme、系统主题逐级判定明暗,并在导出 SVG/PNG 时注入主题样式。
|
||||
* **前端渲染-导出-回写闭环**:离屏渲染图表/思维导图后导出 SVG/PNG 并上传 `/api/v1/files/`,再通过事件 API + 持久化 API 同步更新消息。
|
||||
* **DOCX 导出实战模式**:使用 `TITLE_SOURCE` 回退链(`chat_title -> markdown_title -> 用户名+日期`),导出前剔除 reasoning 块,公式走 `latex2mathml + mathml2omml` 并支持降级,引用可落地为参考文献与锚点。
|
||||
* **OpenWebUI 文件读取多级回退**:DB 内联字节/base64 -> S3 直连 -> 本地路径多变体 -> 公共 URL -> 内部 API `/api/v1/files/{id}/content` -> 原始对象字段,且每层都做字节上限控制。
|
||||
* **Filter 单例安全**:不在 `self` 保存请求级临时状态,请求内数据应基于 `body` 与上下文方法即时计算。
|
||||
* **异步上下文压缩模式**:采用 `inlet` 注入历史摘要 + `outlet` 异步生成新摘要,支持模型级阈值覆盖、快速估算与精算切换,并保护系统提示(`effective_keep_first`)。
|
||||
* **模型兼容性护栏**:对不兼容模型族(如 `copilot_sdk`)跳过特定处理,默认优先使用当前会话模型,避免硬编码。
|
||||
* **文件夹记忆(Folder Memory)模式**:每 N 条消息触发规则提炼,使用 `RULES_BLOCK_START/END` 做幂等替换,并可选上推至根文件夹。
|
||||
* **工作区工具加固**:`list/read/write/delete/publish` 全链路做路径越界校验,读写设置大小限制并区分文本/二进制,发布后返回可直接展示的 Markdown 下载链接。
|
||||
* **MoE 提示词精炼(Pipeline)模式**:通过触发前缀识别聚合请求,解析原问题与多模型回答后重写为综合分析主提示词,并支持聚合阶段模型切换。
|
||||
|
||||
### 6.9 Copilot 相关工程化配置
|
||||
|
||||
为了同时支持 **GitHub Copilot + Gemini CLI + 反重力开发**,建议采用以下工程化控制:
|
||||
|
||||
* **主辅双通道**:Copilot 负责主实现,Gemini CLI 负责草案与交叉校验。
|
||||
* **统一合入契约**:两条通道都必须遵守同一仓库规范(单文件 i18n、上下文方法、事件规范、发布规则)。
|
||||
* **工具参数治理**:Copilot SDK 工具必须使用 Pydantic `params_type` 显式建模。
|
||||
* **反重力安全机制**:小步可回滚、超时保护、回退链路、输出路径确定性。
|
||||
* **文件创建协议**:在 workspace 范围内创建,按发布流程输出,并返回 `/api/v1/files/{id}/content` 进行交付。
|
||||
|
||||
设计文档:
|
||||
|
||||
* `docs/development/copilot-engineering-plan.zh.md`
|
||||
|
||||
---
|
||||
|
||||
## 7. 自定义 Agents 设计建议
|
||||
|
||||
### 7.1 推荐架构(适合你的项目)
|
||||
|
||||
* **Orchestrator Pipe**:负责会话、模型路由、流式事件。
|
||||
* **Tool Adapter Layer**:统一接入 OpenWebUI Tools / OpenAPI / MCP,并做参数校验与名称规范化。
|
||||
* **Workspace I/O Layer**:统一文件读写、发布、沙箱校验。
|
||||
* **Render Layer**:统一处理 `<think>`、工具卡片、通知事件。
|
||||
|
||||
### 7.2 最小可用 Agent 清单(MVP)
|
||||
|
||||
1. `Valves + UserValves` 双层配置(用户优先覆盖)。
|
||||
2. `_get_user_context` / `_get_chat_context` 统一上下文入口。
|
||||
3. 至少 1 个可落地产物工具(如 `publish_file_from_workspace`)。
|
||||
4. 流式事件最小闭环:`reasoning_delta`、`message_delta`、`tool.execution_complete`。
|
||||
5. 错误统一走通知事件,不静默失败。
|
||||
|
||||
### 7.3 你现在就可以新增的 3 类 Agent
|
||||
|
||||
* **Repo Analyst Agent**:扫描仓库并输出“架构图 + 风险清单 + 重构建议”。
|
||||
* **Release Draft Agent**:自动生成 Conventional Commit、双语变更摘要、发布检查清单。
|
||||
* **Docs Sync Agent**:对比代码与文档版本差异,自动给出需同步文件列表。
|
||||
|
||||
### 7.4 实施优先级建议
|
||||
|
||||
* **P0**:先做 `Release Draft Agent`(收益最高、风险最低)。
|
||||
* **P1**:再做 `Docs Sync Agent`(减少文档漂移)。
|
||||
* **P2**:最后做 `Repo Analyst Agent`(用于中长期演进)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 故障排查
|
||||
|
||||
* **HTML 不显示?** 确保包裹在 ` ```html ... ``` ` 代码块中。
|
||||
* **数据库报错?** 检查是否在 `async` 函数中直接调用了同步的 DB 方法,请使用 `asyncio.to_thread`。
|
||||
* **参数未生效?** 检查 `Valves` 定义是否正确,以及是否被 `UserValves` 覆盖。
|
||||
|
||||
71
mkdocs.yml
71
mkdocs.yml
@@ -1,3 +1,4 @@
|
||||
# yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json
|
||||
# MkDocs Configuration for OpenWebUI Extras
|
||||
# Site Information
|
||||
site_name: OpenWebUI Extensions
|
||||
@@ -17,12 +18,12 @@ copyright: Copyright © 2024 OpenWebUI Extensions Contributors
|
||||
theme:
|
||||
name: material
|
||||
language: en
|
||||
|
||||
|
||||
# Logo and Icons
|
||||
icon:
|
||||
logo: material/robot-happy-outline
|
||||
repo: fontawesome/brands/github
|
||||
|
||||
|
||||
# Color Palette with Light/Dark Toggle
|
||||
palette:
|
||||
# Light mode
|
||||
@@ -33,7 +34,7 @@ theme:
|
||||
toggle:
|
||||
icon: material/brightness-7
|
||||
name: Switch to dark mode
|
||||
|
||||
|
||||
# Dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
@@ -42,7 +43,7 @@ theme:
|
||||
toggle:
|
||||
icon: material/brightness-4
|
||||
name: Switch to light mode
|
||||
|
||||
|
||||
# Navigation Features
|
||||
features:
|
||||
- navigation.tabs
|
||||
@@ -59,7 +60,7 @@ theme:
|
||||
- content.code.annotate
|
||||
- content.tabs.link
|
||||
- toc.follow
|
||||
|
||||
|
||||
# Custom fonts
|
||||
font:
|
||||
text: Roboto
|
||||
@@ -94,6 +95,7 @@ plugins:
|
||||
Development: 开发指南
|
||||
Contributing: 贡献指南
|
||||
Plugin Development Guide: 插件开发指南
|
||||
Copilot Engineering Plan: Copilot 工程化配置
|
||||
Documentation Guide: 文档编写指南
|
||||
Smart Mind Map: 智能思维导图
|
||||
Smart Infographic: 智能信息图
|
||||
@@ -121,7 +123,7 @@ markdown_extensions:
|
||||
- toc:
|
||||
permalink: true
|
||||
toc_depth: 3
|
||||
|
||||
|
||||
# Python Markdown Extensions
|
||||
- pymdownx.arithmatex:
|
||||
generic: true
|
||||
@@ -163,9 +165,9 @@ extra:
|
||||
social:
|
||||
- icon: fontawesome/brands/github
|
||||
link: https://github.com/Fu-Jie/openwebui-extensions
|
||||
|
||||
|
||||
generator: false
|
||||
|
||||
|
||||
# Analytics (optional - add your tracking ID)
|
||||
# analytics:
|
||||
# provider: google
|
||||
@@ -179,33 +181,34 @@ extra_css:
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Plugins:
|
||||
- plugins/index.md
|
||||
- Actions:
|
||||
- plugins/actions/index.md
|
||||
- Smart Mind Map: plugins/actions/smart-mind-map.md
|
||||
- Smart Infographic: plugins/actions/smart-infographic.md
|
||||
- Flash Card: plugins/actions/flash-card.md
|
||||
- Export to Excel: plugins/actions/export-to-excel.md
|
||||
- Export to Word: plugins/actions/export-to-word.md
|
||||
- Filters:
|
||||
- plugins/filters/index.md
|
||||
- Async Context Compression: plugins/filters/async-context-compression.md
|
||||
- Context Enhancement: plugins/filters/context-enhancement.md
|
||||
- Multi-Model Context Merger: plugins/filters/multi-model-context-merger.md
|
||||
- Web Gemini Multimodal Filter: plugins/filters/web-gemini-multimodel.md
|
||||
- Pipes:
|
||||
- plugins/pipes/index.md
|
||||
- Pipelines:
|
||||
- plugins/pipelines/index.md
|
||||
- MoE Prompt Refiner: plugins/pipelines/moe-prompt-refiner.md
|
||||
- plugins/index.md
|
||||
- Actions:
|
||||
- plugins/actions/index.md
|
||||
- Smart Mind Map: plugins/actions/smart-mind-map.md
|
||||
- Smart Infographic: plugins/actions/smart-infographic.md
|
||||
- Flash Card: plugins/actions/flash-card.md
|
||||
- Export to Excel: plugins/actions/export-to-excel.md
|
||||
- Export to Word: plugins/actions/export-to-word.md
|
||||
- Filters:
|
||||
- plugins/filters/index.md
|
||||
- Async Context Compression: plugins/filters/async-context-compression.md
|
||||
- Context Enhancement: plugins/filters/context-enhancement.md
|
||||
- Multi-Model Context Merger: plugins/filters/multi-model-context-merger.md
|
||||
- Web Gemini Multimodal Filter: plugins/filters/web-gemini-multimodel.md
|
||||
- Pipes:
|
||||
- plugins/pipes/index.md
|
||||
- Pipelines:
|
||||
- plugins/pipelines/index.md
|
||||
- MoE Prompt Refiner: plugins/pipelines/moe-prompt-refiner.md
|
||||
- Prompts:
|
||||
- prompts/index.md
|
||||
- prompts/library.md
|
||||
- prompts/index.md
|
||||
- prompts/library.md
|
||||
- Enhancements:
|
||||
- enhancements/index.md
|
||||
- enhancements/guide.md
|
||||
- enhancements/index.md
|
||||
- enhancements/guide.md
|
||||
- Development:
|
||||
- development/index.md
|
||||
- Plugin Development Guide: development/plugin-guide.md
|
||||
- Documentation Guide: development/documentation-guide.md
|
||||
- development/index.md
|
||||
- Plugin Development Guide: development/plugin-guide.md
|
||||
- Copilot Engineering Plan: development/copilot-engineering-plan.md
|
||||
- Documentation Guide: development/documentation-guide.md
|
||||
- Contributing: contributing.md
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
# GitHub Copilot SDK Files Filter
|
||||
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.1.2 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.1.3 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **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.**
|
||||
|
||||
## ✨ v0.1.3 Updates (What's New)
|
||||
|
||||
- **🔍 BYOK Model ID Matching Fixed**: Now correctly identifies models in `github_copilot_official_sdk_pipe.xxx` format via prefix matching, in addition to keyword fallback for backward compatibility. (v0.1.3)
|
||||
- **🐛 Dual-channel Debug Log**: Added `show_debug_log` valve. When enabled, logs are written to both server-side logger and browser console (`console.group`). (v0.1.3)
|
||||
|
||||
## 🎯 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.
|
||||
@@ -49,5 +54,5 @@ Default settings work for most users:
|
||||
|
||||
## ⚠️ 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.
|
||||
- **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.
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
# GitHub Copilot SDK 文件过滤器
|
||||
|
||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **版本:** 0.1.2 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT
|
||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **版本:** 0.1.3 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT
|
||||
|
||||
这是一个专门为 [GitHub Copilot SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) 设计的**伴侣过滤器插件**。
|
||||
|
||||
它的核心使命是:**保护用户上传的文件不被 OpenWebUI 核心系统“抢先处理”,确保 Copilot Agent 能够接收到原始文件并进行自主分析。**
|
||||
|
||||
## ✨ 0.1.3 更新内容 (What's New)
|
||||
|
||||
- **🔍 BYOK 模型 ID 匹配修复**: 新增前缀匹配(`github_copilot_official_sdk_pipe.xxx` 格式),修复 BYOK 模型无法被正确识别的问题,关键词兜底保持向后兼容。(v0.1.3)
|
||||
- **🐛 双通道调试日志**: 新增 `show_debug_log` 配置项,启用后同时向后端日志和浏览器控制台(`console.group`)输出调试信息。(v0.1.3)
|
||||
|
||||
## 🎯 为什么需要它?
|
||||
|
||||
在 OpenWebUI 的默认流程中,当你上传一个文件(如 PDF、Excel、Python 脚本)时,OpenWebUI 会自动启动 **RAG(检索增强生成)** 流程:解析文件、向量化、提取文本并注入到提示词中。
|
||||
@@ -47,5 +52,5 @@
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
* **必须配合 Copilot SDK Pipe 使用**:如果你没有安装主 Pipe 插件,本插件将导致上传的文件“凭空消失”。
|
||||
* **Gemini Filter 兼容性**:已完美兼容 Gemini 多模态过滤器。只要优先级设置正确,它们可以共存互不干扰。
|
||||
- **必须配合 Copilot SDK Pipe 使用**:如果你没有安装主 Pipe 插件,本插件将导致上传的文件“凭空消失”。
|
||||
- **Gemini Filter 兼容性**:已完美兼容 Gemini 多模态过滤器。只要优先级设置正确,它们可以共存互不干扰。
|
||||
|
||||
@@ -4,13 +4,18 @@ id: github_copilot_sdk_files_filter
|
||||
author: Fu-Jie
|
||||
author_url: https://github.com/Fu-Jie/openwebui-extensions
|
||||
funding_url: https://github.com/open-webui
|
||||
version: 0.1.2
|
||||
version: 0.1.3
|
||||
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
|
||||
import logging
|
||||
import json
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Filter:
|
||||
@@ -23,10 +28,62 @@ class Filter:
|
||||
default="copilot_sdk",
|
||||
description="Keyword to identify Copilot models (e.g., 'copilot_sdk').",
|
||||
)
|
||||
target_model_prefixes: str = Field(
|
||||
default="github_copilot_official_sdk_pipe.,github_copilot_sdk_pipe.",
|
||||
description="Comma-separated model id prefixes to identify Copilot SDK models.",
|
||||
)
|
||||
show_debug_log: bool = Field(
|
||||
default=False,
|
||||
description="Whether to print model matching debug logs in backend console.",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.valves = self.Valves()
|
||||
|
||||
def _is_copilot_model(self, model_id: str) -> bool:
|
||||
if not isinstance(model_id, str) or not model_id:
|
||||
return False
|
||||
|
||||
current = model_id.strip().lower()
|
||||
if not current:
|
||||
return False
|
||||
|
||||
# 1) Prefix match (most reliable for OpenWebUI model id formats)
|
||||
raw_prefixes = self.valves.target_model_prefixes or ""
|
||||
prefixes = [p.strip().lower() for p in raw_prefixes.split(",") if p.strip()]
|
||||
if any(current.startswith(prefix) for prefix in prefixes):
|
||||
return True
|
||||
|
||||
# 2) Keyword fallback for backward compatibility
|
||||
keyword = (self.valves.target_model_keyword or "").strip().lower()
|
||||
return bool(keyword and keyword in current)
|
||||
|
||||
async def _emit_debug_log(
|
||||
self,
|
||||
__event_emitter__: Optional[Callable[[dict], Awaitable[None]]],
|
||||
title: str,
|
||||
data: dict,
|
||||
):
|
||||
if not self.valves.show_debug_log:
|
||||
return
|
||||
|
||||
logger.info("[Copilot Files Filter] %s: %s", title, data)
|
||||
|
||||
if not __event_emitter__:
|
||||
return
|
||||
|
||||
try:
|
||||
js_code = f"""
|
||||
(async function() {{
|
||||
console.group('🧩 Copilot Files Filter: {title}');
|
||||
console.log({json.dumps(data, ensure_ascii=False)});
|
||||
console.groupEnd();
|
||||
}})();
|
||||
"""
|
||||
await __event_emitter__({"type": "execute", "data": {"code": js_code}})
|
||||
except Exception as e:
|
||||
logger.debug("[Copilot Files Filter] frontend debug emit failed: %s", e)
|
||||
|
||||
async def inlet(
|
||||
self,
|
||||
body: dict,
|
||||
@@ -45,8 +102,30 @@ class Filter:
|
||||
|
||||
current_model = base_model_id if base_model_id else body.get("model", "")
|
||||
|
||||
await self._emit_debug_log(
|
||||
__event_emitter__,
|
||||
"model-debug",
|
||||
{
|
||||
"body_model": body.get("model", ""),
|
||||
"base_model_id": base_model_id,
|
||||
"current_model": current_model,
|
||||
},
|
||||
)
|
||||
|
||||
# Check if it's a Copilot model
|
||||
if self.valves.target_model_keyword.lower() in current_model.lower():
|
||||
is_copilot_model = self._is_copilot_model(current_model)
|
||||
|
||||
await self._emit_debug_log(
|
||||
__event_emitter__,
|
||||
"match-result",
|
||||
{
|
||||
"is_copilot_model": is_copilot_model,
|
||||
"prefixes": self.valves.target_model_prefixes,
|
||||
"keyword": self.valves.target_model_keyword,
|
||||
},
|
||||
)
|
||||
|
||||
if is_copilot_model:
|
||||
# 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"]:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GitHub Copilot SDK Pipe for OpenWebUI
|
||||
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.7.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
|
||||
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.8.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
|
||||
|
||||
This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/open-webui) that integrates the official [GitHub Copilot SDK](https://github.com/github/copilot-sdk). It enables you to use **GitHub Copilot models** (e.g., `gpt-5.2-codex`, `claude-sonnet-4.5`,`gemini-3-pro`, `gpt-5-mini`) **AND** your own models via **BYOK** (OpenAI, Anthropic) directly within OpenWebUI, providing a unified agentic experience with **strict User & Chat-level Workspace Isolation**.
|
||||
|
||||
@@ -14,13 +14,23 @@ This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/
|
||||
|
||||
---
|
||||
|
||||
## ✨ v0.7.0 Updates (What's New)
|
||||
## ✨ v0.8.0 Updates (What's New)
|
||||
|
||||
- **🚀 Integrated CLI Management**: The Copilot CLI is now automatically managed and bundled via the `github-copilot-sdk` pip package. No more manual `curl | bash` installation or version mismatches. (v0.7.0)
|
||||
- **🧠 Native Tool Call UI**: Full adaptation to **OpenWebUI's native tool call UI** and thinking process visualization. (v0.7.0)
|
||||
- **🏠 OpenWebUI v0.8.0+ Fix**: Resolved "Error getting file content" download failure by switching to absolute path registration for published files. (v0.7.0)
|
||||
- **🌐 Comprehensive Multi-language Support**: Native localization for status messages in 11 languages (EN, ZH, JA, KO, FR, DE, ES, IT, RU, VI, ID). (v0.7.0)
|
||||
- **🧹 Architecture Cleanup**: Refactored core setup and optimized reasoning status display for a leaner experience. (v0.7.0)
|
||||
- **🎛️ Conditional Tool Filtering (P1~P4)**: Four-priority tool permission system. **Default ON**: If no tools are selected in Chat UI (P4), all enabled tools are active. **Whitelist Mode**: Once specific tools are checked, the whitelist strictly filters both OpenWebUI tools and MCP servers. Admin-level `config.enable` (P2) allows global server disabling. (v0.8.0)
|
||||
- **🔧 File Publish Reliability**: Fixed `Error getting file content` across all storage backends (local/S3/GCS/Azure) by using `Storage.upload_file()` directly in the fallback path. HTML files are no longer blocked by `ALLOWED_FILE_EXTENSIONS` (`?process=false` always applied). (v0.8.0)
|
||||
- **🌐 HTML Direct Access Link**: When `publish_file_from_workspace` publishes an HTML file, the plugin also provides a directly accessible HTML link for instant in-chat preview/opening. (v0.8.0)
|
||||
- **🔒 Strict File URL Format**: Published file links must be relative paths starting with `/api/v1/files/` (e.g., `/api/v1/files/{id}/content/html`). Do not use `api/...` and do not prepend any domain. (v0.8.0)
|
||||
- **🛠️ CLI Built-in Tools Always Available**: `available_tools` is now always `None`, ensuring Copilot CLI built-ins (e.g. `bash`, `create_file`) are never silently blocked regardless of MCP configuration. (v0.8.0)
|
||||
- **📌 Publish Tool Always Injected**: `publish_file_from_workspace` is no longer lost when `ENABLE_OPENWEBUI_TOOLS` is disabled. (v0.8.0)
|
||||
- **⚠️ Code Interpreter Limitation**: The `code_interpreter` tool runs in a remote, ephemeral environment. A system prompt warning now clarifies that it cannot access local files or persist changes. (v0.8.0)
|
||||
|
||||
### 🐞 Bug Fixes in v0.8.0
|
||||
|
||||
- Fixed `{"detail":"[ERROR: Error getting file content]"}` when publishing files under object storage backends by replacing fallback manual copy/DB writes with `Storage.upload_file()`.
|
||||
- Fixed HTML artifact upload being rejected by `ALLOWED_FILE_EXTENSIONS` by always appending `?process=false` on file upload API calls.
|
||||
- Fixed invalid artifact links generated as `api/...` or domain-prefixed absolute URLs; links are now constrained to `/api/v1/files/...` relative paths.
|
||||
- Fixed Copilot CLI built-ins being silently unavailable when no server tools were configured/loaded (which resulted in `available_tools=[]`); now `available_tools` remains `None`.
|
||||
- Fixed `publish_file_from_workspace` disappearing when `ENABLE_OPENWEBUI_TOOLS` was disabled.
|
||||
|
||||
---
|
||||
|
||||
@@ -33,11 +43,23 @@ This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/
|
||||
- **🧠 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 (bypasses RAG for direct binary access).
|
||||
- **📤 Workspace Artifacts (`publish_file_from_workspace`)**: Agents can generate files (Excel, CSV, HTML reports, etc.) and provide **persistent download links** directly in the chat.
|
||||
- **📤 Workspace Artifacts (`publish_file_from_workspace`)**: Agents can generate files (Excel, CSV, HTML reports, etc.) and provide **persistent download links** directly in the chat. For HTML files, a direct-access HTML link is also provided.
|
||||
- **🖼️ Interactive Artifacts**: Automatically renders HTML/JS apps generated by the agent directly in the chat interface.
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Companion Files Filter (Required for raw files)
|
||||
|
||||
`GitHub Copilot SDK Files Filter` is the companion plugin that prevents OpenWebUI's default RAG pre-processing from consuming uploaded files before the Pipe receives them.
|
||||
|
||||
- **What it does**: Moves uploaded files to `copilot_files` so the Pipe can access raw binaries directly.
|
||||
- **Why it matters**: Without it, uploaded files may be parsed/vectorized early and the Agent may lose direct raw-file access.
|
||||
- **v0.1.3 highlights**:
|
||||
- BYOK model-id matching fix (supports `github_copilot_official_sdk_pipe.xxx` prefixes).
|
||||
- Optional dual-channel debug log (`show_debug_log`) to backend logger + browser console.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Core Configuration (Valves)
|
||||
|
||||
### 1. Administrator Settings (Base)
|
||||
@@ -105,6 +127,15 @@ If this plugin has been useful, a **Star** on [OpenWebUI Extensions](https://git
|
||||
2. Create **Fine-grained token**, granting **Account permissions** -> **Copilot Requests** access.
|
||||
3. Paste the generated Token into the `GH_TOKEN` field in Valves.
|
||||
|
||||
### 3) Authentication Requirement (Mandatory)
|
||||
|
||||
You MUST configure **at least one** credential source:
|
||||
|
||||
- `GH_TOKEN` (GitHub Copilot subscription route), or
|
||||
- `BYOK_API_KEY` (OpenAI/Anthropic route).
|
||||
|
||||
If neither is configured, the model list will not appear.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Troubleshooting & Dependencies
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# GitHub Copilot SDK 官方管道
|
||||
|
||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **版本:** 0.7.0 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT
|
||||
**作者:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **版本:** 0.8.0 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT
|
||||
|
||||
这是一个用于 [OpenWebUI](https://github.com/open-webui/open-webui) 的高级 Pipe 函数,深度集成了 **GitHub Copilot SDK**。它不仅支持 **GitHub Copilot 官方模型**(如 `gpt-5.2-codex`, `claude-sonnet-4.5`, `gemini-3-pro`, `gpt-5-mini`),还支持 **BYOK (自带 Key)** 模式对接自定义服务商(OpenAI, Anthropic),并具备**严格的用户与会话级工作区隔离**能力,提供统一且安全的 Agent 交互体验。
|
||||
|
||||
@@ -14,13 +14,23 @@
|
||||
|
||||
---
|
||||
|
||||
## ✨ 0.7.0 更新内容 (What's New)
|
||||
## ✨ 0.8.0 更新内容 (What's New)
|
||||
|
||||
- **🚀 CLI 免维护集成**: Copilot CLI 现在通过 `github-copilot-sdk` pip 包自动同步管理,彻底告别手动 `curl | bash` 安装及版本不匹配问题。(v0.7.0)
|
||||
- **🧠 原生工具调用 UI**: 全面适配 **OpenWebUI 原生工具调用 UI** 与模型思考过程(思维链)展示。(v0.7.0)
|
||||
- **🏠 OpenWebUI v0.8.0+ 兼容性修复**: 通过切换为绝对路径注册发布文件,彻底解决了“Error getting file content”无法下载到本地的问题。(v0.7.0)
|
||||
- **🌐 全面的多语言支持**: 针对状态消息进行了 11 国语言的原生本地化 (中/英/日/韩/法/德/西/意/俄/越/印尼)。(v0.7.0)
|
||||
- **🧹 架构精简**: 重构了初始化逻辑并优化了推理状态显示,提供更轻量稳健的体验。(v0.7.0)
|
||||
- **🎛️ 条件工具过滤 (P1~P4)**: 四优先级工具权限体系。**默认全开**: 若未在 Chat UI (P4) 勾选任何工具,则默认启用所有工具;**白名单模式**: 一旦勾选特定工具,即刻进入严格过滤模式,且 MCP server 同步受控;管理员亦可通过 `config.enable` (P2) 全局禁用工具服务器。(v0.8.0)
|
||||
- **🔧 文件发布全面修复**: 通过在回退路径直接调用 `Storage.upload_file()`,彻底修复了所有存储后端(local/S3/GCS/Azure)下的 `Error getting file content` 问题;同时上传时自动携带 `?process=false`,HTML 文件不再被 `ALLOWED_FILE_EXTENSIONS` 拦截。(v0.8.0)
|
||||
- **🌐 HTML 直达链接**: 当 `publish_file_from_workspace` 发布的是 HTML 文件时,插件会额外提供可直接访问的 HTML 链接,便于在聊天中即时预览/打开。(v0.8.0)
|
||||
- **🔒 文件链接格式严格约束**: 发布链接必须是以 `/api/v1/files/` 开头的相对路径(例如 `/api/v1/files/{id}/content/html`)。禁止使用 `api/...`,也禁止拼接任何域名。(v0.8.0)
|
||||
- **🛠️ CLI 内置工具始终可用**: `available_tools` 统一设为 `None`,Copilot CLI 内置工具(如 `bash`、`create_file`)无论 MCP 配置如何都不会被静默屏蔽。(v0.8.0)
|
||||
- **📌 发布工具始终注入**: 即使 `ENABLE_OPENWEBUI_TOOLS` 关闭,`publish_file_from_workspace` 工具也不再丢失。(v0.8.0)
|
||||
- **⚠️ 代码解释器限制**: `code_interpreter` 工具运行在远程临时环境中。系统提示词现已包含警告,明确指出该工具无法访问本地文件或持久化更改。(v0.8.0)
|
||||
|
||||
### 🐞 v0.8.0 Bug 修复说明
|
||||
|
||||
- 修复了对象存储后端发布文件时出现的 `{"detail":"[ERROR: Error getting file content]"}`,回退路径从手动复制/写库改为 `Storage.upload_file()`。
|
||||
- 修复了 HTML 产物被 `ALLOWED_FILE_EXTENSIONS` 拦截的问题,上传接口统一追加 `?process=false`。
|
||||
- 修复了产物链接偶发被生成成 `api/...` 或带域名绝对 URL 的问题,现统一限制为 `/api/v1/files/...` 相对路径。
|
||||
- 修复了在未配置/未加载任何 server 工具时(最终出现 `available_tools=[]`)Copilot CLI 内置工具被静默禁用的问题,现统一保持 `available_tools=None`。
|
||||
- 修复了 `ENABLE_OPENWEBUI_TOOLS` 关闭时 `publish_file_from_workspace` 工具丢失的问题。
|
||||
|
||||
---
|
||||
|
||||
@@ -33,11 +43,23 @@
|
||||
- **🧠 深度数据库集成**: 实时持久化 TOD·O 列表到 UI 进度条。
|
||||
- **🌊 深度推理展示**: 完整支持模型思考过程 (Thinking Process) 的流式渲染。
|
||||
- **🖼️ 智能多模态**: 完整支持图像识别与附件上传分析(绕过 RAG 直接访问原始二进制内容)。
|
||||
- **📤 工作区产物工具 (`publish_file_from_workspace`)**: Agent 可生成文件(Excel、CSV、HTML 报告等)并直接在聊天中提供**持久化下载链接**。
|
||||
- **📤 工作区产物工具 (`publish_file_from_workspace`)**: Agent 可生成文件(Excel、CSV、HTML 报告等)并直接在聊天中提供**持久化下载链接**。若为 HTML 文件,还会额外提供可直接访问的 HTML 链接。
|
||||
- **🖼️ 交互式伪影 (Artifacts)**: 自动渲染 Agent 生成的 HTML/JS 应用程序,直接在聊天界面交互。
|
||||
|
||||
---
|
||||
|
||||
## 🧩 配套 Files Filter(原始文件必备)
|
||||
|
||||
`GitHub Copilot SDK Files Filter` 是本 Pipe 的配套插件,用于阻止 OpenWebUI 默认 RAG 在 Pipe 接手前抢先处理上传文件。
|
||||
|
||||
- **作用**: 将上传文件移动到 `copilot_files`,让 Pipe 能直接读取原始二进制。
|
||||
- **必要性**: 若未安装,文件可能被提前解析/向量化,Agent 难以拿到原始文件。
|
||||
- **v0.1.3 重点**:
|
||||
- 修复 BYOK 模型 ID 识别(支持 `github_copilot_official_sdk_pipe.xxx` 前缀匹配)。
|
||||
- 新增双通道调试日志(`show_debug_log`):后端 logger + 浏览器控制台。
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ 核心配置参数 (Valves)
|
||||
|
||||
### 1. 管理员配置 (基础设置)
|
||||
@@ -105,7 +127,16 @@
|
||||
2. 创建 **Fine-grained token**,授予 **Account permissions** -> **Copilot Requests** 访问权限。
|
||||
3. 将生成的 Token 填入插件的 `GH_TOKEN` 配置项中。
|
||||
|
||||
### 3) 配套插件 (强烈推荐)
|
||||
### 3) 认证配置要求(必填)
|
||||
|
||||
你必须至少配置以下一种凭据:
|
||||
|
||||
- `GH_TOKEN`(GitHub Copilot 官方订阅路径),或
|
||||
- `BYOK_API_KEY`(OpenAI/Anthropic 自带 Key 路径)。
|
||||
|
||||
如果两者都未配置,模型列表将不会出现。
|
||||
|
||||
### 4) 配套插件 (强烈推荐)
|
||||
|
||||
为了获得最佳的文件处理体验,请安装 [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)。
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ author_url: https://github.com/Fu-Jie/openwebui-extensions
|
||||
funding_url: https://github.com/open-webui
|
||||
openwebui_id: ce96f7b4-12fc-4ac3-9a01-875713e69359
|
||||
description: Integrate GitHub Copilot SDK. Supports dynamic models, multi-turn conversation, streaming, multimodal input, infinite sessions, and frontend debug logging.
|
||||
version: 0.7.0
|
||||
version: 0.8.0
|
||||
requirements: github-copilot-sdk==0.1.25
|
||||
"""
|
||||
|
||||
@@ -17,11 +17,9 @@ import tempfile
|
||||
import asyncio
|
||||
import logging
|
||||
import shutil
|
||||
import subprocess
|
||||
import hashlib
|
||||
import aiohttp
|
||||
import contextlib
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union, AsyncGenerator, List, Any, Dict, Literal, Tuple
|
||||
from types import SimpleNamespace
|
||||
@@ -46,6 +44,7 @@ from open_webui.models.tools import Tools
|
||||
from open_webui.models.users import Users
|
||||
from open_webui.models.files import Files, FileForm
|
||||
from open_webui.config import UPLOAD_DIR, DATA_DIR
|
||||
from open_webui.storage.provider import Storage
|
||||
import mimetypes
|
||||
import uuid
|
||||
import shutil
|
||||
@@ -138,7 +137,8 @@ BASE_GUIDELINES = (
|
||||
" - **Philosophy**: Visual Artifacts (HTML/Mermaid) and Downloadable Files are **COMPLEMENTARY**. Always aim to provide BOTH: instant visual insight in the chat AND a persistent file for the user to keep.\n"
|
||||
" - **The Rule**: When the user needs to *possess* data (download/export), you MUST publish it. Creating a local file alone is useless because the user cannot access your container.\n"
|
||||
" - **Implicit Requests**: If asked to 'export', 'get link', or 'save', automatically trigger this sequence.\n"
|
||||
" - **Execution Sequence**: 1. **Write Local**: Create file in `.` (current directory). 2. **Publish**: Call `publish_file_from_workspace(filename='your_file.ext')`. 3. **Link**: Present the `download_url` as a Markdown link.\n"
|
||||
" - **Execution Sequence**: 1. **Write Local**: Create file in `.` (current directory). 2. **Publish**: Call `publish_file_from_workspace(filename='your_file.ext')`. 3. **Link**: Present the result based on file type. **For HTML files**: the tool returns both `download_url` (raw file) and `view_url` (`/content/html` format). You MUST present `view_url` as a **[Preview]** link that opens directly in the browser, AND `download_url` as a **[Download]** link. **For image files** (.png, .jpg, .gif, .svg, etc.): embed directly using `` — NEVER use a text link for images. For all other files: present `download_url` as a single download link.\n"
|
||||
" - **URL Format is STRICT**: File links MUST be relative paths starting with `/api/v1/files/` (for example: `/api/v1/files/{id}/content` or `/api/v1/files/{id}/content/html`). NEVER output `api/...` (missing leading slash). NEVER prepend any domain (such as `https://example.com/...`) even if a domain appears in conversation context.\n"
|
||||
" - **Bypass RAG**: This protocol automatically handles S3 storage and bypasses RAG, ensuring 100% accurate data delivery.\n"
|
||||
"6. **TODO Visibility**: Every time you call the `update_todo` tool, you **MUST** immediately follow up with a beautifully formatted **Markdown summary** of the current TODO list. Use task checkboxes (`- [ ]`), progress indicators, and clear headings so the user can see the status directly in the chat.\n"
|
||||
"7. **Python Execution Standard**: For ANY task requiring Python logic (not just data analysis), you **MUST NOT** embed multi-line code directly in a shell command (e.g., using `python -c` or `<< 'EOF'`).\n"
|
||||
@@ -197,10 +197,6 @@ class Pipe:
|
||||
default=True,
|
||||
description="Enable Direct MCP Client connection (Recommended).",
|
||||
)
|
||||
ENABLE_TOOL_CACHE: bool = Field(
|
||||
default=True,
|
||||
description="Cache OpenWebUI tools and MCP servers (performance optimization).",
|
||||
)
|
||||
REASONING_EFFORT: Literal["low", "medium", "high", "xhigh"] = Field(
|
||||
default="medium",
|
||||
description="Reasoning effort level (low, medium, high). Only affects standard Copilot models (not BYOK).",
|
||||
@@ -251,6 +247,10 @@ class Pipe:
|
||||
default="/app/backend/data/uploads",
|
||||
description="Path to OpenWebUI uploads directory (for file processing).",
|
||||
)
|
||||
MODEL_CACHE_TTL: int = Field(
|
||||
default=3600,
|
||||
description="Model list cache TTL in seconds. Set to 0 to disable cache (always fetch). Default: 3600 (1 hour).",
|
||||
)
|
||||
|
||||
BYOK_TYPE: Literal["openai", "anthropic"] = Field(
|
||||
default="openai",
|
||||
@@ -314,10 +314,6 @@ class Pipe:
|
||||
default=True,
|
||||
description="Enable dynamic MCP server loading (overrides global).",
|
||||
)
|
||||
ENABLE_TOOL_CACHE: bool = Field(
|
||||
default=True,
|
||||
description="Enable Tool/MCP configuration caching for this user.",
|
||||
)
|
||||
|
||||
# BYOK User Overrides
|
||||
BYOK_API_KEY: str = Field(
|
||||
@@ -352,8 +348,7 @@ class Pipe:
|
||||
_model_cache: List[dict] = [] # Model list cache
|
||||
_standard_model_ids: set = set() # Track standard model IDs
|
||||
_last_byok_config_hash: str = "" # Track BYOK config for cache invalidation
|
||||
_tool_cache = None # Cache for converted OpenWebUI tools
|
||||
_mcp_server_cache = None # Cache for MCP server config
|
||||
_last_model_cache_time: float = 0 # Timestamp of last model cache refresh
|
||||
_env_setup_done = False # Track if env setup has been completed
|
||||
_last_update_check = 0 # Timestamp of last CLI update check
|
||||
|
||||
@@ -717,46 +712,32 @@ class Pipe:
|
||||
uv = self._get_user_valves(__user__)
|
||||
enable_tools = uv.ENABLE_OPENWEBUI_TOOLS
|
||||
enable_openapi = uv.ENABLE_OPENAPI_SERVER
|
||||
enable_cache = uv.ENABLE_TOOL_CACHE
|
||||
|
||||
# 2. If all tool types are disabled, return empty immediately
|
||||
# 2. Publish tool is always injected, regardless of other settings
|
||||
chat_ctx = self._get_chat_context(body, __metadata__)
|
||||
chat_id = chat_ctx.get("chat_id")
|
||||
file_tool = self._get_publish_file_tool(__user__, chat_id, __request__)
|
||||
final_tools = [file_tool] if file_tool else []
|
||||
|
||||
# 3. If all OpenWebUI tool types are disabled, skip loading and return early
|
||||
if not enable_tools and not enable_openapi:
|
||||
return []
|
||||
return final_tools
|
||||
|
||||
# 3. Check Cache
|
||||
if enable_cache and self._tool_cache is not None:
|
||||
await self._emit_debug_log(
|
||||
"ℹ️ Using cached OpenWebUI tools.", __event_call__
|
||||
)
|
||||
# Create a shallow copy to append user-specific tools without polluting cache
|
||||
tools = list(self._tool_cache)
|
||||
# 4. Extract chat-level tool selection (P4: user selection from Chat UI)
|
||||
chat_tool_ids = None
|
||||
if __metadata__ and isinstance(__metadata__, dict):
|
||||
chat_tool_ids = __metadata__.get("tool_ids") or None
|
||||
|
||||
# Inject File Publish Tool
|
||||
chat_ctx = self._get_chat_context(body, __metadata__)
|
||||
chat_id = chat_ctx.get("chat_id")
|
||||
file_tool = self._get_publish_file_tool(__user__, chat_id, __request__)
|
||||
if file_tool:
|
||||
tools.append(file_tool)
|
||||
|
||||
return tools
|
||||
|
||||
# Load OpenWebUI tools dynamically
|
||||
# 5. Load OpenWebUI tools dynamically (always fresh, no cache)
|
||||
openwebui_tools = await self._load_openwebui_tools(
|
||||
body=body,
|
||||
__user__=__user__,
|
||||
__event_call__=__event_call__,
|
||||
enable_tools=enable_tools,
|
||||
enable_openapi=enable_openapi,
|
||||
chat_tool_ids=chat_tool_ids,
|
||||
)
|
||||
|
||||
# Update Cache
|
||||
if enable_cache:
|
||||
self._tool_cache = openwebui_tools
|
||||
await self._emit_debug_log(
|
||||
"✅ OpenWebUI tools cached for subsequent requests.", __event_call__
|
||||
)
|
||||
|
||||
# Log details only when cache is cold
|
||||
if openwebui_tools:
|
||||
tool_names = [t.name for t in openwebui_tools]
|
||||
await self._emit_debug_log(
|
||||
@@ -770,15 +751,7 @@ class Pipe:
|
||||
__event_call__,
|
||||
)
|
||||
|
||||
# Create a shallow copy to append user-specific tools without polluting cache
|
||||
final_tools = list(openwebui_tools)
|
||||
|
||||
# Inject File Publish Tool
|
||||
chat_ctx = self._get_chat_context(body, __metadata__)
|
||||
chat_id = chat_ctx.get("chat_id")
|
||||
file_tool = self._get_publish_file_tool(__user__, chat_id, __request__)
|
||||
if file_tool:
|
||||
final_tools.append(file_tool)
|
||||
final_tools.extend(openwebui_tools)
|
||||
|
||||
return final_tools
|
||||
|
||||
@@ -892,7 +865,9 @@ class Pipe:
|
||||
import aiohttp
|
||||
|
||||
base_url = str(__request__.base_url).rstrip("/")
|
||||
upload_url = f"{base_url}/api/v1/files/"
|
||||
# ?process=false skips RAG processing AND the
|
||||
# ALLOWED_FILE_EXTENSIONS restriction (which blocks html/htm)
|
||||
upload_url = f"{base_url}/api/v1/files/?process=false"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
with open(target_path, "rb") as f:
|
||||
@@ -925,14 +900,25 @@ class Pipe:
|
||||
except Exception as e:
|
||||
logger.error(f"API upload failed: {e}")
|
||||
|
||||
# 4. Fallback: Manual DB Insert (Local only)
|
||||
# 4. Fallback: Use Storage.upload_file directly (S3/Local/GCS/Azure compatible)
|
||||
if not api_success:
|
||||
file_id = str(uuid.uuid4())
|
||||
safe_filename = target_path.name
|
||||
dest_path = Path(UPLOAD_DIR) / f"{file_id}_{safe_filename}"
|
||||
await asyncio.to_thread(shutil.copy2, target_path, dest_path)
|
||||
storage_filename = f"{file_id}_{safe_filename}"
|
||||
|
||||
db_path = str(dest_path)
|
||||
def _upload_via_storage():
|
||||
with open(target_path, "rb") as f:
|
||||
_, stored_path = Storage.upload_file(
|
||||
f,
|
||||
storage_filename,
|
||||
{
|
||||
"OpenWebUI-User-Id": user_id,
|
||||
"OpenWebUI-File-Id": file_id,
|
||||
},
|
||||
)
|
||||
return stored_path
|
||||
|
||||
db_path = await asyncio.to_thread(_upload_via_storage)
|
||||
|
||||
file_form = FileForm(
|
||||
id=file_id,
|
||||
@@ -943,7 +929,7 @@ class Pipe:
|
||||
"name": safe_filename,
|
||||
"content_type": mimetypes.guess_type(safe_filename)[0]
|
||||
or "text/plain",
|
||||
"size": os.path.getsize(dest_path),
|
||||
"size": os.path.getsize(target_path),
|
||||
"source": "copilot_workspace_publish",
|
||||
"skip_rag": True,
|
||||
},
|
||||
@@ -952,16 +938,16 @@ class Pipe:
|
||||
|
||||
# 5. Result
|
||||
download_url = f"/api/v1/files/{file_id}/content"
|
||||
view_url = download_url
|
||||
is_html = safe_filename.lower().endswith(".html")
|
||||
is_html = safe_filename.lower().endswith((".html", ".htm"))
|
||||
|
||||
# For HTML files, if user is admin, provide a direct view link (/content/html)
|
||||
if is_html and is_admin:
|
||||
# For HTML files, provide a direct view link (/content/html) for browser preview
|
||||
view_url = None
|
||||
if is_html:
|
||||
view_url = f"{download_url}/html"
|
||||
|
||||
# Localized output
|
||||
msg = self._get_translation(user_lang, "publish_success")
|
||||
if is_html and is_admin:
|
||||
if is_html:
|
||||
hint = self._get_translation(
|
||||
user_lang,
|
||||
"publish_hint_html",
|
||||
@@ -977,13 +963,16 @@ class Pipe:
|
||||
download_url=download_url,
|
||||
)
|
||||
|
||||
return {
|
||||
result = {
|
||||
"file_id": file_id,
|
||||
"filename": safe_filename,
|
||||
"download_url": download_url,
|
||||
"message": msg,
|
||||
"hint": hint,
|
||||
}
|
||||
if is_html and view_url:
|
||||
result["view_url"] = view_url
|
||||
return result
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
@@ -1206,6 +1195,31 @@ class Pipe:
|
||||
params_type=ParamsModel,
|
||||
)(_tool)
|
||||
|
||||
def _read_tool_server_connections(self) -> list:
|
||||
"""
|
||||
Read tool server connections directly from the database to avoid stale
|
||||
in-memory state in multi-worker deployments.
|
||||
Falls back to the in-memory PersistentConfig value if DB read fails.
|
||||
"""
|
||||
try:
|
||||
from open_webui.config import get_config
|
||||
|
||||
config_data = get_config()
|
||||
connections = config_data.get("tool_server", {}).get("connections", None)
|
||||
if connections is not None:
|
||||
return connections if isinstance(connections, list) else []
|
||||
except Exception as e:
|
||||
logger.debug(
|
||||
f"[Tools] DB config read failed, using in-memory fallback: {e}"
|
||||
)
|
||||
|
||||
# Fallback: in-memory value (may be stale in multi-worker)
|
||||
if hasattr(TOOL_SERVER_CONNECTIONS, "value") and isinstance(
|
||||
TOOL_SERVER_CONNECTIONS.value, list
|
||||
):
|
||||
return TOOL_SERVER_CONNECTIONS.value
|
||||
return []
|
||||
|
||||
def _build_openwebui_request(self, user: dict = None, token: str = None):
|
||||
"""Build a more complete request-like object with dynamically loaded OpenWebUI configs."""
|
||||
# Dynamically build config from the official registry
|
||||
@@ -1219,12 +1233,9 @@ class Pipe:
|
||||
setattr(config, item.env_name, val)
|
||||
|
||||
# Critical Fix: Explicitly sync TOOL_SERVER_CONNECTIONS to ensure OpenAPI tools work
|
||||
# PERSISTENT_CONFIG_REGISTRY might not contain TOOL_SERVER_CONNECTIONS in all versions
|
||||
if not hasattr(config, "TOOL_SERVER_CONNECTIONS"):
|
||||
if hasattr(TOOL_SERVER_CONNECTIONS, "value"):
|
||||
config.TOOL_SERVER_CONNECTIONS = TOOL_SERVER_CONNECTIONS.value
|
||||
else:
|
||||
config.TOOL_SERVER_CONNECTIONS = TOOL_SERVER_CONNECTIONS
|
||||
# Read directly from DB to avoid stale in-memory state in multi-worker deployments
|
||||
fresh_connections = self._read_tool_server_connections()
|
||||
config.TOOL_SERVER_CONNECTIONS = fresh_connections
|
||||
|
||||
app_state = SimpleNamespace(
|
||||
config=config,
|
||||
@@ -1282,6 +1293,7 @@ class Pipe:
|
||||
__event_call__=None,
|
||||
enable_tools: bool = True,
|
||||
enable_openapi: bool = True,
|
||||
chat_tool_ids: Optional[list] = None,
|
||||
):
|
||||
"""Load OpenWebUI tools and convert them to Copilot SDK tools."""
|
||||
if isinstance(__user__, (list, tuple)):
|
||||
@@ -1300,17 +1312,20 @@ class Pipe:
|
||||
|
||||
# --- PROBE LOG ---
|
||||
if __event_call__:
|
||||
conn_status = "Missing"
|
||||
if hasattr(TOOL_SERVER_CONNECTIONS, "value"):
|
||||
val = TOOL_SERVER_CONNECTIONS.value
|
||||
conn_status = (
|
||||
f"List({len(val)})" if isinstance(val, list) else str(type(val))
|
||||
)
|
||||
conn_list = self._read_tool_server_connections()
|
||||
conn_summary = []
|
||||
for i, s in enumerate(conn_list):
|
||||
if isinstance(s, dict):
|
||||
s_id = s.get("info", {}).get("id") or s.get("id") or str(i)
|
||||
s_type = s.get("type", "openapi")
|
||||
s_enabled = s.get("config", {}).get("enable", False)
|
||||
conn_summary.append(
|
||||
{"id": s_id, "type": s_type, "enable": s_enabled}
|
||||
)
|
||||
|
||||
await self._emit_debug_log(
|
||||
f"[Tools Debug] Entry. UserID: {user_id}, EnableTools: {enable_tools}, EnableOpenAPI: {enable_openapi}, Connections: {conn_status}",
|
||||
f"[Tools] TOOL_SERVER_CONNECTIONS ({len(conn_summary)} entries): {conn_summary}",
|
||||
__event_call__,
|
||||
debug_enabled=True,
|
||||
)
|
||||
# -----------------
|
||||
|
||||
@@ -1327,67 +1342,85 @@ class Pipe:
|
||||
|
||||
# 2. Get OpenAPI Tool Server tools
|
||||
if enable_openapi:
|
||||
if hasattr(TOOL_SERVER_CONNECTIONS, "value"):
|
||||
raw_connections = TOOL_SERVER_CONNECTIONS.value
|
||||
raw_connections = self._read_tool_server_connections()
|
||||
|
||||
# Handle Pydantic model vs List vs Dict
|
||||
connections = []
|
||||
if isinstance(raw_connections, list):
|
||||
connections = raw_connections
|
||||
elif hasattr(raw_connections, "dict"):
|
||||
connections = raw_connections.dict()
|
||||
elif hasattr(raw_connections, "model_dump"):
|
||||
connections = raw_connections.model_dump()
|
||||
# Handle Pydantic model vs List vs Dict
|
||||
connections = []
|
||||
if isinstance(raw_connections, list):
|
||||
connections = raw_connections
|
||||
elif hasattr(raw_connections, "dict"):
|
||||
connections = raw_connections.dict()
|
||||
elif hasattr(raw_connections, "model_dump"):
|
||||
connections = raw_connections.model_dump()
|
||||
|
||||
# Debug logging for connections
|
||||
if self.valves.DEBUG:
|
||||
await self._emit_debug_log(
|
||||
f"[Tools] Found {len(connections)} server connections (Type: {type(raw_connections)})",
|
||||
__event_call__,
|
||||
)
|
||||
|
||||
for idx, server in enumerate(connections):
|
||||
# Handle server item type
|
||||
s_type = (
|
||||
server.get("type", "openapi")
|
||||
if isinstance(server, dict)
|
||||
else getattr(server, "type", "openapi")
|
||||
)
|
||||
|
||||
# P2: config.enable check — skip admin-disabled servers
|
||||
s_config = (
|
||||
server.get("config", {})
|
||||
if isinstance(server, dict)
|
||||
else getattr(server, "config", {})
|
||||
)
|
||||
s_enabled = (
|
||||
s_config.get("enable", False)
|
||||
if isinstance(s_config, dict)
|
||||
else getattr(s_config, "enable", False)
|
||||
)
|
||||
if not s_enabled:
|
||||
if self.valves.DEBUG:
|
||||
await self._emit_debug_log(
|
||||
f"[Tools] Skipped disabled server at index {idx}",
|
||||
__event_call__,
|
||||
)
|
||||
continue
|
||||
|
||||
# Handle server ID: Priority info.id > server.id > index
|
||||
s_id = None
|
||||
if isinstance(server, dict):
|
||||
info = server.get("info", {})
|
||||
s_id = info.get("id") or server.get("id")
|
||||
else:
|
||||
info = getattr(server, "info", {})
|
||||
if isinstance(info, dict):
|
||||
s_id = info.get("id")
|
||||
else:
|
||||
s_id = getattr(info, "id", None)
|
||||
|
||||
if not s_id:
|
||||
s_id = getattr(server, "id", None)
|
||||
|
||||
if not s_id:
|
||||
s_id = str(idx)
|
||||
|
||||
# Debug logging for connections
|
||||
if self.valves.DEBUG:
|
||||
await self._emit_debug_log(
|
||||
f"[Tools] Found {len(connections)} server connections (Type: {type(raw_connections)})",
|
||||
f"[Tools] Checking Server: ID={s_id}, Type={s_type}",
|
||||
__event_call__,
|
||||
)
|
||||
|
||||
for idx, server in enumerate(connections):
|
||||
# Handle server item type
|
||||
s_type = (
|
||||
server.get("type", "openapi")
|
||||
if isinstance(server, dict)
|
||||
else getattr(server, "type", "openapi")
|
||||
if s_type == "openapi":
|
||||
# Ensure we don't add empty IDs, though fallback to idx should prevent this
|
||||
if s_id:
|
||||
tool_ids.append(f"server:{s_id}")
|
||||
elif self.valves.DEBUG:
|
||||
await self._emit_debug_log(
|
||||
f"[Tools] Skipped non-OpenAPI server: {s_id} ({s_type})",
|
||||
__event_call__,
|
||||
)
|
||||
|
||||
# Handle server ID: Priority info.id > server.id > index
|
||||
s_id = None
|
||||
if isinstance(server, dict):
|
||||
info = server.get("info", {})
|
||||
s_id = info.get("id") or server.get("id")
|
||||
else:
|
||||
info = getattr(server, "info", {})
|
||||
if isinstance(info, dict):
|
||||
s_id = info.get("id")
|
||||
else:
|
||||
s_id = getattr(info, "id", None)
|
||||
|
||||
if not s_id:
|
||||
s_id = getattr(server, "id", None)
|
||||
|
||||
if not s_id:
|
||||
s_id = str(idx)
|
||||
|
||||
if self.valves.DEBUG:
|
||||
await self._emit_debug_log(
|
||||
f"[Tools] Checking Server: ID={s_id}, Type={s_type}",
|
||||
__event_call__,
|
||||
)
|
||||
|
||||
if s_type == "openapi":
|
||||
# Ensure we don't add empty IDs, though fallback to idx should prevent this
|
||||
if s_id:
|
||||
tool_ids.append(f"server:{s_id}")
|
||||
elif self.valves.DEBUG:
|
||||
await self._emit_debug_log(
|
||||
f"[Tools] Skipped non-OpenAPI server: {s_id} ({s_type})",
|
||||
__event_call__,
|
||||
)
|
||||
|
||||
if (
|
||||
not tool_ids and not enable_tools
|
||||
): # No IDs and no built-ins either if tools disabled
|
||||
@@ -1397,6 +1430,16 @@ class Pipe:
|
||||
)
|
||||
return []
|
||||
|
||||
# P4: Chat tool_ids whitelist — only active when user explicitly selected tools
|
||||
if chat_tool_ids:
|
||||
chat_tool_ids_set = set(chat_tool_ids)
|
||||
filtered = [tid for tid in tool_ids if tid in chat_tool_ids_set]
|
||||
await self._emit_debug_log(
|
||||
f"[Tools] tool_ids whitelist active: {len(tool_ids)} → {len(filtered)} (selected: {chat_tool_ids})",
|
||||
__event_call__,
|
||||
)
|
||||
tool_ids = filtered
|
||||
|
||||
if self.valves.DEBUG and tool_ids:
|
||||
await self._emit_debug_log(
|
||||
f"[Tools] Requesting tool IDs: {tool_ids}", __event_call__
|
||||
@@ -1469,7 +1512,28 @@ class Pipe:
|
||||
# Fetch Built-in Tools (Web Search, Memory, etc.)
|
||||
if enable_tools:
|
||||
try:
|
||||
# Resolve real model dict from DB to respect meta.builtinTools config
|
||||
model_dict = {}
|
||||
model_id = body.get("model", "") if isinstance(body, dict) else ""
|
||||
if model_id:
|
||||
try:
|
||||
from open_webui.models.models import Models as _Models
|
||||
|
||||
model_record = _Models.get_model_by_id(model_id)
|
||||
if model_record:
|
||||
model_dict = {"info": model_record.model_dump()}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Get builtin tools
|
||||
# Open all feature gates so filtering is driven solely by
|
||||
# model.meta.builtinTools (defaults to all-enabled when absent).
|
||||
all_features = {
|
||||
"memory": True,
|
||||
"web_search": True,
|
||||
"image_generation": True,
|
||||
"code_interpreter": True,
|
||||
}
|
||||
builtin_tools = get_builtin_tools(
|
||||
self._build_openwebui_request(user_data),
|
||||
{
|
||||
@@ -1477,16 +1541,8 @@ class Pipe:
|
||||
"__chat_id__": extra_params.get("__chat_id__"),
|
||||
"__message_id__": extra_params.get("__message_id__"),
|
||||
},
|
||||
model={
|
||||
"info": {
|
||||
"meta": {
|
||||
"capabilities": {
|
||||
"web_search": True,
|
||||
"image_generation": True,
|
||||
}
|
||||
}
|
||||
}
|
||||
}, # Mock capabilities to allow all globally enabled tools
|
||||
features=all_features,
|
||||
model=model_dict, # model.meta.builtinTools controls which categories are active
|
||||
)
|
||||
if builtin_tools:
|
||||
tools_dict.update(builtin_tools)
|
||||
@@ -1504,16 +1560,15 @@ class Pipe:
|
||||
tool_metadata_cache = {}
|
||||
server_metadata_cache = {}
|
||||
|
||||
# Pre-build server metadata cache from TOOL_SERVER_CONNECTIONS
|
||||
if hasattr(TOOL_SERVER_CONNECTIONS, "value"):
|
||||
for server in TOOL_SERVER_CONNECTIONS.value:
|
||||
server_id = server.get("id") or server.get("info", {}).get("id")
|
||||
if server_id:
|
||||
info = server.get("info", {})
|
||||
server_metadata_cache[server_id] = {
|
||||
"name": info.get("name") or server_id,
|
||||
"description": info.get("description", ""),
|
||||
}
|
||||
# Pre-build server metadata cache from DB-fresh tool server connections
|
||||
for server in self._read_tool_server_connections():
|
||||
server_id = server.get("id") or server.get("info", {}).get("id")
|
||||
if server_id:
|
||||
info = server.get("info", {})
|
||||
server_metadata_cache[server_id] = {
|
||||
"name": info.get("name") or server_id,
|
||||
"description": info.get("description", ""),
|
||||
}
|
||||
|
||||
for tool_name, tool_def in tools_dict.items():
|
||||
tool_id = tool_def.get("tool_id", "")
|
||||
@@ -1578,7 +1633,10 @@ class Pipe:
|
||||
return converted_tools
|
||||
|
||||
def _parse_mcp_servers(
|
||||
self, __event_call__=None, enable_mcp: bool = True, enable_cache: bool = True
|
||||
self,
|
||||
__event_call__=None,
|
||||
enable_mcp: bool = True,
|
||||
chat_tool_ids: Optional[list] = None,
|
||||
) -> Optional[dict]:
|
||||
"""
|
||||
Dynamically load MCP servers from OpenWebUI TOOL_SERVER_CONNECTIONS.
|
||||
@@ -1587,17 +1645,22 @@ class Pipe:
|
||||
if not enable_mcp:
|
||||
return None
|
||||
|
||||
# Check Cache
|
||||
if enable_cache and self._mcp_server_cache is not None:
|
||||
return self._mcp_server_cache
|
||||
|
||||
mcp_servers = {}
|
||||
|
||||
# Iterate over OpenWebUI Tool Server Connections
|
||||
if hasattr(TOOL_SERVER_CONNECTIONS, "value"):
|
||||
connections = TOOL_SERVER_CONNECTIONS.value
|
||||
else:
|
||||
connections = []
|
||||
# Read MCP servers directly from DB to avoid stale in-memory cache
|
||||
connections = self._read_tool_server_connections()
|
||||
|
||||
if __event_call__:
|
||||
mcp_summary = []
|
||||
for s in connections if isinstance(connections, list) else []:
|
||||
if isinstance(s, dict) and s.get("type") == "mcp":
|
||||
s_id = s.get("info", {}).get("id") or s.get("id", "?")
|
||||
s_enabled = s.get("config", {}).get("enable", False)
|
||||
mcp_summary.append({"id": s_id, "enable": s_enabled})
|
||||
self._emit_debug_log_sync(
|
||||
f"[MCP] TOOL_SERVER_CONNECTIONS MCP entries ({len(mcp_summary)}): {mcp_summary}",
|
||||
__event_call__,
|
||||
)
|
||||
|
||||
for conn in connections:
|
||||
if conn.get("type") == "mcp":
|
||||
@@ -1605,6 +1668,18 @@ class Pipe:
|
||||
# Use ID from info or generate one
|
||||
raw_id = info.get("id", f"mcp-server-{len(mcp_servers)}")
|
||||
|
||||
# P2: config.enable check — skip admin-disabled servers
|
||||
mcp_config = conn.get("config", {})
|
||||
if not mcp_config.get("enable", False):
|
||||
self._emit_debug_log_sync(
|
||||
f"[MCP] Skipped disabled server: {raw_id}", __event_call__
|
||||
)
|
||||
continue
|
||||
|
||||
# P4: chat_tool_ids whitelist — if user selected tools, only include matching servers
|
||||
if chat_tool_ids and f"server:{raw_id}" not in chat_tool_ids:
|
||||
continue
|
||||
|
||||
# Sanitize server_id (using same logic as tools)
|
||||
server_id = re.sub(r"[^a-zA-Z0-9_-]", "_", raw_id)
|
||||
if not server_id or re.match(r"^[_.-]+$", server_id):
|
||||
@@ -1636,7 +1711,6 @@ class Pipe:
|
||||
headers.update(custom_headers)
|
||||
|
||||
# Get filtering configuration
|
||||
mcp_config = conn.get("config", {})
|
||||
function_filter = mcp_config.get("function_name_filter_list", "")
|
||||
|
||||
allowed_tools = ["*"]
|
||||
@@ -1658,10 +1732,6 @@ class Pipe:
|
||||
f"🔌 MCP Integrated: {server_id}", __event_call__
|
||||
)
|
||||
|
||||
# Update Cache
|
||||
if self.valves.ENABLE_TOOL_CACHE:
|
||||
self._mcp_server_cache = mcp_servers
|
||||
|
||||
return mcp_servers if mcp_servers else None
|
||||
|
||||
async def _emit_debug_log(
|
||||
@@ -1899,6 +1969,22 @@ class Pipe:
|
||||
)
|
||||
break
|
||||
|
||||
# Append Code Interpreter Warning
|
||||
code_interpreter_warning = (
|
||||
"\n\n[System Note]\n"
|
||||
"The `execute_code` tool (builtin category: `code_interpreter`) executes code in a remote, ephemeral environment. "
|
||||
"It cannot access files in your local workspace or persist changes. "
|
||||
"Use it only for calculation or logic verification, not for file manipulation."
|
||||
"\n"
|
||||
"For links returned by `publish_file_from_workspace`, URL formatting is strict: "
|
||||
"always use relative paths that start with `/api/v1/files/`. "
|
||||
"Do not output `api/...` and do not prepend any domain."
|
||||
)
|
||||
if system_prompt_content:
|
||||
system_prompt_content += code_interpreter_warning
|
||||
else:
|
||||
system_prompt_content = code_interpreter_warning.strip()
|
||||
|
||||
return system_prompt_content, system_prompt_source
|
||||
|
||||
def _get_workspace_dir(self, user_id: str = None, chat_id: str = None) -> str:
|
||||
@@ -1969,7 +2055,7 @@ class Pipe:
|
||||
is_admin: bool = False,
|
||||
user_id: str = None,
|
||||
enable_mcp: bool = True,
|
||||
enable_cache: bool = True,
|
||||
chat_tool_ids: Optional[list] = None,
|
||||
__event_call__=None,
|
||||
):
|
||||
"""Build SessionConfig for Copilot SDK."""
|
||||
@@ -2018,7 +2104,7 @@ class Pipe:
|
||||
}
|
||||
|
||||
mcp_servers = self._parse_mcp_servers(
|
||||
__event_call__, enable_mcp=enable_mcp, enable_cache=enable_cache
|
||||
__event_call__, enable_mcp=enable_mcp, chat_tool_ids=chat_tool_ids
|
||||
)
|
||||
|
||||
# Prepare session config parameters
|
||||
@@ -2061,12 +2147,12 @@ class Pipe:
|
||||
|
||||
if mcp_servers:
|
||||
session_params["mcp_servers"] = mcp_servers
|
||||
# Critical Fix: When using MCP, available_tools must be None to allow dynamic discovery
|
||||
session_params["available_tools"] = None
|
||||
else:
|
||||
session_params["available_tools"] = (
|
||||
[t.name for t in custom_tools] if custom_tools else None
|
||||
)
|
||||
|
||||
# Always set available_tools=None so the Copilot CLI's built-in tools
|
||||
# (e.g. bash, create_file) remain accessible alongside our custom tools.
|
||||
# Custom tools are registered via the 'tools' param; whitelist filtering
|
||||
# via available_tools would silently block CLI built-ins.
|
||||
session_params["available_tools"] = None
|
||||
|
||||
if provider_config:
|
||||
session_params["provider"] = provider_config
|
||||
@@ -2338,7 +2424,7 @@ class Pipe:
|
||||
|
||||
# 1. Environment Setup (Only if needed or not done)
|
||||
if needs_setup:
|
||||
self._setup_env(token=token, skip_cli_install=True)
|
||||
self._setup_env(token=token)
|
||||
self.__class__._last_update_check = now
|
||||
else:
|
||||
# Still inject token for BYOK real-time updates
|
||||
@@ -2380,6 +2466,19 @@ class Pipe:
|
||||
current_config_str = f"{token}|{uv.BYOK_BASE_URL or self.valves.BYOK_BASE_URL}|{uv.BYOK_API_KEY or self.valves.BYOK_API_KEY}|{self.valves.BYOK_BEARER_TOKEN}"
|
||||
current_config_hash = hashlib.md5(current_config_str.encode()).hexdigest()
|
||||
|
||||
# TTL-based cache expiry
|
||||
cache_ttl = self.valves.MODEL_CACHE_TTL
|
||||
if (
|
||||
self._model_cache
|
||||
and cache_ttl > 0
|
||||
and (now - self.__class__._last_model_cache_time) > cache_ttl
|
||||
):
|
||||
if self.valves.DEBUG:
|
||||
logger.info(
|
||||
f"[Pipes] Model cache expired (TTL={cache_ttl}s). Invalidating."
|
||||
)
|
||||
self.__class__._model_cache = []
|
||||
|
||||
if (
|
||||
self._model_cache
|
||||
and self.__class__._last_byok_config_hash != current_config_hash
|
||||
@@ -2397,8 +2496,12 @@ class Pipe:
|
||||
if self.valves.DEBUG:
|
||||
logger.info("[Pipes] Refreshing model cache...")
|
||||
try:
|
||||
# Use effective token for fetching
|
||||
self._setup_env(token=token, skip_cli_install=True)
|
||||
# Use effective token for fetching.
|
||||
# If COPILOT_CLI_PATH is missing (e.g. env cleared after worker restart),
|
||||
# force a full re-discovery by resetting _env_setup_done first.
|
||||
if not os.environ.get("COPILOT_CLI_PATH"):
|
||||
self.__class__._env_setup_done = False
|
||||
self._setup_env(token=token)
|
||||
|
||||
# Fetch BYOK models if configured
|
||||
byok = []
|
||||
@@ -2478,7 +2581,24 @@ class Pipe:
|
||||
)
|
||||
|
||||
self._model_cache = standard + byok
|
||||
self.__class__._last_model_cache_time = now
|
||||
if not self._model_cache:
|
||||
has_byok = bool(
|
||||
(uv.BYOK_BASE_URL or self.valves.BYOK_BASE_URL)
|
||||
and (
|
||||
uv.BYOK_API_KEY
|
||||
or self.valves.BYOK_API_KEY
|
||||
or uv.BYOK_BEARER_TOKEN
|
||||
or self.valves.BYOK_BEARER_TOKEN
|
||||
)
|
||||
)
|
||||
if not token and not has_byok:
|
||||
return [
|
||||
{
|
||||
"id": "no_token",
|
||||
"name": "⚠️ No credentials configured. Please set GH_TOKEN or BYOK settings in Valves.",
|
||||
}
|
||||
]
|
||||
return [
|
||||
{
|
||||
"id": "warming_up",
|
||||
@@ -2530,8 +2650,6 @@ class Pipe:
|
||||
debug_enabled: bool = False,
|
||||
token: str = None,
|
||||
enable_mcp: bool = True,
|
||||
enable_cache: bool = True,
|
||||
skip_cli_install: bool = False, # Kept for call-site compatibility, no longer used
|
||||
__event_emitter__=None,
|
||||
user_lang: str = "en-US",
|
||||
):
|
||||
@@ -2548,7 +2666,6 @@ class Pipe:
|
||||
__event_call__,
|
||||
debug_enabled,
|
||||
enable_mcp=enable_mcp,
|
||||
enable_cache=enable_cache,
|
||||
)
|
||||
return
|
||||
|
||||
@@ -2762,7 +2879,6 @@ class Pipe:
|
||||
__event_call__=None,
|
||||
debug_enabled: bool = False,
|
||||
enable_mcp: bool = True,
|
||||
enable_cache: bool = True,
|
||||
):
|
||||
"""Sync MCP configuration to ~/.copilot/config.json."""
|
||||
path = os.path.expanduser("~/.copilot/config.json")
|
||||
@@ -2786,9 +2902,7 @@ class Pipe:
|
||||
pass
|
||||
return
|
||||
|
||||
mcp = self._parse_mcp_servers(
|
||||
__event_call__, enable_mcp=enable_mcp, enable_cache=enable_cache
|
||||
)
|
||||
mcp = self._parse_mcp_servers(__event_call__, enable_mcp=enable_mcp)
|
||||
if not mcp:
|
||||
return
|
||||
try:
|
||||
@@ -2866,9 +2980,13 @@ class Pipe:
|
||||
)
|
||||
chat_id = chat_ctx.get("chat_id") or "default"
|
||||
|
||||
# Determine effective MCP and cache settings
|
||||
# Determine effective MCP settings
|
||||
effective_mcp = user_valves.ENABLE_MCP_SERVER
|
||||
effective_cache = user_valves.ENABLE_TOOL_CACHE
|
||||
|
||||
# P4: Chat tool_ids whitelist — extract once, reuse for both OpenAPI and MCP
|
||||
chat_tool_ids = None
|
||||
if __metadata__ and isinstance(__metadata__, dict):
|
||||
chat_tool_ids = __metadata__.get("tool_ids") or None
|
||||
|
||||
user_ctx = await self._get_user_context(__user__, __event_call__, __request__)
|
||||
user_lang = user_ctx["user_language"]
|
||||
@@ -2879,7 +2997,6 @@ class Pipe:
|
||||
debug_enabled=effective_debug,
|
||||
token=effective_token,
|
||||
enable_mcp=effective_mcp,
|
||||
enable_cache=effective_cache,
|
||||
__event_emitter__=__event_emitter__,
|
||||
user_lang=user_lang,
|
||||
)
|
||||
@@ -3125,7 +3242,7 @@ class Pipe:
|
||||
|
||||
# Check MCP Servers
|
||||
mcp_servers = self._parse_mcp_servers(
|
||||
__event_call__, enable_mcp=effective_mcp, enable_cache=effective_cache
|
||||
__event_call__, enable_mcp=effective_mcp, chat_tool_ids=chat_tool_ids
|
||||
)
|
||||
mcp_server_names = list(mcp_servers.keys()) if mcp_servers else []
|
||||
if mcp_server_names:
|
||||
@@ -3180,15 +3297,13 @@ class Pipe:
|
||||
mcp_servers = self._parse_mcp_servers(
|
||||
__event_call__,
|
||||
enable_mcp=effective_mcp,
|
||||
enable_cache=effective_cache,
|
||||
chat_tool_ids=chat_tool_ids,
|
||||
)
|
||||
if mcp_servers:
|
||||
resume_params["mcp_servers"] = mcp_servers
|
||||
resume_params["available_tools"] = None
|
||||
else:
|
||||
resume_params["available_tools"] = (
|
||||
[t.name for t in custom_tools] if custom_tools else None
|
||||
)
|
||||
|
||||
# Always None: let CLI built-ins (bash etc.) remain available.
|
||||
resume_params["available_tools"] = None
|
||||
|
||||
# Always inject the latest system prompt in 'replace' mode
|
||||
# This handles both custom models and user-defined system messages
|
||||
@@ -3274,7 +3389,7 @@ class Pipe:
|
||||
is_admin=is_admin,
|
||||
user_id=user_id,
|
||||
enable_mcp=effective_mcp,
|
||||
enable_cache=effective_cache,
|
||||
chat_tool_ids=chat_tool_ids,
|
||||
__event_call__=__event_call__,
|
||||
)
|
||||
|
||||
|
||||
127
plugins/pipes/github-copilot-sdk/v0.8.0.md
Normal file
127
plugins/pipes/github-copilot-sdk/v0.8.0.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# 🚀 GitHub Copilot SDK Pipe v0.8.0: Conditional Tool Filtering & Publish Reliability 🎛️
|
||||
|
||||
**GitHub Copilot SDK Pipe v0.8.0** — A major control and reliability upgrade. This release introduces a four-priority tool permission system that makes tool access fully configurable per conversation, fixes file publishing across all storage backends, and ensures CLI built-in tools are never accidentally silenced.
|
||||
|
||||
---
|
||||
|
||||
## 📦 Quick Installation
|
||||
|
||||
- **GitHub Copilot SDK (Pipe)**: [Install v0.8.0](https://openwebui.com/posts/ce96f7b4-12fc-4ac3-9a01-875713e69359)
|
||||
- **GitHub Copilot SDK (Filter)**: [Install v0.1.3](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 What's New in v0.8.0
|
||||
|
||||
### 1. Conditional Tool Filtering (P1~P4) — The Headline Feature
|
||||
|
||||
This release introduces a **four-priority tool permission system** that gives you precise control over which tools are active in each conversation.
|
||||
|
||||
| Priority | Scope | Mechanism |
|
||||
| :--- | :--- | :--- |
|
||||
| **P1** | Global | Pipe-level Valve switches (`ENABLE_OPENWEBUI_TOOLS`, `ENABLE_OPENAPI_SERVER`, `ENABLE_MCP`) |
|
||||
| **P2** | Server-level | Admin `config.enable` — disable a tool server from the OpenWebUI admin panel without changing code |
|
||||
| **P3** | User-level | Per-user `UserValves` overrides |
|
||||
| **P4** | Conversation-level | **Chat UI tool selection** — only tools the user explicitly checks in the chat input are activated |
|
||||
|
||||
**How P4 works**: OpenWebUI passes the user's currently selected tools as `tool_ids` in `__metadata__`. The plugin reads this whitelist and filters both **OpenWebUI tools** and **MCP servers** accordingly.
|
||||
|
||||
- **Default ON (No Selection)**: If you **do not select any tools** (leave the checkbox empty), the plugin considers all enabled tools active (provided the P1 global Valve is enabled). OpenWebUI tools, OpenAPI Servers, and MCP Servers are **all mounted by default**.
|
||||
- **Whitelist Mode (Selection Active)**: Once you explicitly check at least one tool (e.g., only "web search"), **only your selected tools are activated**, and all other tools (including MCP servers) are filtered out.
|
||||
|
||||
```
|
||||
User checks ✅ web-search, ✅ code-runner in Chat UI
|
||||
↓
|
||||
chat_tool_ids = ["web-search", "server:code-runner"]
|
||||
↓
|
||||
filtered tools = only the two checked tools
|
||||
↓
|
||||
MCP servers not in the list are skipped entirely
|
||||
```
|
||||
|
||||
**P2 admin control**: Each tool server entry in OpenWebUI now has a `config.enable` field. Setting it to `false` from the admin panel disables that server globally — no Valve edits required.
|
||||
|
||||
### 2. File Publish Reliability — All Storage Backends Fixed
|
||||
|
||||
The persistent `{"detail":"[ERROR: Error getting file content]"}` error when agents publish files has been fully resolved.
|
||||
|
||||
| Root Cause | Fix |
|
||||
| :--- | :--- |
|
||||
| Fallback path used `shutil.copy2` + manual DB write, which stored a local absolute path that S3-backed deployments cannot resolve | Fallback now calls `Storage.upload_file()` directly, auto-adapting to local/S3/GCS/Azure |
|
||||
| HTML files were blocked by OpenWebUI's `ALLOWED_FILE_EXTENSIONS` check | Upload URL always includes `?process=false`, bypassing the content-type filter |
|
||||
|
||||
When the published artifact is an HTML file, the plugin also returns a direct-access HTML link for immediate opening/preview in chat.
|
||||
|
||||
All published links now follow a strict format: they must be relative paths beginning with `/api/v1/files/`.
|
||||
`api/...` (without leading slash) and domain-prefixed absolute URLs are treated as invalid.
|
||||
|
||||
### 3. CLI Built-in Tools Always Available
|
||||
|
||||
`available_tools` is now always `None` (instead of filtering by loaded tool IDs), which means Copilot CLI built-in tools — such as `bash`, `create_file`, `read_file`, `list_directory` — are **always available by default**.
|
||||
|
||||
Previously, when no server tools were configured/loaded, the CLI could receive `available_tools=[]` and silently block all built-ins.
|
||||
|
||||
### 4. Publish Tool Always Injected
|
||||
|
||||
`publish_file_from_workspace` is injected into the tool list before the `ENABLE_OPENWEBUI_TOOLS` guard, so it is **never lost** even when all OpenWebUI tool loading is disabled via Valves.
|
||||
|
||||
### 5. Code Interpreter Warning (Not Disabled)
|
||||
|
||||
The built-in `code_interpreter` tool is **enabled** but operates in a remote, ephemeral sandbox.
|
||||
|
||||
A system prompt warning is now automatically injected to clarify its limitations:
|
||||
> "The `code_interpreter` tool executes code in a remote, ephemeral environment. It cannot access files in your local workspace or persist changes."
|
||||
|
||||
### 6. Bug Fixes Summary
|
||||
|
||||
- **File publish backend mismatch**: Fixed object-storage publish failures (`Error getting file content`) caused by local-path fallback writes.
|
||||
- **HTML artifact extension rejection**: Fixed HTML uploads being blocked by OpenWebUI extension processing by forcing `?process=false`.
|
||||
- **Invalid artifact URL formats**: Fixed links occasionally generated as `api/...` or domain-prefixed URLs; output is now constrained to `/api/v1/files/...` relative paths.
|
||||
- **CLI built-ins silently blocked**: Fixed built-ins becoming unavailable when no server tools were configured/loaded (which resulted in `available_tools=[]`); now default remains `None`.
|
||||
- **Publish tool injection loss**: Fixed `publish_file_from_workspace` being omitted when `ENABLE_OPENWEBUI_TOOLS=False`.
|
||||
|
||||
### 7. Companion Files Filter Included in Pipe Guidance
|
||||
|
||||
The release documentation now embeds the key points of `GitHub Copilot SDK Files Filter` so users can configure the full workflow from a single page:
|
||||
|
||||
- It prevents default RAG pre-processing from consuming uploaded files before Pipe processing.
|
||||
- It preserves raw binary access by moving uploads into `copilot_files`.
|
||||
- v0.1.3 improvements included in guidance: BYOK model-id prefix matching + optional dual-channel debug logs.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Key Capabilities
|
||||
|
||||
| Feature | Description |
|
||||
| :--- | :--- |
|
||||
| **Conditional Tool Filtering (P1~P4)** | Multi-priority permission system: global Valves → admin config.enable → user valves → Chat UI selection |
|
||||
| **Universal Tool Protocol** | Native support for **MCP**, **OpenAPI**, and **OpenWebUI built-in tools** |
|
||||
| **Native Tool Call UI** | Adapted to OpenWebUI's built-in tool call rendering |
|
||||
| **Workspace Isolation** | Strict sandboxing for per-session data privacy and security |
|
||||
| **Workspace Artifacts** | Agents generate files (Excel/CSV/HTML) with persistent download links |
|
||||
| **Multi-storage Publishing** | File publish works across local disk, S3, GCS, and Azure backends |
|
||||
| **11-Language Localization** | Auto-detected, native status messages for global users |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Migration Notes
|
||||
|
||||
- **No breaking changes** for existing users. The P4 whitelist is only active when the user has explicitly selected tools in the Chat UI; with no selection, all enabled tools are passed as before.
|
||||
- Users on S3/GCS/Azure deployments who experienced file download failures can now publish normally without any additional configuration.
|
||||
- `ENABLE_TOOL_CACHE` Valve has been removed. Tool server connections are now always read fresh from the database to avoid stale state across multiple workers. Use `MODEL_CACHE_TTL` for model list caching.
|
||||
|
||||
---
|
||||
|
||||
## 📥 Import Chat Templates
|
||||
|
||||
- [📥 Star Prediction Chat log](https://fu-jie.github.io/awesome-openwebui/plugins/pipes/star-prediction-chat.json)
|
||||
- [📥 Video Processing Chat log](https://fu-jie.github.io/awesome-openwebui/plugins/pipes/video-processing-chat.json)
|
||||
|
||||
*Settings → Data → Import Chats.*
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Resources
|
||||
|
||||
- **GitHub Repository**: [openwebui-extensions](https://github.com/Fu-Jie/openwebui-extensions)
|
||||
- **Full Changelog**: [README.md](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/pipes/github-copilot-sdk/README.md)
|
||||
131
plugins/pipes/github-copilot-sdk/v0.8.0_CN.md
Normal file
131
plugins/pipes/github-copilot-sdk/v0.8.0_CN.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# 🚀 GitHub Copilot SDK Pipe v0.8.0:条件工具过滤 & 发布可靠性全面升级 🎛️
|
||||
|
||||
**GitHub Copilot SDK Pipe v0.8.0** — 一次重大的权限管控与稳定性升级。本次更新引入了四优先级工具权限体系,使工具访问可按每次对话精细配置;同时彻底修复了所有存储后端的文件发布问题,并确保 CLI 内置工具不再被意外屏蔽。
|
||||
|
||||
---
|
||||
|
||||
## 📦 快速安装
|
||||
|
||||
- **GitHub Copilot SDK (Pipe 插件)**: [安装 v0.8.0](https://openwebui.com/posts/ce96f7b4-12fc-4ac3-9a01-875713e69359)
|
||||
- **GitHub Copilot SDK (Filter 插件)**: [安装 v0.1.3](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 v0.8.0 更新内容
|
||||
|
||||
### 1. 条件工具过滤 (P1~P4) — 核心新特性
|
||||
|
||||
本次更新引入了**四优先级工具权限体系**,为您提供对每次对话中可用工具的精确控制。
|
||||
|
||||
| 优先级 | 作用域 | 机制描述 |
|
||||
| :--- | :--- | :--- |
|
||||
| **P1** | 全局 | Pipe 级 Valve 开关(`ENABLE_OPENWEBUI_TOOLS`、`ENABLE_OPENAPI_SERVER`、`ENABLE_MCP`) |
|
||||
| **P2** | 服务器级 | 管理员 `config.enable` — 无需修改代码,直接从 OpenWebUI 管理面板禁用某个工具服务器 |
|
||||
| **P3** | 用户级 | 用户专属 `UserValves` 覆盖设置 |
|
||||
| **P4** | 对话级 | **Chat UI 工具选择** — 只有用户在输入框明确勾选的工具才会在本次对话中激活 |
|
||||
|
||||
**P4 工作原理**:OpenWebUI 会将用户当前选中的工具 ID 通过 `__metadata__` 中的 `tool_ids` 字段传入。插件读取该白名单并对 **OpenWebUI 工具**和 **MCP server** 同时进行过滤。
|
||||
|
||||
- **默认全开 (Default ON)**:如果您在 Chat UI 中**未勾选任何工具**(留空),只要 P1 全局 Valve 开启,插件就会默认启用**所有可用工具**。OpenWebUI 工具、OpenAPI Server 和 MCP Server 将默认全部挂载。
|
||||
- **按需激活 (Whitelist Mode)**:一旦您在输入框主动勾选了至少一个工具(例如只选了“联网搜索”),则**只有您选中的工具会生效**,其他未选中的工具(包括 MCP 服务器)都将被过滤掉。
|
||||
|
||||
```
|
||||
用户在 Chat UI 勾选 ✅ web-search,✅ code-runner
|
||||
↓
|
||||
chat_tool_ids = ["web-search", "server:code-runner"]
|
||||
↓
|
||||
过滤后工具列表 = 仅包含上述两个工具
|
||||
↓
|
||||
白名单外的 MCP server 将被完全跳过
|
||||
```
|
||||
|
||||
**P2 管理员管控**:OpenWebUI 中每个工具服务器条目现在都有 `config.enable` 字段。从管理面板将其设为 `false` 即可全局禁用该服务器,无需修改任何 Valve 配置。
|
||||
|
||||
### 2. 文件发布全面修复 — 适配所有存储后端
|
||||
|
||||
Agent 发布文件时持续出现的 `{"detail":"[ERROR: Error getting file content]"}` 错误已被彻底解决。
|
||||
|
||||
| 根本原因 | 修复方案 |
|
||||
| :--- | :--- |
|
||||
| 回退路径使用 `shutil.copy2` + 手动写入 DB,导致 `file.path` 存储的是本地绝对路径,S3 等对象存储后端无法解析 | 回退路径改为直接调用 `Storage.upload_file()`,自动适配 local/S3/GCS/Azure |
|
||||
| HTML 文件被 OpenWebUI `ALLOWED_FILE_EXTENSIONS` 内容类型检查拦截 | 上传 URL 始终追加 `?process=false`,绕过文件类型限制 |
|
||||
|
||||
当发布产物为 HTML 文件时,插件还会返回一个可直接访问的 HTML 链接,便于在聊天中即时打开/预览。
|
||||
|
||||
所有发布链接现统一执行严格格式:必须是以 `/api/v1/files/` 开头的相对路径。
|
||||
`api/...`(缺少前导 `/`)或拼接域名的绝对 URL 都视为无效格式。
|
||||
|
||||
### 3. CLI 内置工具始终可用
|
||||
|
||||
`available_tools` 现在统一设为 `None`(不再根据已加载的工具 ID 列表过滤),这意味着 Copilot CLI 内置工具 —— 如 `bash`、`create_file`、`read_file`、`list_directory` —— **默认始终可用**。
|
||||
|
||||
此前,在未配置/未加载任何 server 工具时,CLI 可能收到 `available_tools=[]`,导致所有内置工具被静默屏蔽。
|
||||
|
||||
### 4. 发布工具始终注入
|
||||
|
||||
`publish_file_from_workspace` 工具的注入逻辑提前到 `ENABLE_OPENWEBUI_TOOLS` 守卫之前,因此即使通过 Valve 关闭所有 OpenWebUI 工具加载,该工具也**不再丢失**。
|
||||
|
||||
### 5. 代码解释器警告(未禁用)
|
||||
|
||||
内置的 `code_interpreter` 工具保持**启用**状态,但运行在远程的临时沙箱中。
|
||||
|
||||
为了避免混淆,系统提示词中会自动注入一段警告说明:
|
||||
> "The `code_interpreter` tool executes code in a remote, ephemeral environment. It cannot access files in your local workspace or persist changes."
|
||||
|
||||
请仅将其用于纯计算或逻辑验证,勿用于文件操作。
|
||||
|
||||
### 6. Bug 修复汇总
|
||||
|
||||
- **文件发布后端不匹配**:修复了对象存储场景下因本地路径回退写入导致的 `Error getting file content`。
|
||||
- **HTML 产物扩展名拦截**:修复了 OpenWebUI 扩展名处理导致 HTML 上传被拒的问题,统一强制 `?process=false`。
|
||||
- **产物链接格式错误**:修复了链接偶发被生成成 `api/...` 或带域名 URL 的问题,现统一限制为 `/api/v1/files/...` 相对路径。
|
||||
- **CLI 内置工具被静默屏蔽**:修复了在未配置/未加载任何 server 工具时(最终出现 `available_tools=[]`)导致内置工具不可用的问题,默认保持 `None`。
|
||||
- **发布工具注入丢失**:修复了 `ENABLE_OPENWEBUI_TOOLS=False` 时 `publish_file_from_workspace` 被遗漏的问题。
|
||||
|
||||
### 7. 在 Pipe 文档中整合 Files Filter 指南
|
||||
|
||||
本次已将 `GitHub Copilot SDK Files Filter` 的核心说明整合进 Pipe 文档,用户可在单页完成完整配置:
|
||||
|
||||
- 阻止默认 RAG 在 Pipe 接手前抢先处理上传文件。
|
||||
- 通过将上传文件移动到 `copilot_files` 保留原始二进制访问能力。
|
||||
- 纳入 v0.1.3 重点:BYOK 模型 ID 前缀匹配修复 + 可选双通道调试日志。
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 核心能力
|
||||
|
||||
| 特性 | 描述 |
|
||||
| :--- | :--- |
|
||||
| **条件工具过滤 (P1~P4)** | 多优先级权限体系:全局 Valve → 管理员 config.enable → 用户 Valve → Chat UI 选择 |
|
||||
| **通用工具协议** | 原生支持 **MCP**、**OpenAPI** 以及 **OpenWebUI 内置工具** |
|
||||
| **原生工具 UI** | 完美适配 OpenWebUI 内置的工具调用渲染 |
|
||||
| **物理隔离工作区** | 为每个会话提供严格的沙箱环境,保护数据隐私与安全 |
|
||||
| **工作区产物工具** | Agent 可生成文件(Excel/CSV/HTML)并提供持久化下载链接 |
|
||||
| **多存储后端发布** | 文件发布兼容本地磁盘、S3、GCS 及 Azure 后端 |
|
||||
| **11 国语言本地化** | 自动检测并显示原生语言的状态消息 |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 迁移说明
|
||||
|
||||
- **无破坏性变更**。P4 白名单过滤仅在用户主动在 Chat UI 选择了工具时生效;未做任何选择时,所有已启用的工具照常传递给 Copilot。
|
||||
- 使用 S3/GCS/Azure 存储后端、曾遭遇文件下载失败的用户,无需任何额外配置即可正常发布文件。
|
||||
- `ENABLE_TOOL_CACHE` Valve 已移除。工具服务器连接现在始终从数据库实时读取,避免多 Worker 环境下的状态过时。模型列表缓存请使用 `MODEL_CACHE_TTL`。
|
||||
|
||||
---
|
||||
|
||||
## 📥 导入对话模板
|
||||
|
||||
您可以下载并导入真实案例的 JSON 对话日志,直观查看模型如何调用工具:
|
||||
|
||||
- [📥 对话日志:GitHub 增长预测](https://fu-jie.github.io/awesome-openwebui/plugins/pipes/star-prediction-chat.json)
|
||||
- [📥 对话日志:视频高质量 GIF 转换](https://fu-jie.github.io/awesome-openwebui/plugins/pipes/video-processing-chat.json)
|
||||
|
||||
*导入方式:前往 `设置 → 数据 → 导入对话`。*
|
||||
|
||||
---
|
||||
|
||||
## 🔗 相关资源
|
||||
|
||||
- **GitHub 仓库**: [openwebui-extensions](https://github.com/Fu-Jie/openwebui-extensions)
|
||||
- **完整变更日志**: [README_CN.md](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/pipes/github-copilot-sdk/README_CN.md)
|
||||
Reference in New Issue
Block a user